PHPackages                             ayup-creative/laravel-dsql - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. ayup-creative/laravel-dsql

ActiveLibrary[Database &amp; ORM](/categories/database)

ayup-creative/laravel-dsql
==========================

Advanced DSL search parser for Laravel with virtual columns, operators, relationship traversal and export-ready queries.

v1.0.2(2mo ago)14MITPHPPHP ^8.2CI passing

Since Apr 2Pushed 2mo agoCompare

[ Source](https://github.com/Ayup-Creative/laravel-dsql)[ Packagist](https://packagist.org/packages/ayup-creative/laravel-dsql)[ RSS](/packages/ayup-creative-laravel-dsql/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (3)Dependencies (12)Versions (6)Used By (0)

Laravel Advanced Search DSL
===========================

[](#laravel-advanced-search-dsl)

[![CI/CD](https://github.com/Ayup-Creative/laravel-dsql/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/ayup-creative/laravel-dsql/actions/workflows/ci-cd.yml)[![Latest Version on Packagist](https://camo.githubusercontent.com/599615ad0af1281d12f0a07fb32881c523d34793d9c5c33fc702da15ecc3c3db/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f617975702d63726561746976652f6c61726176656c2d6473716c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ayup-creative/laravel-dsql)[![Total Downloads](https://camo.githubusercontent.com/6bbf13f7af99b80b9279ce8b39c6bf350edc3751ea68fbbc9da20cd4efa2f6d8/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f617975702d63726561746976652f6c61726176656c2d6473716c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ayup-creative/laravel-dsql)[![License](https://camo.githubusercontent.com/a54ef286d1d90c9ff203a02e5d04e0aa2251cac1d3207d0812c37f2ca19ea25b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f617975702d63726561746976652f6c61726176656c2d6473716c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ayup-creative/laravel-dsql)

A production-ready domain-specific language (DSL) for Laravel applications. This package allows you to expose complex, user-defined search expressions while abstracting and protecting your underlying database schema.

Designed for reporting tools, analytics dashboards, and advanced filtering APIs, it converts a safe string-based syntax into composable Eloquent queries.

✨ Features
----------

[](#-features)

- **Standardised DSL Syntax**: Clear and unambiguous syntax for filtering and reporting.
- **Column Selection**: Specify which fields to return with `SELECT [col1], [col2] AS "alias"`.
- **Alias Reuse**: Reuse calculated aliases within the same `SELECT` and `WHERE` clauses.
- **Boolean Logic**: Full support for `AND`, `OR`, and grouped expressions with parentheses.
- **Virtual Columns**: Map safe, user-facing field names to internal database logic using attributes (`#[VirtualColumn]`).
- **Custom Operator Registry**: Easily extend the language with new operators (e.g., `equals`, `in`, `gt`, `between`, `contains`).
- **Column-to-Column Comparisons**: Compare fields safely (e.g., `[processed_at]:gt[created_at]`).
- **Arithmetic Expressions**: Perform calculations directly in the search string (e.g., `[price] / 1.2 :gt 100`).
- **Calculated Virtual Columns**: Define aliases for complex expressions using attributes.
- **Dynamic Values**: Express real-time dates and values like `now()`, `today()`, or `now()->subDays(7)`.
- **Custom Selection Casting**: Apply PHP-side formatting to selected values using `CAST(expr, 'type')`.
- **Default Selections**: Define fallback fields to return when no `SELECT` clause is specified.
- **Column Metadata**: Attach custom metadata (e.g., for UI formatting/casts) to search columns.
- **Searchable Trait**: Simplified model integration with convenient methods for report generation.
- **Relationship Traversal**: Query across Eloquent relationships using dot notation (e.g., `[customer.ref]:equals"ABC123"`).
- **Built-in Sorting &amp; Limits**: Include reporting instructions directly in the query string.
- **Relationship Aggregates**: Perform existence and count checks on relationships (e.g., `EXISTS([orders])`, `COUNT([items]):gt 5`).
- **Security-First**: Uses a lexer and Pratt parser to build a safe abstract syntax tree (AST) before compilation.
- **Autocomplete Ready**: Built-in methods to retrieve searchable columns and blank syntax for UI integration.

🚀 Quick Start
-------------

[](#-quick-start)

### Installation

[](#installation)

```
composer require ayup-creative/laravel-dsql
```

### Basic Usage

[](#basic-usage)

```
use AyupCreative\AdvancedSearch\Facade\AdvancedSearch;
use AyupCreative\AdvancedSearch\Attributes\VirtualColumn;
use AyupCreative\AdvancedSearch\Attributes\DefaultSelections;
use AyupCreative\AdvancedSearch\Concerns\Searchable;
use AyupCreative\AdvancedSearch\Contracts\Queryable;
use App\Models\Order;

// 1. Define virtual columns and report metadata in your model
#[DefaultSelections(['id', 'status', 'total'])]
class Order extends Model implements Queryable
{
    use Searchable;

    #[VirtualColumn('status', metadata: ['label' => 'Order Status'])]
    public static function searchStatus($query, $op, $val) {
        $query->where('status', $op, $val);
    }
}

// 2. Apply search to your query builder using the Facade
// You can pass the Model FQN, a builder instance, or a model instance
AdvancedSearch::apply(Order::class, '[status]:equals"processed"');

// Or using an existing builder
$query = Order::query()->where('active', true);
AdvancedSearch::apply($query, '[status]:equals"processed"');

// 3. Retrieve results and use selection metadata for UI
// All methods support passing a Model FQN, Builder, or Model instance
$selections = AdvancedSearch::getSelections('[status]:equals"processed"', Order::class);
// Or using the trait: $selections = $order->getSelections();
```

🔍 Autocomplete Support
----------------------

[](#-autocomplete-support)

The DSL provides built-in support for generating metadata for your UI via the `AdvancedSearch` facade.

```
use AyupCreative\AdvancedSearch\Facade\AdvancedSearch;

// 1. Get all available columns and relationships for a given model (one-level deep)
$schema = AdvancedSearch::getSchema(Order::class);
/*
Returns: [
    ['name' => 'id', 'type' => 'column'],
    ['name' => 'status', 'type' => 'column'],
    ['name' => 'customer', 'type' => 'relationship', 'model' => 'App\Models\Customer'],
    ...
]
*/

// 2. Get all registered virtual columns for a given model
$columns = AdvancedSearch::getAutocomplete(Order::class);
// Returns: ['status', 'customer.name', ...]

// 3. Get available operators (optionally for a specific column)
$operators = AdvancedSearch::getAvailableOperators('status');
// Returns: ['equals', 'in', ...]

// 3. Get a blank syntax template for a selected column (optionally with an operator)
$syntax = AdvancedSearch::getBlankSyntax('status'); // '[status]:equals""'
$syntaxIn = AdvancedSearch::getBlankSyntax('status', 'in'); // '[status]:in()'

// 4. Get smart autocomplete suggestions based on partial input
$suggestions = AdvancedSearch::suggest('[stat', Order::class); // ['status']
$suggestions = AdvancedSearch::suggest('[status]:', Order::class); // ['equals', 'in', ...]
$suggestions = AdvancedSearch::suggest('[status]:equals"', Order::class); // [] (inside quotes)

// 5. Get selected columns/expressions from a query string
$selections = AdvancedSearch::getSelections('SELECT [id], [total] * 1.2 AS "vat_total" WHERE [status]:equals"processed"', Order::class);
/*
Returns: [
    ['name' => 'id', 'label' => 'id', 'is_alias' => false, 'expression' => '[id]'],
    ['name' => 'vat_total', 'label' => 'vat_total', 'is_alias' => true, 'expression' => '([total] * 1.2)']
]
*/

// 6. Get default selections when no SELECT is passed
$defaults = AdvancedSearch::getSelections('[status]:equals"processed"', Order::class);
// Returns default fields defined in Order model (or getFillable() fallback)

// 7. Validate a query string against a model
try {
    AdvancedSearch::validate('SELECT [invalid] WHERE [status]:wrong_op"val"', Order::class);
} catch (\AyupCreative\AdvancedSearch\Exceptions\AdvancedSearchException $e) {
    // Handle validation error (unknown operator, etc.)
}
```

📖 Syntax Guide
--------------

[](#-syntax-guide)

### Column Selection

[](#column-selection)

Specify which fields to return with `SELECT`:

- `SELECT [name], [price] WHERE [status]:equals"active"`
- `SELECT [name] AS "product_name", [price] * 1.2 AS "vat_price"`
- `SELECT CAST([price], "money") AS "formatted_price" WHERE [status]:equals"active"`
- `SELECT CAST([price], "money") WHERE [status]:equals"active"` (Alias defaults to `price`)

### Alias Reuse

[](#alias-reuse)

Aliases defined in the `SELECT` clause can be reused in subsequent `SELECT` expressions and in the `WHERE` clause:

- `SELECT [price] * 0.3 AS "a", [a] * 2 AS "b" WHERE [a]:gt 10`
- `SELECT CAST([price] * 0.3, "money") AS "a", [a] AS "a_copy"`

If no `SELECT` is specified, the system defaults to selecting all columns (`*`) in the database query.

### Default Selections for UI/Reports

[](#default-selections-for-uireports)

You can define a default set of fields to be returned by `getSelections()` when no `SELECT` clause is provided. This is useful for building dynamic report tables.

```
use AyupCreative\AdvancedSearch\Attributes\DefaultSelections;

#[DefaultSelections(['id', 'sku', 'price'])]
class Product extends Model { ... }
```

Or by defining a static method:

```
public static function getAdvancedSearchDefaultSelections(): array
{
    return ['id', 'sku', 'price'];
}
```

If neither is defined, it fallbacks to the model's `getFillable()` list.

### Virtual Columns

[](#virtual-columns)

Virtual columns allow you to expose internal database logic or complex calculations as safe, user-friendly field names. You can define them using the `#[VirtualColumn]` attribute on your model classes or static methods.

A virtual column can specify:

- How it's handled for **filtering** (`WHERE` clause) via a custom resolver method.
- How it's handled for **selection** (`SELECT` clause) via a SQL `expression`.

```
use AyupCreative\AdvancedSearch\Attributes\VirtualColumn;

class Receipt extends Model implements Queryable
{
    use Searchable;

    #[VirtualColumn(
        name: 'purchase_price',
        expression: 'CAST(amount / 100 AS DECIMAL(10,2))'
    )]
    public static function searchPurchasePrice($query, $op, $value)
    {
        // Custom resolver for filtering (WHERE)
        $query->whereRaw("CAST(amount / 100 AS DECIMAL(10,2)) {$op} ?", [$value]);
    }
}
```

If only an `expression` is provided (on the class or a method), it will be used for both selection and filtering. If a method is decorated, it acts as the primary resolver for filtering.

### Fields (Columns)

[](#fields-columns)

Fields are always wrapped in square brackets: `[status]`, `[customer.name]`.

### Literals (Values)

[](#literals-values)

- **Strings**: Quoted or unquoted if they don't contain special characters: `"active"`, `'pending'`, `ABC123`.
- **Numbers**: Integers or decimals: `100`, `99.99`.
- **Lists**: Used for `in` or `between` operators: `(active, pending)`, `(10, 20)`.
- **Dynamic Functions**: Express real-time values: `now()`, `today()`, `yesterday()`, `tomorrow()`.
- **Method Chaining**: Call methods on dynamic values: `now()->subDays(7)`, `now()->startOfMonth()`.

### Arithmetic

[](#arithmetic)

Full support for `+`, `-`, `*`, `/` on the left-hand side of conditions:

- `[price] / 1.2 :gt 100`
- `([amount] + [shipping]) * 1.1 :lt 500`

### Relationship Aggregates

[](#relationship-aggregates)

- **Existence**: `EXISTS([relationship])` (returns boolean)
- **Count**: `COUNT([relationship])` (returns integer)
- **Example**: `SELECT [name], COUNT([orders]) AS "order_count" WHERE EXISTS([orders])`

### Operators

[](#operators)

- `equals`: `[status]:equals"active"`
- `in`: `[status]:in(processed, pending)`
- `gt` / `lt`: `[price]:gt 100`, `[price]:lt[discount_price]`
- `between`: `[created_at]:between("2024-01-01", "2024-12-31")`
- `contains`: `[name]:contains"John"`

### Relationship Traversal

[](#relationship-traversal)

The DSL supports querying across Eloquent relationships using dot notation:

- `[customer.name]:equals"John"` (Automatically uses `whereHas('customer', ...)`)
- `[registration.model.standard_warranty_years]:lt 10`

For selection, dotted paths are supported directly in the `SELECT` clause:

- `SELECT [customer.name], [customer.address.city]`

The system automatically detects relationships in the `SELECT` clause, eager-loads them using `$query->with()`, and ensures that necessary foreign keys (for `BelongsTo`) or primary keys (for `HasOne`/`HasMany`) are included in the SQL selection. Plural relationships (e.g., `HasMany`) are automatically resolved into arrays when accessed via `getSelectionValue()`.

### Boolean Logic

[](#boolean-logic)

`([status]:equals"active" OR [priority]:gt 5) AND [category]:in(books, electronics)`

### Sorting &amp; Limits

[](#sorting--limits)

- `sort(column, direction)`: `sort(created_at, desc)`
- `limit(number)`: `limit(10)`

Developer Guide
---------------

[](#developer-guide)

For detailed information on how to extend the system with custom operators or advanced resolvers, please see the [DEVELOPER\_GUIDE.md](./DEVELOPER_GUIDE.md).

🛠️ Development
--------------

[](#️-development)

This project uses [pre-commit](https://pre-commit.com/) to maintain code quality and ensure consistent commit messages.

### Pre-commit Hooks

[](#pre-commit-hooks)

We use `pre-commit` to automatically run:

- **Pint**: Ensures PHP code style follows Laravel standards.
- **PHPUnit**: Runs the test suite to prevent regressions.
- **Conventional Commits**: Validates commit messages.

To set up `pre-commit` locally:

1. [Install pre-commit](https://pre-commit.com/#install) on your machine.
2. Run `pre-commit install` in the project root to set up the git hook scripts.
3. Run `pre-commit install --hook-type commit-msg` to enable commit message linting.

### Conventional Commits

[](#conventional-commits)

We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for all commit messages. This allows us to automate our release process and generate consistent changelogs.

Common types:

- `feat`: A new feature
- `fix`: A bug fix
- `docs`: Documentation only changes
- `style`: Changes that do not affect the meaning of the code (white-space, formatting, etc)
- `refactor`: A code change that neither fixes a bug nor adds a feature
- `perf`: A code change that improves performance
- `test`: Adding missing tests or correcting existing tests
- `chore`: Changes to the build process or auxiliary tools and libraries

### CI/CD &amp; Automated Releases

[](#cicd--automated-releases)

Our GitHub Actions workflow handles the following sequentially:

1. **PHPUnit tests**: Runs tests across multiple PHP versions (8.2, 8.3, 8.4, 8.5).
2. **Commit/PR lint**: Validates that pull request titles follow conventional commit standards.
3. **Release Please**: Automatically creates GitHub releases, tags, and updates the changelog when a PR is merged into the `main` branch.

Testing
-------

[](#testing)

Run the test suite with PHPUnit:

```
vendor/bin/phpunit
```

You can also run tests using Composer:

```
composer test
```

For coverage reports (requires Xdebug):

```
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text
```

Pint is also available for code formatting. Run `pint` to fix any issues.

```
composer pint
```

License
-------

[](#license)

MIT License.

###  Health Score

39

—

LowBetter than 85% of packages

Maintenance84

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 75% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~4 days

Total

3

Last Release

83d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/494de8188ad97403b72e40e385235a74ef6331aa2710b3727193f646189af9fc?d=identicon)[mykemeynell](/maintainers/mykemeynell)

---

Top Contributors

[![mykemeynell](https://avatars.githubusercontent.com/u/1590190?v=4)](https://github.com/mykemeynell "mykemeynell (3 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

dsqleloquenteloquent-ormeloquent-searchlaravellaravel-packagequerydsl

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ayup-creative-laravel-dsql/health.svg)

```
[![Health](https://phpackages.com/badges/ayup-creative-laravel-dsql/health.svg)](https://phpackages.com/packages/ayup-creative-laravel-dsql)
```

###  Alternatives

[spatie/laravel-medialibrary

Associate files with Eloquent models

6.1k43.2M624](/packages/spatie-laravel-medialibrary)[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k8.4M94](/packages/mongodb-laravel-mongodb)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8793.2M25](/packages/yajra-laravel-oci8)[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k32.6M45](/packages/kirschbaum-development-eloquent-power-joins)[glushkovds/phpclickhouse-laravel

Adapter of the most popular library https://github.com/smi2/phpClickHouse to Laravel

2051.5M2](/packages/glushkovds-phpclickhouse-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
