Skip to content

Common Use Cases

Real-world scenarios every Laravel developer faces. Find your situation and see exactly how specifications solve your specific problems.

🛍️ E-commerce & Shopping

Product Search & Filtering

Your Challenge:

"Our product search has 15 different filters, the controller is 200+ lines, and adding new filters breaks existing functionality."

The Specification Solution:

php
// Instead of controller chaos
class ProductController extends Controller
{
    public function search(ProductSearchRequest $request)
    {
        $searchSpec = ProductSearchSpecification::fromRequest($request);
        return Product::whereSpecification($searchSpec)->paginate(20);
    }
}

// Clean, composable search logic
class ProductSearchSpecification extends AbstractSpecification
{
    public static function fromRequest($request): self
    {
        $builder = new SpecificationBuilder();
        
        if ($request->categories) {
            $builder->whereIn('category_id', $request->categories);
        }
        
        if ($request->price_range) {
            $builder->whereBetween('price', $request->price_range[0], $request->price_range[1]);
        }
        
        if ($request->in_stock_only) {
            $builder->where('stock_quantity', '>', 0);
        }
        
        return new self($builder->build());
    }
}

Business Impact:

  • New filters in minutes, not hours
  • Zero regression risk when adding features
  • Reusable filters across mobile API, web, admin panel
  • A/B testing different filter combinations

Shopping Cart Eligibility

Your Challenge:

"We have complex rules for discounts, free shipping, promotions, and they're scattered across controllers, jobs, and policies."

The Specification Solution:

php
class CartEligibilityService
{
    public function checkDiscountEligibility(Cart $cart, User $user): array
    {
        $checks = [
            'free_shipping' => new FreeShippingEligibleSpecification($cart->total),
            'bulk_discount' => new BulkDiscountEligibleSpecification($cart->itemCount),
            'loyalty_discount' => new LoyaltyDiscountEligibleSpecification($user),
            'first_time_buyer' => new FirstTimeBuyerSpecification($user),
            'seasonal_promo' => new SeasonalPromoEligibleSpecification(),
        ];
        
        return collect($checks)->map(function($spec, $type) use ($cart) {
            return [
                'type' => $type,
                'eligible' => $spec->isSatisfiedBy($cart),
                'spec' => $spec
            ];
        })->toArray();
    }
}

👥 User Management & Access Control

User Permission System

Your Challenge:

"Our permission system is a nightmare. We have role checks scattered everywhere, and nobody understands who can access what."

The Specification Solution:

php
// Clear, testable access control
class DocumentAccessSpecification extends AbstractSpecification
{
    public function __construct(private User $user) {}
    
    public function isSatisfiedBy(mixed $candidate): bool
    {
        if (!$candidate instanceof Document) {
            return false;
        }
        
        return $this->ownerAccess($candidate)
            ->or($this->adminAccess())
            ->or($this->teamAccess($candidate))
            ->or($this->publicAccess($candidate))
            ->isSatisfiedBy($candidate);
    }
    
    private function ownerAccess(Document $doc): AbstractSpecification
    {
        return new WhereSpecification('user_id', $this->user->id);
    }
    
    private function adminAccess(): AbstractSpecification
    {
        return $this->user->hasRole('admin') 
            ? new AlwaysTrueSpecification()
            : new AlwaysFalseSpecification();
    }
    
    private function teamAccess(Document $doc): AbstractSpecification
    {
        return new WhereHasSpecification('team.members', 
            new WhereSpecification('user_id', $this->user->id)
        );
    }
    
    private function publicAccess(Document $doc): AbstractSpecification
    {
        return (new WhereSpecification('is_public', true))
            ->and(new WhereSpecification('published_at', '<=', now()));
    }
}

// Use everywhere consistently
class DocumentPolicy
{
    public function view(User $user, Document $document)
    {
        return (new DocumentAccessSpecification($user))->isSatisfiedBy($document);
    }
}

class DocumentController extends Controller
{
    public function index()
    {
        $accessSpec = new DocumentAccessSpecification(auth()->user());
        return Document::whereSpecification($accessSpec)->paginate(10);
    }
}

