NotSpecification
A composite specification that negates another specification, inverting its truth value. If the wrapped specification is satisfied, the NotSpecification is not satisfied, and vice versa.
Namespace
php
DangerWayne\Specification\Specifications\Composite\NotSpecification
Constructor
php
public function __construct(
SpecificationInterface $specification
)
Parameters
$specification
(SpecificationInterface) - The specification to negate
Usage Examples
Basic Negation
php
use DangerWayne\Specification\Specifications\Common\WhereSpecification;
use DangerWayne\Specification\Specifications\Composite\NotSpecification;
// Users who are NOT admin
$adminSpec = new WhereSpecification('role', 'admin');
$notAdminSpec = new NotSpecification($adminSpec);
// Apply to query
$nonAdminUsers = User::whereSpecification($notAdminSpec)->get();
// Use with collection
$users = User::all();
$regularUsers = $users->filter(function($user) use ($notAdminSpec) {
return $notAdminSpec->isSatisfiedBy($user);
});
Fluent Interface (Recommended)
Instead of using NotSpecification directly, use the fluent not()
method:
php
$adminSpec = new WhereSpecification('role', 'admin');
// More readable than new NotSpecification($adminSpec)
$notAdminSpec = $adminSpec->not();
$nonAdminUsers = User::whereSpecification($notAdminSpec)->get();
Common Scenarios
Exclusion Filters
php
// Exclude deleted records
$deletedSpec = new WhereNullSpecification('deleted_at', false);
$activeRecordsSpec = $deletedSpec->not();
$activeUsers = User::whereSpecification($activeRecordsSpec)->get();
Status Exclusion
php
// Orders that are NOT completed
$completedSpec = new WhereSpecification('status', 'completed');
$pendingOrdersSpec = $completedSpec->not();
$pendingOrders = Order::whereSpecification($pendingOrdersSpec)->get();
Role-Based Exclusion
php
// Content visible to non-admin users
$adminOnlySpec = new WhereSpecification('visibility', 'admin_only');
$publicContentSpec = $adminOnlySpec->not();
$publicPosts = Post::whereSpecification($publicContentSpec)->get();
Complex Negations
Negating Composite Specifications
php
// NOT (Admin AND Active)
$adminSpec = new WhereSpecification('role', 'admin');
$activeSpec = new WhereSpecification('status', 'active');
$adminActiveSpec = $adminSpec->and($activeSpec);
$notAdminActiveSpec = $adminActiveSpec->not();
// This matches users who are either NOT admin OR NOT active (or both)
$users = User::whereSpecification($notAdminActiveSpec)->get();
Double Negation
php
// NOT (NOT Active) = Active
$activeSpec = new WhereSpecification('status', 'active');
$notActiveSpec = $activeSpec->not();
$doubleNegationSpec = $notActiveSpec->not(); // Equivalent to $activeSpec
$activeUsers = User::whereSpecification($doubleNegationSpec)->get();
De Morgan's Laws in Action
php
// NOT (A AND B) = (NOT A) OR (NOT B)
$aSpec = new WhereSpecification('field_a', 'value_a');
$bSpec = new WhereSpecification('field_b', 'value_b');
// These are logically equivalent:
$notAndSpec = $aSpec->and($bSpec)->not();
$orNotSpec = $aSpec->not()->or($bSpec->not());
// NOT (A OR B) = (NOT A) AND (NOT B)
$notOrSpec = $aSpec->or($bSpec)->not();
$andNotSpec = $aSpec->not()->and($bSpec->not());
Real-World Examples
Content Moderation
php
// Posts that are NOT flagged as inappropriate
$inappropriateSpec = new WhereSpecification('content_flag', 'inappropriate');
$appropriateContentSpec = $inappropriateSpec->not();
// Posts that are NOT from suspended authors
$suspendedAuthorSpec = new WhereHasSpecification('author',
new WhereSpecification('status', 'suspended')
);
$fromActiveAuthorsSpec = $suspendedAuthorSpec->not();
// Combine: appropriate content from active authors
$displayableSpec = $appropriateContentSpec->and($fromActiveAuthorsSpec);
$displayablePosts = Post::whereSpecification($displayableSpec)->get();
User Segmentation
php
// Users who are NOT in the target demographic
$targetAgeSpec = new WhereBetweenSpecification('age', 25, 45);
$targetLocationSpec = new WhereInSpecification('country', ['US', 'CA', 'GB']);
$premiumUserSpec = new WhereSpecification('subscription_type', 'premium');
$targetDemographicSpec = $targetAgeSpec
->and($targetLocationSpec)
->and($premiumUserSpec);
$nonTargetUsersSpec = $targetDemographicSpec->not();
// Users for alternative marketing campaign
$alternativeAudienceUsers = User::whereSpecification($nonTargetUsersSpec)->get();
Inventory Management
php
// Products that are NOT out of stock
$outOfStockSpec = new WhereSpecification('stock_quantity', 0);
$inStockSpec = $outOfStockSpec->not();
// Products that are NOT discontinued
$discontinuedSpec = new WhereSpecification('status', 'discontinued');
$activeProductsSpec = $discontinuedSpec->not();
// Available products: in stock and not discontinued
$availableSpec = $inStockSpec->and($activeProductsSpec);
$availableProducts = Product::whereSpecification($availableSpec)->get();
Access Control
php
// Resources NOT restricted to admin users
$adminOnlySpec = new WhereSpecification('access_level', 'admin_only');
$publicResourcesSpec = $adminOnlySpec->not();
// Content NOT requiring premium subscription
$premiumOnlySpec = new WhereSpecification('requires_premium', true);
$freeContentSpec = $premiumOnlySpec->not();
// Publicly accessible content
$publicContentSpec = $publicResourcesSpec->and($freeContentSpec);
$publicContent = Content::whereSpecification($publicContentSpec)->get();
Implementation Details
isSatisfiedBy() Method
php
public function isSatisfiedBy(mixed $candidate): bool
{
return !$this->specification->isSatisfiedBy($candidate);
}
toQuery() Method
php
public function toQuery(Builder $query): Builder
{
return $query->whereNot(function ($subQuery) {
$this->specification->toQuery($subQuery);
});
}
Performance Considerations
Query Optimization
- Index Usage: NOT conditions can prevent index usage in some cases
- Subquery Performance: NOT wraps conditions in a subquery which can be expensive
- Alternative Approaches: Sometimes positive conditions are more efficient
- Database Differences: Different databases handle NOT differently
php
// Consider positive alternatives when possible
// Instead of NOT deleted_at IS NOT NULL
$notDeletedSpec = new WhereNullSpecification('deleted_at', false)->not();
// Use the more direct approach:
$activeSpec = new WhereNullSpecification('deleted_at');
Query Plan Analysis
php
// Complex NOT conditions can be expensive
$complexNotSpec = $spec1->and($spec2)->or($spec3)->not();
// Consider rewriting using De Morgan's laws for better performance
$optimizedSpec = $spec1->not()->or($spec2->not())->and($spec3->not());
Testing
php
use Tests\TestCase;
use DangerWayne\Specification\Specifications\Common\WhereSpecification;
use DangerWayne\Specification\Specifications\Composite\NotSpecification;
class NotSpecificationTest extends TestCase
{
public function test_it_inverts_specification_result()
{
User::factory()->create(['status' => 'active']);
User::factory()->create(['status' => 'inactive']);
User::factory()->create(['status' => 'suspended']);
$activeSpec = new WhereSpecification('status', 'active');
$notActiveSpec = new NotSpecification($activeSpec);
$users = User::whereSpecification($notActiveSpec)->get();
$this->assertCount(2, $users);
$this->assertFalse($users->contains('status', 'active'));
$this->assertTrue($users->contains('status', 'inactive'));
$this->assertTrue($users->contains('status', 'suspended'));
}
public function test_fluent_not_method_works_identically()
{
User::factory()->create(['role' => 'admin']);
User::factory()->create(['role' => 'user']);
User::factory()->create(['role' => 'moderator']);
$adminSpec = new WhereSpecification('role', 'admin');
// Direct NotSpecification
$directNot = new NotSpecification($adminSpec);
$directResults = User::whereSpecification($directNot)->get();
// Fluent not() method
$fluentNot = $adminSpec->not();
$fluentResults = User::whereSpecification($fluentNot)->get();
$this->assertEquals($directResults->count(), $fluentResults->count());
$this->assertEquals(
$directResults->pluck('id')->sort()->values(),
$fluentResults->pluck('id')->sort()->values()
);
}
public function test_double_negation_equals_original()
{
User::factory()->create(['status' => 'active']);
User::factory()->create(['status' => 'inactive']);
$activeSpec = new WhereSpecification('status', 'active');
$doubleNegationSpec = $activeSpec->not()->not();
$originalResults = User::whereSpecification($activeSpec)->get();
$doubleNegationResults = User::whereSpecification($doubleNegationSpec)->get();
$this->assertEquals($originalResults->count(), $doubleNegationResults->count());
$this->assertEquals(
$originalResults->pluck('id')->sort()->values(),
$doubleNegationResults->pluck('id')->sort()->values()
);
}
public function test_de_morgans_law_not_and_equals_or_not()
{
User::factory()->create(['role' => 'admin', 'status' => 'active']);
User::factory()->create(['role' => 'admin', 'status' => 'inactive']);
User::factory()->create(['role' => 'user', 'status' => 'active']);
User::factory()->create(['role' => 'user', 'status' => 'inactive']);
$adminSpec = new WhereSpecification('role', 'admin');
$activeSpec = new WhereSpecification('status', 'active');
// NOT (admin AND active)
$notAndSpec = $adminSpec->and($activeSpec)->not();
// (NOT admin) OR (NOT active)
$orNotSpec = $adminSpec->not()->or($activeSpec->not());
$notAndResults = User::whereSpecification($notAndSpec)->get();
$orNotResults = User::whereSpecification($orNotSpec)->get();
$this->assertEquals($notAndResults->count(), $orNotResults->count());
$this->assertEquals(
$notAndResults->pluck('id')->sort()->values(),
$orNotResults->pluck('id')->sort()->values()
);
}
}
Advanced Patterns
Conditional Negation
php
class ConditionalNotSpecification
{
public static function create(
SpecificationInterface $spec,
bool $shouldNegate = false
): SpecificationInterface {
return $shouldNegate ? $spec->not() : $spec;
}
}
// Usage
$includeInactive = $request->boolean('include_inactive');
$activeSpec = new WhereSpecification('status', 'active');
$finalSpec = ConditionalNotSpecification::create($activeSpec, !$includeInactive);
Exclusion Lists
php
class ExclusionSpecification
{
private array $exclusions = [];
public function exclude(SpecificationInterface $spec): self
{
$this->exclusions[] = $spec;
return $this;
}
public function build(): ?SpecificationInterface
{
if (empty($this->exclusions)) {
return null;
}
return collect($this->exclusions)
->map(fn($spec) => $spec->not())
->reduce(fn($carry, $spec) => $carry ? $carry->and($spec) : $spec);
}
}
// Usage
$exclusionSpec = (new ExclusionSpecification())
->exclude(new WhereSpecification('status', 'banned'))
->exclude(new WhereSpecification('role', 'spam'))
->exclude(new WhereNullSpecification('email_verified_at', false))
->build();
Whitelist vs Blacklist
php
class AccessControlSpecification
{
public static function whitelist(array $allowedValues, string $field): SpecificationInterface
{
return new WhereInSpecification($field, $allowedValues);
}
public static function blacklist(array $deniedValues, string $field): SpecificationInterface
{
return (new WhereInSpecification($field, $deniedValues))->not();
}
}
// Usage
$whitelistSpec = AccessControlSpecification::whitelist(['admin', 'moderator'], 'role');
$blacklistSpec = AccessControlSpecification::blacklist(['banned', 'suspended'], 'status');
$allowedUsersSpec = $whitelistSpec->and($blacklistSpec);
Logical Laws and Transformations
De Morgan's Laws
php
// NOT (A AND B) = (NOT A) OR (NOT B)
function notAnd(SpecificationInterface $a, SpecificationInterface $b): SpecificationInterface
{
// These are equivalent:
$version1 = $a->and($b)->not();
$version2 = $a->not()->or($b->not());
return $version2; // Often more efficient
}
// NOT (A OR B) = (NOT A) AND (NOT B)
function notOr(SpecificationInterface $a, SpecificationInterface $b): SpecificationInterface
{
// These are equivalent:
$version1 = $a->or($b)->not();
$version2 = $a->not()->and($b->not());
return $version2; // Often more efficient
}
Double Negation Elimination
php
class OptimizedSpecification
{
public static function optimize(SpecificationInterface $spec): SpecificationInterface
{
// Eliminate double negation: NOT (NOT A) = A
if ($spec instanceof NotSpecification) {
$inner = $spec->getSpecification();
if ($inner instanceof NotSpecification) {
return self::optimize($inner->getSpecification());
}
}
return $spec;
}
}
Best Practices
1. Use Fluent Interface
php
// Preferred: More readable
$spec = $condition->not();
// Avoid: Constructor approach
$spec = new NotSpecification($condition);
2. Consider Positive Alternatives
php
// Sometimes positive conditions are clearer and more efficient
// Instead of:
$notDeletedSpec = new WhereNullSpecification('deleted_at', false)->not();
// Use:
$activeSpec = new WhereNullSpecification('deleted_at');
3. Apply De Morgan's Laws for Performance
php
// Less efficient: complex NOT
$complexNotSpec = $spec1->and($spec2)->or($spec3)->not();
// More efficient: distributed NOT
$distributedSpec = $spec1->not()->or($spec2->not())->and($spec3->not());
4. Use Descriptive Variable Names
php
// Good: Clear intent
$adminSpec = new WhereSpecification('role', 'admin');
$nonAdminSpec = $adminSpec->not();
// Less clear
$spec = new WhereSpecification('role', 'admin');
$notSpec = $spec->not();
Common Anti-Patterns
Avoid Unnecessary Negation
php
// Anti-pattern: Double negation
$spec = $condition->not()->not(); // Just use $condition
// Anti-pattern: Negating simple conditions
$notActiveSpec = new WhereSpecification('status', 'active')->not();
// Better: Use direct condition
$inactiveSpec = new WhereSpecification('status', 'inactive');
Avoid Complex Nested Negations
php
// Anti-pattern: Hard to understand
$complexSpec = $spec1->not()->and($spec2->not()->or($spec3->not()))->not();
// Better: Break down and use positive logic where possible
$simpleSpec1 = $spec1->or($spec2->and($spec3));
See Also
- AndSpecification - Combine specifications with AND logic
- OrSpecification - Combine specifications with OR logic
- AbstractSpecification - Base class with composite methods
- SpecificationBuilder - Fluent interface for building specifications