Skip to content

SpecificationBuilder

The SpecificationBuilder provides a fluent interface for building complex specifications using method chaining. It offers a Laravel-like query builder experience for creating specifications.

Namespace

php
DangerWayne\Specification\Specifications\Builders\SpecificationBuilder

Basic Usage

Via Facade

php
use DangerWayne\Specification\Facades\Specification;

$spec = Specification::create()
    ->where('status', 'active')
    ->where('age', '>=', 18)
    ->whereNotNull('email_verified_at')
    ->build();

$users = User::whereSpecification($spec)->get();

Direct Instantiation

php
use DangerWayne\Specification\Specifications\Builders\SpecificationBuilder;

$builder = new SpecificationBuilder();
$spec = $builder
    ->where('status', 'active')
    ->whereIn('role', ['admin', 'moderator'])
    ->build();

Available Methods

where()

Add a basic WHERE condition.

php
where(string $field, mixed $operator = null, mixed $value = null): self

Examples:

php
// Equality (two parameters)
$builder->where('status', 'active');

// Comparison (three parameters)
$builder->where('age', '>=', 18);
$builder->where('price', '<', 100);

whereIn()

Add a WHERE IN condition.

php
whereIn(string $field, array $values): self

Example:

php
$builder->whereIn('status', ['active', 'pending', 'approved']);

whereNotIn()

Add a WHERE NOT IN condition.

php
whereNotIn(string $field, array $values): self

Example:

php
$builder->whereNotIn('role', ['banned', 'suspended']);

whereBetween()

Add a WHERE BETWEEN condition.

php
whereBetween(string $field, mixed $min, mixed $max): self

Example:

php
$builder->whereBetween('age', 18, 65);
$builder->whereBetween('price', 10.00, 99.99);

whereNotBetween()

Add a WHERE NOT BETWEEN condition.

php
whereNotBetween(string $field, mixed $min, mixed $max): self

Example:

php
$builder->whereNotBetween('score', 0, 50);

whereNull()

Add a WHERE NULL condition.

php
whereNull(string $field): self

Example:

php
$builder->whereNull('deleted_at');

whereNotNull()

Add a WHERE NOT NULL condition.

php
whereNotNull(string $field): self

Example:

php
$builder->whereNotNull('email_verified_at');

whereHas()

Add a WHERE HAS relationship condition.

php
whereHas(string $relation, SpecificationInterface $specification = null): self

Examples:

php
// Check relationship exists
$builder->whereHas('posts');

// Check relationship with specification
$publishedSpec = new WhereSpecification('status', 'published');
$builder->whereHas('posts', $publishedSpec);

or()

Start an OR group for the next condition.

php
or(): self

Example:

php
$builder
    ->where('role', 'admin')
    ->or()
    ->where('role', 'moderator');

and()

Explicitly start an AND group (default behavior).

php
and(): self

Example:

php
$builder
    ->where('status', 'active')
    ->and()
    ->where('verified', true);

build()

Build and return the final specification.

php
build(): SpecificationInterface

Example:

php
$spec = $builder
    ->where('status', 'active')
    ->whereNotNull('email')
    ->build();

raw()

Add a raw specification to the builder.

php
raw(SpecificationInterface $specification): self

Example:

php
$customSpec = new CustomBusinessRuleSpecification();
$builder->raw($customSpec);

Complex Examples

php
$spec = Specification::create()
    ->whereIn('category_id', $request->categories)
    ->whereBetween('price', $request->min_price, $request->max_price)
    ->where('in_stock', true)
    ->whereHas('reviews', new WhereSpecification('rating', '>=', 4))
    ->build();

$products = Product::whereSpecification($spec)->get();

User Permission System

php
$spec = Specification::create()
    ->where('role', 'admin')
    ->or()
    ->where('role', 'moderator')
    ->or()
    ->where('permissions', 'like', '%manage_users%')
    ->and()
    ->whereNotNull('email_verified_at')
    ->build();

$authorizedUsers = User::whereSpecification($spec)->get();

Advanced Report Filtering

php
$reportSpec = Specification::create()
    ->whereBetween('created_at', $startDate, $endDate)
    ->whereIn('status', ['completed', 'shipped'])
    ->whereHas('customer', 
        Specification::create()
            ->where('type', 'premium')
            ->where('country', $country)
            ->build()
    )
    ->whereNotNull('payment_confirmed_at')
    ->build();

$sales = Sale::whereSpecification($reportSpec)->get();

Method Chaining Pattern

The builder uses a fluent interface pattern, allowing unlimited chaining:

php
$spec = Specification::create()
    ->where('a', 1)           // AND a = 1
    ->where('b', 2)           // AND b = 2
    ->or()                    // Start OR group
    ->where('c', 3)           // OR c = 3
    ->and()                   // Back to AND
    ->where('d', 4)           // AND d = 4
    ->whereIn('e', [5, 6])    // AND e IN (5, 6)
    ->or()                    // Start OR group
    ->whereNull('f')          // OR f IS NULL
    ->build();

// Resulting logic: (a = 1 AND b = 2) OR (c = 3) AND (d = 4 AND e IN (5, 6)) OR (f IS NULL)

Building Custom Specifications

You can mix the builder with custom specifications:

php
// Custom specification
class VipCustomerSpecification extends AbstractSpecification
{
    // Implementation...
}

// Mix with builder
$spec = Specification::create()
    ->where('status', 'active')
    ->raw(new VipCustomerSpecification())
    ->whereHas('orders')
    ->build();

Reusable Specification Builders

Create reusable builders for common patterns:

php
class UserSpecificationBuilder
{
    private SpecificationBuilder $builder;
    
    public function __construct()
    {
        $this->builder = new SpecificationBuilder();
    }
    
    public function active(): self
    {
        $this->builder->where('status', 'active');
        return $this;
    }
    
    public function verified(): self
    {
        $this->builder->whereNotNull('email_verified_at');
        return $this;
    }
    
    public function withRole(string $role): self
    {
        $this->builder->where('role', $role);
        return $this;
    }
    
    public function build(): SpecificationInterface
    {
        return $this->builder->build();
    }
}

// Usage
$userBuilder = new UserSpecificationBuilder();
$spec = $userBuilder
    ->active()
    ->verified()
    ->withRole('premium')
    ->build();

Performance Tips

1. Order Matters

Place the most selective conditions first:

php
// Good: Most selective first
$spec = Specification::create()
    ->where('unique_code', $code)  // Very selective
    ->where('status', 'active')    // Less selective
    ->build();

// Less optimal
$spec = Specification::create()
    ->where('status', 'active')    // Less selective
    ->where('unique_code', $code)  // Very selective
    ->build();

2. Use Appropriate Methods

php
// Good: Use whereBetween for ranges
$spec = Specification::create()
    ->whereBetween('age', 18, 65)
    ->build();

// Less optimal: Multiple WHERE conditions
$spec = Specification::create()
    ->where('age', '>=', 18)
    ->where('age', '<=', 65)
    ->build();

3. Cache Complex Specifications

php
$complexSpec = Cache::remember('complex-spec-key', 3600, function() {
    return Specification::create()
        ->whereHas('orders', /* ... */)
        ->whereHas('reviews', /* ... */)
        ->where(/* ... */)
        ->build();
});

Testing

php
use Tests\TestCase;
use DangerWayne\Specification\Facades\Specification;

class SpecificationBuilderTest extends TestCase
{
    public function test_it_builds_complex_specifications()
    {
        $spec = Specification::create()
            ->where('status', 'active')
            ->whereIn('role', ['admin', 'moderator'])
            ->whereNotNull('email_verified_at')
            ->build();
        
        $user = User::factory()->create([
            'status' => 'active',
            'role' => 'admin',
            'email_verified_at' => now(),
        ]);
        
        $this->assertTrue($spec->isSatisfiedBy($user));
    }
    
    public function test_it_handles_or_conditions()
    {
        $spec = Specification::create()
            ->where('role', 'admin')
            ->or()
            ->where('role', 'moderator')
            ->build();
        
        $admin = User::factory()->create(['role' => 'admin']);
        $moderator = User::factory()->create(['role' => 'moderator']);
        $user = User::factory()->create(['role' => 'user']);
        
        $this->assertTrue($spec->isSatisfiedBy($admin));
        $this->assertTrue($spec->isSatisfiedBy($moderator));
        $this->assertFalse($spec->isSatisfiedBy($user));
    }
}

Common Patterns

Conditional Building

php
$builder = Specification::create();

if ($request->has('status')) {
    $builder->where('status', $request->status);
}

if ($request->has('category')) {
    $builder->whereIn('category_id', $request->category);
}

if ($request->has('search')) {
    $builder->where('name', 'like', '%' . $request->search . '%');
}

$spec = $builder->build();

Factory Pattern

php
class SpecificationFactory
{
    public static function activeUsers(): SpecificationInterface
    {
        return Specification::create()
            ->where('status', 'active')
            ->whereNotNull('email_verified_at')
            ->build();
    }
    
    public static function premiumProducts(): SpecificationInterface
    {
        return Specification::create()
            ->where('tier', 'premium')
            ->where('available', true)
            ->build();
    }
}

See Also

Released under the MIT License.