User Segmentation for Marketing

Your Challenge:

"We need to send targeted emails to different user segments, but the query logic is scattered across multiple marketing tools."

The Specification Solution:

php
class UserSegmentationSpecifications
{
    public static function vipCustomers(): AbstractSpecification
    {
        return (new WhereSpecification('total_spent', '>=', 10000))
            ->and(new WhereSpecification('orders_count', '>=', 20))
            ->and(new WhereHasSpecification('reviews', 
                new WhereSpecification('rating', '>=', 4)
            ));
    }
    
    public static function atRiskCustomers(): AbstractSpecification
    {
        return (new WhereSpecification('last_purchase_at', '<=', now()->subMonths(6)))
            ->and(new WhereSpecification('total_spent', '>=', 500))
            ->and(new WhereSpecification('email_unsubscribed', false));
    }
    
    public static function newCustomers(): AbstractSpecification
    {
        return (new WhereSpecification('created_at', '>=', now()->subDays(30)))
            ->and(new WhereSpecification('orders_count', '<=', 3));
    }
}

// Use in marketing campaigns
class EmailCampaignService
{
    public function sendVipNewsletter()
    {
        User::whereSpecification(UserSegmentationSpecifications::vipCustomers())
            ->chunk(100, function ($users) {
                foreach ($users as $user) {
                    Mail::to($user)->queue(new VipNewsletterMail());
                }
            });
    }
}

📊 Analytics & Reporting

Dynamic Report Filters

Your Challenge:

"Our reporting dashboard has 20+ filter combinations, and the SQL queries are becoming unmaintainable monsters."

The Specification Solution:

php
class SalesReportSpecification extends AbstractSpecification
{
    public function __construct(
        private ?array $dateRange = null,
        private ?array $salespeople = null,
        private ?array $regions = null,
        private ?float $minAmount = null,
        private ?string $customerSegment = null
    ) {}
    
    public static function fromRequest(ReportRequest $request): self
    {
        return new self(
            dateRange: $request->date_range ? [
                Carbon::parse($request->date_range[0]),
                Carbon::parse($request->date_range[1])
            ] : null,
            salespeople: $request->salespeople,
            regions: $request->regions,
            minAmount: $request->min_amount,
            customerSegment: $request->customer_segment
        );
    }
    
    public function toQuery(Builder $query): Builder
    {
        return $this->buildReportCriteria()->toQuery($query);
    }
    
    private function buildReportCriteria(): AbstractSpecification
    {
        $builder = Specification::create();
        
        if ($this->dateRange) {
            $builder->whereBetween('created_at', $this->dateRange[0], $this->dateRange[1]);
        }
        
        if ($this->salespeople) {
            $builder->whereIn('salesperson_id', $this->salespeople);
        }
        
        if ($this->regions) {
            $builder->whereHas('customer', 
                new WhereInSpecification('region', $this->regions)
            );
        }
        
        if ($this->minAmount) {
            $builder->where('total_amount', '>=', $this->minAmount * 100);
        }
        
        if ($this->customerSegment) {
            $builder->raw($this->getCustomerSegmentSpec());
        }
        
        return $builder->build();
    }
}

// Use across multiple report types
class ReportController extends Controller
{
    public function sales(ReportRequest $request)
    {
        $spec = SalesReportSpecification::fromRequest($request);
        
        $data = Sale::whereSpecification($spec)
            ->with(['customer', 'salesperson', 'products'])
            ->get();
            
        return new SalesReportResource($data);
    }
    
    public function exportSales(ReportRequest $request)
    {
        $spec = SalesReportSpecification::fromRequest($request);
        
        return Excel::download(
            new SalesExport($spec), 
            'sales-report.xlsx'
        );
    }
}

📧 Content Management & Moderation

Content Publishing Workflow

Your Challenge:

"We have complex content approval rules based on author reputation, content type, and moderation policies. The logic is everywhere."

The Specification Solution:

php
class ContentPublishableSpecification extends AbstractSpecification
{
    public function __construct(
        private User $author,
        private ?string $contentType = null
    ) {}
    
    public function isSatisfiedBy(mixed $candidate): bool
    {
        if (!$candidate instanceof Content) {
            return false;
        }
        
        return $this->authorEligible()
            ->and($this->contentMeetsStandards($candidate))
            ->and($this->noViolations($candidate))
            ->and($this->typeSpecificRules($candidate))
            ->isSatisfiedBy($candidate);
    }
    
    private function authorEligible(): AbstractSpecification
    {
        return (new WhereSpecification('reputation_score', '>=', 50))
            ->and(new WhereSpecification('account_status', 'active'))
            ->and(new WhereSpecification('violations_count', '<', 3));
    }
    
    private function contentMeetsStandards(Content $content): AbstractSpecification
    {
        return (new WhereSpecification('word_count', '>=', 100))
            ->and(new WhereSpecification('is_spam', false))
            ->and(new WhereSpecification('profanity_score', '<', 0.1));
    }
    
    private function typeSpecificRules(Content $content): AbstractSpecification
    {
        return match ($content->type) {
            'video' => new VideoContentSpecification(),
            'article' => new ArticleContentSpecification(),
            'tutorial' => new TutorialContentSpecification(),
            default => new AlwaysTrueSpecification()
        };
    }
}

// Use in multiple contexts
class ContentModerationService
{
    public function autoApprove(Content $content): bool
    {
        $publishableSpec = new ContentPublishableSpecification($content->author);
        return $publishableSpec->isSatisfiedBy($content);
    }
}

class ContentController extends Controller
{
    public function publish(Content $content)
    {
        $publishableSpec = new ContentPublishableSpecification(auth()->user());
        
        if (!$publishableSpec->isSatisfiedBy($content)) {
            return response()->json([
                'error' => 'Content does not meet publishing requirements'
            ], 422);
        }
        
        $content->update(['status' => 'published']);
        
        return response()->json(['message' => 'Content published']);
    }
}

🚚 Logistics & Operations

Order Fulfillment Rules

Your Challenge:

"We have complex shipping rules, warehouse allocation logic, and fulfillment criteria scattered across our system."

The Specification Solution:

php
class OrderFulfillmentSpecifications
{
    public static function readyToShip(): AbstractSpecification
    {
        return (new WhereSpecification('payment_status', 'paid'))
            ->and(new WhereSpecification('inventory_allocated', true))
            ->and(new WhereNullSpecification('shipped_at'))
            ->and(new WhereSpecification('fraud_check_passed', true));
    }
    
    public static function expressShippingEligible(): AbstractSpecification
    {
        return (new WhereSpecification('total_amount', '>=', 10000)) // $100+
            ->and(new WhereSpecification('weight', '<=', 5000)) // 5kg max
            ->and(new WhereSpecification('contains_hazardous', false))
            ->and(new WhereHasSpecification('shippingAddress', 
                new WhereInSpecification('country', ['US', 'CA'])
            ));
    }
    
    public static function requiresSpecialHandling(): AbstractSpecification
    {
        return (new WhereSpecification('is_fragile', true))
            ->or(new WhereSpecification('is_perishable', true))
            ->or(new WhereSpecification('value', '>=', 100000)) // $1000+
            ->or(new WhereHasSpecification('items',
                new WhereSpecification('requires_signature', true)
            ));
    }
}

// Use in fulfillment workflows
class FulfillmentService
{
    public function processOrderQueue(): void
    {
        // Process ready-to-ship orders
        Order::whereSpecification(
            OrderFulfillmentSpecifications::readyToShip()
        )->chunk(50, function ($orders) {
            foreach ($orders as $order) {
                $this->allocateInventory($order);
                $this->generateShippingLabel($order);
            }
        });
        
        // Flag special handling orders
        Order::whereSpecification(
            OrderFulfillmentSpecifications::requiresSpecialHandling()
        )->update(['requires_manual_review' => true]);
    }
    
