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

Detective Laravel Package

lucasbedout/detective

Detective is a Laravel package for inspecting and diagnosing application behavior, giving developers a clearer view of what happens under the hood so debugging and development investigations are faster and easier.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is small and thinly documented, so treat the source as the spec. First step is to read src/ and the README in the repo before writing any code — method names and config keys are not stable knowledge in your head, and there is no current documentation site.

Install

composer require lucasbedout/detective

Register the provider (pre-Laravel 5.5 auto-discovery)

// config/app.php
'providers' => [
    // ...
    LucasBedout\Detective\DetectiveServiceProvider::class,
],

If the package ships a config file, publish it and read every key before touching it:

php artisan vendor:publish --provider="LucasBedout\Detective\DetectiveServiceProvider"

Wire a model

The core of the package is a trait applied to an Eloquent model plus a declaration of which columns are filterable. Check the trait name in src/ — it is the single most important thing to confirm, because everything downstream depends on it.

use LucasBedout\Detective\...; // confirm the trait namespace

class Post extends Model
{
    use /* the detective trait */;

    protected $filterable = [
        'title',
        'status',
        'created_at',
    ];
}

First use case

An index endpoint that accepts query string filters instead of hand-rolled if ($request->has(...)) chains:

public function index(Request $request)
{
    return Post::filter($request->only(['title', 'status', 'created_at']))
        ->paginate(25);
}

That is the whole value proposition: one scope call replaces a dozen conditional where clauses. Get that working on one resource before adopting it anywhere else.

Implementation Patterns

Whitelist at the model, not the controller. The safest pattern with any request-driven filter is to keep the allowlist in the model's $filterable property and never trust the query string. Pass $request->only([...]) from the controller so unknown keys never reach the scope at all. Two layers of defence is cheap here.

Dedicated filter request class. Rather than validating inline, move the contract into a form request so the filter surface is documented and testable:

class PostFilterRequest extends FormRequest
{
    public function rules()
    {
        return [
            'title'      => 'sometimes|string|max:255',
            'status'     => 'sometimes|in:draft,published',
            'created_at' => 'sometimes|date',
        ];
    }
}

Then Post::filter($request->validated()). This is the pattern I would standardize on across a codebase — it keeps the controller to three lines and makes the available filters discoverable.

Compose with existing scopes, don't replace them. Detective should slot in alongside your domain scopes, not swallow them:

Post::query()
    ->published()
    ->filter($request->validated())
    ->with('author')
    ->latest()
    ->paginate(25);

Centralize the index pattern. If you have ten resources, write one abstract base controller or a small Filterable concern that takes a model class and a request, so filtering behaviour is identical everywhere. Divergent per-controller filtering is how these packages rot.

Sorting and pagination. If the package supports sort parameters, keep sort keys on the same allowlist as filters — an unvalidated orderBy from the query string is an enumeration/DoS vector even when it isn't injectable. Always pair with paginate(), never get(), on user-facing endpoints.

Testing. Because the package is unmaintained, wrap it in feature tests rather than trusting it. One test per filterable column asserting the generated SQL or result set, plus a test asserting a disallowed column is ignored:

public function test_unknown_filter_is_ignored()
{
    $this->get('/posts?password=secret')
        ->assertOk();
    // assert the query did not filter on password
}

Those tests are your upgrade insurance if you ever migrate off the package.

Migration path in the same PR. Since the package is a thin layer, isolate it behind your own scope (scopeSearch() on a base model) so replacing it later with spatie/laravel-query-builder or plain query-builder code is a one-file change rather than a grep across the app.

Gotchas and Tips

It is effectively abandoned. Last release was October 2016, 13 stars, no maintenance. It predates Laravel 5.5 auto-discovery, Arr::only semantics you may rely on, and modern PHP type handling. On Laravel 8+ expect provider registration issues, potential deprecation warnings, and no fixes. Do not add it to a new project — vendor the relevant logic instead, which for a filtering trait is usually under 150 lines.

Verify framework compatibility before installing. Pin the package against your Laravel version in composer.json and run the full test suite immediately after install. An abandoned package resolving against a modern Laravel can silently break other dependencies during composer update.

Request mass-assignment is the classic footgun. If the package's examples show Model::filter($request->all()), do not copy that. $request->all() includes _token, page, and anything else a client sends. Always ->only() or ->validated().

Ambiguous columns on joins. Filtering on created_at when the query joins authors (which also has created_at) produces an ambiguous-column SQL error. Qualify columns in your $filterable list (posts.created_at) if the package passes them through verbatim — check the source for whether it table-qualifies.

LIKE filters and wildcards. String filters typically map to LIKE %value%. That means a user typing % matches everything, and leading wildcards prevent index use on large tables. For high-traffic endpoints, prefer prefix matching or full-text search rather than relying on the package's default.

Operator parsing is the injection surface. If the package supports operators (>, <, !=) embedded in values or keys, confirm they are validated against a fixed list. If not, do not expose operator syntax to untrusted input at all.

Debug with the query log, not guesswork. DB::listen(fn ($q) => logger($q->sql, $q->bindings)) or ->toSql() on the built query tells you exactly what the scope produced. With minimal docs, reading the generated SQL is faster than reading the source.

Extension points. The realistic customisation hooks are: overriding the filter-resolution method in your model's trait usage (insteadof/alias), adding your own scopes before/after the filter call, or subclassing the service provider. Beyond that, you are patching vendor code — which is the signal to fork or replace.

Have an exit plan documented. Note in your repo's README or an ADR that filtering depends on an unmaintained package, what the replacement is, and which tests cover it. That single paragraph saves the next developer a day of archaeology.

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.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk