Skip to content

Artisan Generator โ€‹

Master the powerful make:specification command that generates specifications with domain organization, tests, and multiple templates. Create production-ready specifications in seconds!

Why You'll Love This Generator โ€‹

Building on Laravel's excellent Artisan foundation, the specifications generator adds powerful domain-driven features:

  • ๐Ÿ“ Domain Organization: Automatically organizes specs by business domain
  • ๐Ÿงช Multiple Templates: Choose from 7 different specification types
  • ๐Ÿ”„ Model Integration: Smart model binding with type hints
  • ๐Ÿงต Auto-Testing: Generates comprehensive test files
  • โš™๏ธ Customizable: Publish and modify stub templates

Basic Usage โ€‹

Simple Specification โ€‹

bash
php artisan make:specification UserActiveSpecification

Creates app/Specifications/UserActiveSpecification.php:

php
<?php

namespace App\Specifications;

use DangerWayne\Specification\Specifications\AbstractSpecification;
use Illuminate\Database\Eloquent\Builder;

class UserActiveSpecification extends AbstractSpecification
{
    public function isSatisfiedBy(mixed $candidate): bool
    {
        // TODO: Implement your business rule logic here
        return false;
    }

    public function toQuery(Builder $query): Builder
    {
        // TODO: Implement your query logic here
        return $query;
    }
    
    protected function getParameters(): array
    {
        return [
            // Add constructor parameters for caching
        ];
    }
}

Domain Organization โ€‹

The Power of Namespacing โ€‹

Organize specifications by business domain for better maintainability:

bash
# User domain
php artisan make:specification User/ActiveUserSpecification
php artisan make:specification User/PremiumUserSpecification
php artisan make:specification User/EmailVerifiedSpecification

# Order domain
php artisan make:specification Order/PaidOrderSpecification
php artisan make:specification Order/ShippableOrderSpecification
php artisan make:specification Order/RefundableOrderSpecification

# Product domain
php artisan make:specification Product/AvailableProductSpecification
php artisan make:specification Product/FeaturedProductSpecification

This creates a clean directory structure:

app/Specifications/
โ”œโ”€โ”€ User/
โ”‚   โ”œโ”€โ”€ ActiveUserSpecification.php
โ”‚   โ”œโ”€โ”€ PremiumUserSpecification.php
โ”‚   โ””โ”€โ”€ EmailVerifiedSpecification.php
โ”œโ”€โ”€ Order/
โ”‚   โ”œโ”€โ”€ PaidOrderSpecification.php
โ”‚   โ””โ”€โ”€ ShippableOrderSpecification.php
โ””โ”€โ”€ Product/
    โ”œโ”€โ”€ AvailableProductSpecification.php
    โ””โ”€โ”€ FeaturedProductSpecification.php

Model Binding โ€‹

Generate specifications with smart model integration:

bash
php artisan make:specification User/ActiveUserSpecification --model=User

Creates a specification with model-specific type hints and validation:

php
<?php

namespace App\Specifications\User;

use App\Models\User;
use DangerWayne\Specification\Specifications\AbstractSpecification;
use Illuminate\Database\Eloquent\Builder;

class ActiveUserSpecification extends AbstractSpecification
{
    /**
     * Determine if the user satisfies the specification.
     */
    public function isSatisfiedBy(mixed $candidate): bool
    {
        if (!$candidate instanceof User) {
            return false;
        }

        // TODO: Implement your User business rule logic here
        return false;
    }

    /**
     * Apply the specification to a User query builder.
     * 
     * @param  Builder<User>  $query
     * @return Builder<User>
     */
    public function toQuery(Builder $query): Builder
    {
        // TODO: Implement your User query logic here
        return $query;
    }
}

Template Options โ€‹

1. Composite Specifications โ€‹

For complex specifications that combine multiple conditions:

bash
php artisan make:specification Order/ComplexOrderSpecification --composite
php
class ComplexOrderSpecification extends AbstractSpecification
{
    private array $specifications = [];
    
    public function addSpecification(SpecificationInterface $specification): self
    {
        $this->specifications[] = $specification;
        return $this;
    }
    
    public function isSatisfiedBy(mixed $candidate): bool
    {
        foreach ($this->specifications as $spec) {
            if (!$spec->isSatisfiedBy($candidate)) {
                return false;
            }
        }
        
        return true;
    }
    
    // Additional methods for managing child specifications...
}

2. Cacheable Specifications โ€‹

For expensive operations that benefit from caching:

bash
php artisan make:specification User/ExpensiveUserSpecification --cacheable
php
use DangerWayne\Specification\Traits\CacheableSpecification;

class ExpensiveUserSpecification extends AbstractSpecification
{
    use CacheableSpecification;
    
    protected int $cacheTtl = 3600; // 1 hour
    
    public function getCacheKey(): string
    {
        return 'expensive_user_spec_' . md5(serialize($this->getParameters()));
    }
    
    // Implementation...
}

3. Builder Pattern Specifications โ€‹

For fluent specification building:

bash
php artisan make:specification Search/ProductSearchSpecification --builder
php
use DangerWayne\Specification\Specifications\Builders\SpecificationBuilder;

class ProductSearchSpecification extends AbstractSpecification
{
    private SpecificationInterface $specification;
    
    public function __construct(array $filters = [])
    {
        $this->specification = $this->buildFromFilters($filters);
    }
    
    private function buildFromFilters(array $filters): SpecificationInterface
    {
        $builder = new SpecificationBuilder();
        
        if (!empty($filters['category'])) {
            $builder->whereIn('category_id', $filters['category']);
        }
        
        if (!empty($filters['price_range'])) {
            $builder->whereBetween('price', $filters['price_range'][0], $filters['price_range'][1]);
        }
        
        return $builder->build();
    }
    
    // Delegate to built specification...
}

Testing Integration โ€‹

Generate specifications with comprehensive tests:

bash
php artisan make:specification User/PremiumUserSpecification --model=User --test

Creates both the specification and a test file:

php
// tests/Unit/Specifications/User/PremiumUserSpecificationTest.php
class PremiumUserSpecificationTest extends TestCase
{
    public function test_it_identifies_premium_users()
    {
        $premiumUser = User::factory()->create([
            'subscription_type' => 'premium'
        ]);
        
        $basicUser = User::factory()->create([
            'subscription_type' => 'basic'
        ]);
        
        $spec = new PremiumUserSpecification();
        
        $this->assertTrue($spec->isSatisfiedBy($premiumUser));
        $this->assertFalse($spec->isSatisfiedBy($basicUser));
    }
    
    public function test_it_filters_premium_users_in_query()
    {
        User::factory()->create(['subscription_type' => 'premium']);
        User::factory()->create(['subscription_type' => 'basic']);
        User::factory()->create(['subscription_type' => 'premium']);
        
        $spec = new PremiumUserSpecification();
        $premiumUsers = User::whereSpecification($spec)->get();
        
        $this->assertCount(2, $premiumUsers);
        $this->assertTrue($premiumUsers->every(fn($user) => $user->subscription_type === 'premium'));
    }
}

Pest Testing Support โ€‹

Prefer Pest? No problem:

bash
php artisan make:specification User/ActiveUserSpecification --pest
php
// tests/Unit/Specifications/User/ActiveUserSpecificationTest.php
use App\Models\User;
use App\Specifications\User\ActiveUserSpecification;

it('identifies active users', function () {
    $activeUser = User::factory()->create(['status' => 'active']);
    $inactiveUser = User::factory()->create(['status' => 'inactive']);
    
    $spec = new ActiveUserSpecification();
    
    expect($spec->isSatisfiedBy($activeUser))->toBeTrue();
    expect($spec->isSatisfiedBy($inactiveUser))->toBeFalse();
});

it('filters active users in queries', function () {
    User::factory()->count(3)->create(['status' => 'active']);
    User::factory()->count(2)->create(['status' => 'inactive']);
    
    $spec = new ActiveUserSpecification();
    $activeUsers = User::whereSpecification($spec)->get();
    
    expect($activeUsers)->toHaveCount(3);
    expect($activeUsers->every(fn($user) => $user->status === 'active'))->toBeTrue();
});

Real-World Examples โ€‹

bash
# Generate the main search specification
php artisan make:specification Product/ProductSearchSpecification --composite --cacheable

# Generate individual filter specifications
php artisan make:specification Product/PriceRangeSpecification --model=Product
php artisan make:specification Product/CategorySpecification --model=Product
php artisan make:specification Product/AvailabilitySpecification --model=Product --test

User Management System โ€‹

bash
# Core user specifications
php artisan make:specification User/ActiveUserSpecification --model=User --test
php artisan make:specification User/VerifiedUserSpecification --model=User

# Permission system
php artisan make:specification User/Permission/HasPermissionSpecification --composite
php artisan make:specification User/Permission/RoleBasedAccessSpecification --model=User

# Engagement tracking
php artisan make:specification User/Engagement/ActiveEngagementSpecification --cacheable

