Weave 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

Lara Eye Laravel Package

railken/lara-eye

railken/lara-eye is a lightweight Laravel package that provides a simple, configurable way to add focused application functionality and manage data within existing PHP projects. It integrates cleanly with Laravel, is easy to install, and suits developers looking for a small, practical package rather...

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and check what it registers before writing any filter code:

composer require railken/lara-eye

Laravel's package discovery should pick up the service provider automatically; if not, add it to config/app.php. Check whether there is a publishable config:

php artisan vendor:publish --provider="Railken\LaraEye\ServiceProvider"

Where to look first, in this order:

  1. The README examples — they are the de-facto contract for this package.
  2. vendor/railken/lara-eye/src/ — find the facade/entry class and the parser or grammar class. The package is small (39 stars), so the source is the documentation. Read the tests in vendor/railken/lara-eye/tests if they ship — they are usually the most accurate usage reference.
  3. Any config file listing allowed operators, columns, or delimiters.

First use case — a filterable index endpoint. The package's reason for existing is turning a user-supplied structured filter into a Query\Builder without hand-writing a hundred if ($request->has('name')) blocks. The typical first win:

// Illustrative — confirm the actual entry point in the README
$query = Product::query();

$filtered = Eye::make($request->input('filter'))
    ->apply($query);

return $filtered->paginate(25);

Start with a single model, two or three columns, and one operator (=). Get that round-tripping through an HTTP test before you touch relations or nested boolean logic.

Implementation Patterns

Treat the filter string as untrusted input, always. The pattern that scales is: parse → validate against a whitelist → apply.

$allowed = [
    'name'       => ['=', 'like'],
    'price'      => ['=', '>', '<', '>=', '<='],
    'created_at' => ['=', '>', '<'],
];

$query = Eye::make($request->input('filter'))
    ->allowColumns(array_keys($allowed))
    ->apply(Product::query());

If the package does not expose a whitelist hook directly, do it one layer up: reject any filter referencing unknown columns before handing it to the builder, and return a 422.

Wrap filters in a dedicated request/query object. Don't scatter filter parsing through controllers. A single ProductFilter class that takes the request, validates, and returns a builder keeps the DSL out of your HTTP layer and makes it testable:

final class ProductFilter
{
    public function __construct(private Request $request) {}

    public function apply(Builder $query): Builder
    {
        if (! $this->request->filled('filter')) {
            return $query;
        }

        return Eye::make($this->request->input('filter'))->apply($query);
    }
}

Compose with sorting and pagination, not instead of them. lara-eye filters; it does not sort. Keep ?sort=-created_at&per_page=50 in your own code and pass only the filter payload to the package. Mixing the two responsibilities is the fastest way to end up with an unmaintainable endpoint.

Reuse the same filter contract across endpoints. Once the DSL is stable, expose it on /api/products, /api/exports/products, and any admin table. Because the filter is data (not code), you can store saved filters in a saved_filters table:

Schema::create('saved_filters', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id');
    $table->string('resource');   // 'products'
    $table->json('filter');       // the DSL payload
    $table->timestamps();
});

That is where a package like this pays for itself — saved/reusable filter definitions are painful with hand-rolled if chains.

Relation filters need whereHas, not a bare column. If the package supports dotted columns (customer.country), verify it generates a subquery. If it doesn't, map relation filters yourself before delegating scalar filters to the package — a bare customer.country = 'IT' will throw an SQL error or silently match nothing.

Test the DSL, not just the endpoint. Table-driven tests are the cheapest safety net:

it('filters by price range', function () {
    Product::factory()->create(['price' => 10]);
    Product::factory()->create(['price' => 100]);

    $response = $this->getJson('/api/products?filter='.urlencode("price >= 50"));

    $response->assertJsonCount(1, 'data');
});

Include at least one test asserting that an unknown column or operator returns 422 rather than 500 or, worse, succeeding.

Gotchas and Tips

The SQL injection surface is the whole package. A structured query language that emits SQL is only as safe as its column whitelist. Never pass a raw $request->input('filter') into the builder on a model whose column names are user-influenced (multi-tenant schemas, EAV tables). Whitelist columns and operators, and be equally strict about relation names.

Pin the version. 39 stars means a small user base and a correspondingly thin buffer against regressions. Use "railken/lara-eye": "^1.0" (adjust to the actual tag) and treat every composer update as something to re-test against your filter fixtures. Check whether the maintainer has been active recently before betting a large surface on it.

Grammar strictness will bite you first. A hand-typed filter from a front-end date picker (created_at >= 2024-01-01) may parse differently than a quoted string. Decide early whether your API accepts the DSL as a JSON object or a string, and normalize on the client. Document the exact accepted syntax in your OpenAPI spec — your consumers will get it wrong otherwise, and the parser's error messages are often terse.

Debug by dumping the SQL, not the filter. When a filter returns the wrong rows:

$query = Eye::make($filter)->apply(Product::query());
dump($query->toSql(), $query->getBindings());

If the parsed structure is what surprises you, log the intermediate representation (if the package exposes one) before it hits the builder. Comparing "what the parser understood" against "what the SQL says" localizes the bug in one step.

Watch for N+1s and deep nesting. Relation filters via whereHas generate correlated subqueries; chained deeply they can scan badly. Check EXPLAIN on your largest table with a realistic 3–4 condition filter. Also confirm there's a nesting depth limit — user input like ((((a or b) or c) or d)) repeated 200 times can blow the parser stack and return a 500.

Empty and null handling is a classic trap. field = '' and field is null are different filter intents and a surprising number of DSLs conflate them, or treat field = "" as "no filter, ignore." Test explicitly: filter by empty string, filter by null, and filter by "field is not set." Each should behave distinctly.

Extension points to look for when the built-in operators aren't enough:

  • A custom operator/function registry (e.g. between, in, full-text search).
  • A column resolver hook for aliasing (q → name for a search box).
  • A post-application callback so you can add ->with() eager loads after filtering without losing them.

If none exist, wrap the package rather than forking it — a thin FilterService that pre-processes the input and post-processes the builder keeps you on the upstream release line.

Failing closed is the right default. If parsing throws, return 422 with a generic message. Do not fall back to an unfiltered query — a malformed filter silently returning all rows is how customers end up downloading another tenant's data from an export endpoint.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core