    public function suggestShippingOptions(Order $order): array
    {
        $options = ['standard'];
        
        if (OrderFulfillmentSpecifications::expressShippingEligible()->isSatisfiedBy($order)) {
            $options[] = 'express';
            $options[] = 'overnight';
        }
        
        return $options;
    }
}

💳 Financial & Payments

Risk Assessment & Fraud Detection

Your Challenge:

"Our fraud detection has dozens of rules, risk scoring algorithms, and payment validation logic that's becoming unmaintainable."

The Specification Solution:

php
class PaymentRiskAssessmentSpecification extends AbstractSpecification
{
    public function __construct(private Payment $payment) {}
    
    public function getRiskScore(): float
    {
        $riskFactors = [
            'high_amount' => $this->highAmountRisk(),
            'new_customer' => $this->newCustomerRisk(),
            'suspicious_location' => $this->locationRisk(),
            'velocity_check' => $this->velocityRisk(),
            'payment_method' => $this->paymentMethodRisk()
        ];
        
        return collect($riskFactors)->sum(fn($spec) => 
            $spec->isSatisfiedBy($this->payment) ? $spec->getRiskWeight() : 0
        );
    }
    
    public function requiresManualReview(): bool
    {
        return $this->getRiskScore() >= 0.7;
    }
    
    public function shouldBlock(): bool
    {
        return $this->getRiskScore() >= 0.9;
    }
    
    private function highAmountRisk(): RiskSpecification
    {
        return new RiskSpecification(
            new WhereSpecification('amount', '>=', 100000), // $1000+
            weight: 0.3
        );
    }
    
    private function newCustomerRisk(): RiskSpecification
    {
        return new RiskSpecification(
            new WhereHasSpecification('customer',
                new WhereSpecification('created_at', '>=', now()->subDays(7))
            ),
            weight: 0.4
        );
    }
    
    private function velocityRisk(): RiskSpecification
    {
        $recentPayments = Payment::where('customer_id', $this->payment->customer_id)
            ->where('created_at', '>=', now()->subHour())
            ->count();
            
        return new RiskSpecification(
            $recentPayments > 5 ? new AlwaysTrueSpecification() : new AlwaysFalseSpecification(),
            weight: 0.6
        );
    }
}

// Integrate with payment processing
class PaymentProcessor
{
    public function processPayment(PaymentRequest $request): PaymentResult
    {
        $payment = Payment::create($request->toArray());
        $riskAssessment = new PaymentRiskAssessmentSpecification($payment);
        
        if ($riskAssessment->shouldBlock()) {
            $payment->update(['status' => 'blocked']);
            return PaymentResult::blocked('High risk transaction');
        }
        
        if ($riskAssessment->requiresManualReview()) {
            $payment->update(['status' => 'pending_review']);
            dispatch(new ReviewPaymentJob($payment));
            return PaymentResult::pending('Manual review required');
        }
        
        return $this->authorizePayment($payment);
    }
}

🎓 Use Case Selection Guide

How to Choose Your First Use Case

  1. Start with Pain Points

    • Look for methods with 10+ conditional statements
    • Find logic that's duplicated across controllers
    • Identify complex business rules that are hard to test
  2. Pick High-Impact Areas

    • User authentication and authorization
    • Search and filtering functionality
    • Business rule validation
    • Reporting and analytics queries
  3. Consider Team Benefits

    • Areas where junior developers struggle
    • Code that causes frequent bugs
    • Logic that's hard to explain in code reviews
    • Features that require frequent changes

Implementation Priority

PriorityUse Case TypeWhy Start Here
🔥 HighSearch/FilteringImmediate user impact, clear boundaries
🔥 HighUser PermissionsSecurity critical, well-defined rules
🟡 MediumBusiness ValidationComplex logic, high test value
🟡 MediumReport FilteringQuery optimization benefits
🟢 LowContent ModerationComplex workflows, gradual migration

Ready to Implement?

Found your use case? Ready to see some mind-blowing advanced patterns?


Discover Mind-Blowing Patterns →

Released under the MIT License.