Order Processing Pipeline โ€‹

bash
# Order status specifications
php artisan make:specification Order/ReadyToShipSpecification --model=Order --test
php artisan make:specification Order/PaymentProcessedSpecification --model=Order
php artisan make:specification Order/RefundableOrderSpecification --model=Order

# Complex processing rules
php artisan make:specification Order/Processing/OrderProcessingSpecification --composite

Advanced Options โ€‹

Multiple Options Combined โ€‹

bash
# Create a comprehensive specification with all features
php artisan make:specification Order/ComplexOrderProcessingSpecification \
    --model=Order \
    --composite \
    --cacheable \
    --test

Force Overwrite โ€‹

bash
# Overwrite existing specifications
php artisan make:specification User/ActiveUserSpecification --force

Inline Generation โ€‹

bash
# Skip domain organization
php artisan make:specification FlatSpecification --inline

Customizing Templates โ€‹

Publish and customize the stub templates:

bash
php artisan vendor:publish --tag=specification-stubs

This creates customizable stubs in resources/stubs/specification/:

  • specification.stub - Basic template
  • specification.model.stub - Model-bound template
  • specification.composite.stub - Composite template
  • specification.cacheable.stub - Cacheable template
  • specification.test.stub - PHPUnit test template
  • specification.pest.stub - Pest test template

Custom Stub Example โ€‹

Edit resources/stubs/specification/specification.stub:

php
<?php

namespace {{ namespace }};

use DangerWayne\Specification\Specifications\AbstractSpecification;
use Illuminate\Database\Eloquent\Builder;

/**
 * {{ class }}
 * 
 * Business rule: TODO - Document your business rule here
 */
class {{ class }} extends AbstractSpecification
{
    public function __construct(
        // TODO: Add your parameters here
    ) {
        //
    }
    
    public function isSatisfiedBy(mixed $candidate): bool
    {
        // TODO: Implement your business logic here
        return false;
    }
    
    public function toQuery(Builder $query): Builder
    {
        // TODO: Implement your database query here
        return $query;
    }
    
    protected function getParameters(): array
    {
        return [
            // TODO: Return constructor parameters for caching
        ];
    }
}

Pro Tips โ€‹

1. Domain-First Approach โ€‹

Always organize by business domain, not technical concerns:

bash
# Good: Domain organization
php artisan make:specification User/ActiveUserSpecification
php artisan make:specification Order/PaidOrderSpecification
php artisan make:specification Product/AvailableProductSpecification

# Avoid: Technical organization
php artisan make:specification Database/UserDatabaseSpecification
php artisan make:specification API/UserAPISpecification

2. Descriptive Names โ€‹

Use descriptive, business-focused names:

bash
# Good: Clear business intent
php artisan make:specification User/EligibleForDiscountSpecification
php artisan make:specification Order/ReadyForShippingSpecification
php artisan make:specification Product/InStockAndAvailableSpecification

# Avoid: Generic or technical names
php artisan make:specification User/UserCheckSpecification
php artisan make:specification Order/OrderValidatorSpecification

3. Test Everything Important โ€‹

Always generate tests for critical business logic:

bash
# Critical business rules should always have tests
php artisan make:specification User/EligibleForLoanSpecification --model=User --test
php artisan make:specification Order/HighRiskOrderSpecification --model=Order --test
php artisan make:specification Payment/FraudDetectionSpecification --model=Payment --test

What You've Mastered โ€‹

Congratulations! You now know how to:

  • โœ… Generate specifications with domain organization
  • โœ… Use model binding for type safety
  • โœ… Create composite and cacheable specifications
  • โœ… Generate comprehensive test suites
  • โœ… Customize templates for your team's standards
  • โœ… Follow best practices for naming and organization

Quick Start Complete! โ€‹

You've now completed the Laravel Specifications Quick Start! You've learned:

  1. โœ… Installation - Set up the package
  2. โœ… Your First Specification - Transform messy logic
  3. โœ… Artisan Generator - Master the code generator

What's Next? โ€‹

Ready to dive deeper? Explore these sections:

  • ๐Ÿฝ๏ธ Appetizers - See dramatic before/after transformations
  • ๐Ÿ“š Complete Guide - Master all features and advanced patterns
  • ๐Ÿ’ก Examples - Real-world implementations
  • ๐Ÿ—๏ธ API Reference - Complete technical documentation

You're Ready!

You now have everything you need to start writing clean, maintainable Laravel applications with the Specification Pattern. Happy coding!

Explore More Examples โ†’

Released under the MIT License.