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
DangerWayne\Specification\Specifications\Builders\SpecificationBuilder
Basic Usage
Via Facade
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
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.
where(string $field, mixed $operator = null, mixed $value = null): self
Examples:
// Equality (two parameters)
$builder->where('status', 'active');
// Comparison (three parameters)
$builder->where('age', '>=', 18);
$builder->where('price', '<', 100);
whereIn()
Add a WHERE IN condition.
whereIn(string $field, array $values): self
Example:
$builder->whereIn('status', ['active', 'pending', 'approved']);
whereNotIn()
Add a WHERE NOT IN condition.
whereNotIn(string $field, array $values): self
Example:
$builder->whereNotIn('role', ['banned', 'suspended']);
whereBetween()
Add a WHERE BETWEEN condition.
whereBetween(string $field, mixed $min, mixed $max): self
Example:
$builder->whereBetween('age', 18, 65);
$builder->whereBetween('price', 10.00, 99.99);
whereNotBetween()
Add a WHERE NOT BETWEEN condition.
whereNotBetween(string $field, mixed $min, mixed $max): self
Example:
$builder->whereNotBetween('score', 0, 50);
whereNull()
Add a WHERE NULL condition.
whereNull(string $field): self
Example:
$builder->whereNull('deleted_at');
whereNotNull()
Add a WHERE NOT NULL condition.
whereNotNull(string $field): self
Example:
$builder->whereNotNull('email_verified_at');
whereHas()
Add a WHERE HAS relationship condition.
whereHas(string $relation, SpecificationInterface $specification = null): self
Examples:
// 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.
or(): self
Example:
$builder
->where('role', 'admin')
->or()
->where('role', 'moderator');
and()
Explicitly start an AND group (default behavior).
and(): self
Example:
$builder
->where('status', 'active')
->and()
->where('verified', true);
build()
Build and return the final specification.
build(): SpecificationInterface
Example:
$spec = $builder
->where('status', 'active')
->whereNotNull('email')
->build();
raw()
Add a raw specification to the builder.
raw(SpecificationInterface $specification): self
Example:
$customSpec = new CustomBusinessRuleSpecification();
$builder->raw($customSpec);
Complex Examples
E-commerce Product Search
$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
$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
$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:
$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:
// 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:
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:
// 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
// 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
$complexSpec = Cache::remember('complex-spec-key', 3600, function() {
return Specification::create()
->whereHas('orders', /* ... */)
->whereHas('reviews', /* ... */)
->where(/* ... */)
->build();
});
Testing
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
$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
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
- SpecificationInterface - Core interface
- WhereSpecification - Basic WHERE conditions
- Composite Specifications - AND, OR, NOT operations
- Artisan Generator - Generate specifications