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

Laravel Querybuilder Laravel Package

ghostcompiler/laravel-querybuilder

API-ready Eloquent query builder for Laravel with strict allow-lists for filters, sorts, includes, and sparse fields. Supports nested relation filters/sorting, custom filters, tenant scoping, safe public query interfaces, and pagination helpers for clean API responses.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require ghostcompiler/laravel-querybuilder
    

    Publish the config (optional):

    php artisan vendor:publish --provider="GhostCompiler\QueryBuilder\QueryBuilderServiceProvider"
    
  2. Define a Schema: Create a schema class for your model (e.g., UserSchema):

    use GhostCompiler\QueryBuilder\Contracts\Schema;
    
    class UserSchema implements Schema
    {
        public function getFilters(): array
        {
            return [
                'name' => ['operator' => 'like'],
                'email' => ['operator' => 'like'],
                'active' => ['operator' => 'eq'],
            ];
        }
    
        public function getSorts(): array
        {
            return ['name', 'created_at'];
        }
    
        public function getIncludes(): array
        {
            return ['posts', 'roles'];
        }
    
        public function getFields(): array
        {
            return ['id', 'name', 'email', 'active'];
        }
    }
    
  3. First Use Case: Apply the trait to your model:

    use GhostCompiler\QueryBuilder\Eloquent\QueryBuilder;
    
    class User extends Model
    {
        use QueryBuilder;
    
        protected static function getSchema(): string
        {
            return UserSchema::class;
        }
    }
    

    Now, your API endpoint can handle requests like:

    /users?name=John&sort=-created_at&fields=id,name&include=posts
    

Implementation Patterns

Core Workflows

  1. Schema-Driven API Endpoints:

    public function index(Request $request)
    {
        $query = User::query()->applyQuery($request);
        return $query->paginate($request->input('per_page', 15));
    }
    
    • applyQuery() processes all allowed filters, sorts, includes, and fields from the request.
  2. Nested Relation Filtering: Define nested filters in your schema:

    public function getFilters(): array
    {
        return [
            'posts.title' => ['operator' => 'like'],
            'posts.published' => ['operator' => 'eq'],
        ];
    }
    

    Usage:

    /users?posts.title=Laravel
    
  3. Strict Mode: Enable strict mode in config/query-builder.php to reject unknown parameters:

    'strict' => env('QUERY_BUILDER_STRICT', true),
    

    Or per-query:

    User::query()->applyQuery($request, strict: true);
    
  4. Custom Filters: Register custom filters in your schema:

    public function getCustomFilters(): array
    {
        return [
            'role' => [RoleFilter::class],
        ];
    }
    

    Implement the filter:

    class RoleFilter implements CustomFilter
    {
        public function apply(Builder $query, $value)
        {
            return $query->whereHas('roles', fn($q) => $q->where('name', $value));
        }
    }
    
  5. Pagination Helpers: Use built-in pagination logic:

    $query->paginate($request->input('per_page', 15));
    

    Or disable pagination:

    $query->disablePagination();
    
  6. Sparse Fieldsets: Limit returned fields via fields parameter:

    /users?fields=id,name,email
    
  7. Relation Includes: Include related models:

    /users?include=posts,roles
    

    Or with nested includes:

    /users?include=posts.author
    

Integration Tips

  1. Middleware for Tenant Awareness: Use middleware to scope queries by tenant:

    public function handle(Request $request, Closure $next)
    {
        $request->merge(['tenant_id' => auth()->tenant()->id]);
        return $next($request);
    }
    

    Then define tenant-aware filters in your schema.

  2. Policy-Aware Includes: Restrict includes based on user permissions:

    public function getIncludes(): array
    {
        return [
            'posts' => ['policy' => CanViewPosts::class],
            'roles' => ['policy' => CanViewRoles::class],
        ];
    }
    
  3. Dynamic Schemas: Use closures for dynamic schemas:

    protected static function getSchema(): string|Closure
    {
        return fn () => new DynamicUserSchema(request()->user());
    }
    
  4. JSON:API Compliance: Map query parameters to JSON:API conventions:

    ?filter[name][like]=John&sort=-createdAt&fields=id,name&include=posts
    

    Use the jsonapi config option to enable this.

  5. Caching Queries: Cache compiled query logic:

    $query = User::query()->applyQuery($request)->remember();
    
  6. Testing: Mock schemas and requests:

    $request = new Request(['name' => 'John', 'sort' => '-created_at']);
    $query = User::query()->applyQuery($request);
    $this->assertDatabaseCount('users', 1);
    

Gotchas and Tips

Pitfalls

  1. Strict Mode Overhead:

    • Enabling strict: true rejects unknown parameters before validation, which can lead to unexpected 400 responses. Test thoroughly.
    • Tip: Use strict: false during development and enable it in production.
  2. Nested Relation Performance:

    • Deeply nested includes (e.g., posts.author.profile) can cause N+1 queries. Use with() or loadMissing() manually if needed.
    • Tip: Add loadMissing to your schema:
      public function getIncludes(): array
      {
          return [
              'posts' => ['loadMissing' => true],
          ];
      }
      
  3. Custom Filter Edge Cases:

    • Custom filters bypass default validation. Ensure they handle:
      • Invalid input types (e.g., null or empty strings).
      • SQL injection risks (always use Eloquent methods like whereRaw with bindings).
    • Tip: Add input sanitization in custom filters:
      public function apply(Builder $query, $value)
      {
          if (!is_string($value)) {
              throw new \InvalidArgumentException('Value must be a string.');
          }
          return $query->where(...);
      }
      
  4. Schema Caching:

    • Schemas are cached by class name. Changes to schema methods require cache clearing:
      php artisan config:clear
      
    • Tip: Use php artisan query-builder:clear-schema-cache if provided.
  5. Field Masking Conflicts:

    • Sensitive fields (e.g., password) are masked by default. If you explicitly include them in getFields(), they will still be masked unless configured otherwise.
    • Tip: Exclude sensitive fields from getFields() or override masking:
      public function getFields(): array
      {
          return ['id', 'name', 'email'];
      }
      
      public function getMaskedFields(): array
      {
          return ['password', 'api_token'];
      }
      
  6. Pagination Parameter Conflicts:

    • The page parameter is used for pagination, but some libraries (e.g., Laravel Scout) also use it. Rename it in your schema:
      public function getPaginationParameter(): string
      {
          return 'p';
      }
      
  7. Relation Include Validation:

    • Includes are validated against getIncludes(), but nested includes (e.g., posts.author) must be explicitly defined in the schema.
    • Tip: Use wildcards for nested includes:
      public function getIncludes(): array
      {
          return ['posts.*']; // Allows posts.author, posts.comments, etc.
      }
      
  8. Query Builder Chaining:

    • Avoid chaining applyQuery() multiple times on the same query. It re-applies all filters, sorts, etc.
    • Tip: Apply once and then chain other methods:
      // Bad
      User::query()->applyQuery($request)->applyQuery($request);
      
      // Good
      $query = User::query()->applyQuery($request);
      return $query->where('active', true)->get();
      

Debugging Tips

  1. Log Queries: Enable query logging in config/query-builder.php:

    'debug' => env('QUERY_BUILDER_DEBUG', false),
    

    Or log manually:

    \DB::enableQueryLog();
    User::query()->applyQuery($request)->get();
    \Log::info(\DB::getQueryLog());
    
  2. Validate Schema: Use the validateSchema method to check your schema definition:

    User::validateSchema();
    

    This throws exceptions for invalid configurations.

  3. **Inspect Applied Query

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.
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor