Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Specification Laravel Package

happyr/doctrine-specification

Reusable Doctrine query Specifications for PHP. Replace messy repositories and huge QueryBuilder methods with small, composable, testable spec classes. Reduce duplication, avoid methods with many arguments, and extend queries cleanly as your app grows.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Installation**:
   ```bash
   composer require happyr/doctrine-specification

Ensure your Laravel app uses Doctrine ORM (e.g., via doctrine/orm or a bridge like laravel-doctrine).

  1. First Use Case: Replace a bloated repository method with a reusable Specification class. Example: Filter active users with a Published spec:

    // app/Specifications/User/Published.php
    namespace App\Specifications\User;
    
    use Happyr\DoctrineSpecification\Specification\BaseSpecification;
    use Happyr\DoctrineSpecification\Specification\Spec;
    
    class Published extends BaseSpecification
    {
        public function getSpec()
        {
            return Spec::eq('isPublished', true);
        }
    }
    
  2. Apply in Repository:

    // app/Repositories/UserRepository.php
    use App\Specifications\User\Published;
    use Doctrine\ORM\EntityRepository;
    
    class UserRepository extends EntityRepository
    {
        public function findPublishedUsers()
        {
            return $this->match(new Published());
        }
    }
    
  3. Usage in Controller:

    use App\Repositories\UserRepository;
    
    class UserController extends Controller
    {
        public function index(UserRepository $repository)
        {
            $users = $repository->findPublishedUsers();
            return view('users.index', compact('users'));
        }
    }
    

Implementation Patterns

Core Workflows

  1. Composing Specifications: Combine specs using Spec::andX(), Spec::orX(), or Spec::not() for complex queries.

    $spec = Spec::andX(
        new Published(),
        Spec::gt('createdAt', new \DateTime('-7 days'))
    );
    
  2. Context-Dependent Specs: Pass dynamic data via constructor (e.g., role-based access):

    class HasRole extends BaseSpecification
    {
        private $role;
    
        public function __construct(string $role)
        {
            $this->role = $role;
        }
    
        public function getSpec()
        {
            return Spec::contains('roles', $this->role);
        }
    }
    
  3. Repository Integration: Extend EntityRepository to add match() method (if not auto-injected):

    use Happyr\DoctrineSpecification\Repository\SpecificationRepository;
    
    class UserRepository extends SpecificationRepository
    {
        // Inherits match() method
    }
    
  4. Collection Filtering: Use filterCollection() for in-memory arrays (e.g., API responses):

    $activeUsers = (new Published())->filterCollection($allUsers);
    

Laravel-Specific Tips

  • Service Container Binding: Bind repositories to Laravel’s container for dependency injection:

    $this->app->bind(UserRepository::class, function ($app) {
        return $app['doctrine.orm.entity_manager']
            ->getRepository(User::class);
    });
    
  • Query Scoping: Use specs to scope queries in Eloquent-like services:

    class UserService
    {
        public function getActiveUsers(UserRepository $repo)
        {
            return $repo->match(Spec::andX(
                new Published(),
                new HasRole('admin')
            ));
        }
    }
    
  • API Resource Filtering: Dynamically build specs from request parameters:

    $spec = Spec::andX(
        new Published(),
        ...array_map(fn($field) => Spec::eq($field, $request->$field), ['status', 'region'])
    );
    

Gotchas and Tips

Pitfalls

  1. Context Resolution:

    • Issue: Joins in specs may fail if aliases aren’t resolved correctly.
    • Fix: Use Spec::join() with explicit aliases or extend DQLContextResolver:
      Spec::join('user.roles', 'r', 'WITH', 'r.active = true');
      
  2. Performance:

    • Issue: Overly complex specs (e.g., nested orX with many conditions) can bloat queries.
    • Fix: Profile with Doctrine’s SQL logger or use ->getQuery()->getSQL().
  3. Doctrine Version Mismatch:

    • Issue: v2.x requires Doctrine ORM ≥2.5. Older versions may break.
    • Fix: Pin versions in composer.json or use v1.x.
  4. Caching:

    • Issue: Specs aren’t cached by default; repeated identical queries may hit the DB.
    • Fix: Cache repository results or use QueryBuilder hints:
      $qb->setMaxResults(100)->setCacheable(true);
      

Debugging

  • Query Dumping: Enable Doctrine’s SQL logging in config/doctrine.php:

    'logging' => true,
    'logging_format' => '%%sql%%',
    
  • Spec Validation: Test specs in isolation with isSatisfiedBy():

    $spec = new Published();
    $this->assertTrue($spec->isSatisfiedBy($publishedUser));
    

Extension Points

  1. Custom Operators: Extend Happyr\DoctrineSpecification\Specification\Operands for domain-specific logic:

    class CustomOperands extends Operands
    {
        public static function isActive(): Comparison
        {
            return new Comparison('isActive', '=', true);
        }
    }
    
  2. Repository Decorators: Wrap repositories to add cross-cutting concerns (e.g., logging):

    class LoggingRepositoryDecorator implements SpecificationRepository
    {
        public function match(BaseSpecification $spec)
        {
            \Log::debug('Executing spec: ' . get_class($spec));
            return $this->decorated->match($spec);
        }
    }
    
  3. Context Providers: Inject dynamic context (e.g., tenant ID) via BaseSpecification constructor:

    class TenantScoped extends BaseSpecification
    {
        public function __construct(private string $tenantId)
        {
        }
    
        public function getSpec()
        {
            return Spec::eq('tenantId', $this->tenantId);
        }
    }
    

Laravel-Specific Quirks

  • Eloquent Hybrid: Avoid mixing specs with Eloquent’s where() clauses unless using a bridge like doctrine/orm-elq.
  • Migration Impact: Specs are logic-agnostic; refactor queries before migrating to specs to avoid breaking changes.

Pro Tips

  • Naming Conventions: Prefix specs with the entity name (e.g., UserPublished, PostDraft) for clarity.
  • Immutable Specs: Make specs immutable (no setters) to ensure thread-safe reuse.
  • Testing: Mock specs in unit tests:
    $mockSpec = $this->createMock(BaseSpecification::class);
    $mockSpec->method('getSpec')->willReturn(Spec::eq('active', true));
    $this->assertEquals([$activeUser], $repo->match($mockSpec));
    

```markdown
## Gotchas and Tips (Continued)

### Advanced Patterns
1. **Specification Factories**:
   Centralize spec creation for consistency:
   ```php
   class UserSpecFactory
   {
       public static function published(): Published
       {
           return new Published();
       }

       public static function withRole(string $role): HasRole
       {
           return new HasRole($role);
       }
   }
  1. Composite Specs: Build reusable composite specs (e.g., "Admin or Editor"):

    class IsEditorOrAdmin extends BaseSpecification
    {
        public function getSpec()
        {
            return Spec::orX(
                new HasRole('admin'),
                new HasRole('editor')
            );
        }
    }
    
  2. Spec Caching: Cache compiled specs for performance-critical paths:

    class CachedSpecification
    {
        private static $cache = [];
    
        public static function get(BaseSpecification $spec, string $key)
        {
            return self::$cache[$key] ?? self::$cache[$key] = $spec;
        }
    }
    

Laravel Integration Deep Dive

  • API Resource Filtering: Use specs to filter API responses dynamically:

    class UserResource extends JsonResource
    {
        public function toArray($request)
        {
            $spec = $this->buildSpecFromRequest($request);
            return parent::toArray($request)->filter($spec->filterCollection(...));
        }
    }
    
  • Policy Integration: Replace authorize() logic with specs:

    class PostPolicy
    {
        public function view(User $user, Post $post)
        {
            $spec = Spec::andX(
                new OwnsPost($user),
                new IsPublished()
            );
            return $spec->isSatisfiedBy($post);
        }
    }
    
  • Event Listeners: Trigger specs on entity events (e.g., soft-deletes):

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours