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

Filter Laravel Package

blessing/filter

blessing/filter is a Laravel package for organizing and applying query filters in a clean, reusable way. Define filters once, compose them with Eloquent queries, and keep controllers and models lean. Ideal for APIs and admin lists that need flexible, maintainable search. Simple to extend.

View on GitHub
Deep Wiki
Context7

Getting Started

blessing/filter is a small, dependency-light PHP library that gives you a WordPress-style filter/hook registry so a host application can expose extension points and plugins can attach to them without touching core code. It has effectively zero public adoption (0 stars, 0 dependents), so treat the repository itself as the documentation.

Install and pin

composer require blessing/filter

Because the package is unmaintained-by-popularity, commit your composer.lock and pin a caret version only after reading src/. There are no downstream dependents to inherit compatibility guarantees from.

Where to look first, in order

  1. composer.json — check the required PHP version and whether it pulls in illuminate/support (it may be Laravel-flavored) or is framework-agnostic.
  2. src/ — usually one or two classes. Read every public method; the entire API surface is probably under 10 methods (add, apply, remove, has, and maybe a priority argument).
  3. tests/ — if present, this is your real documentation. The test cases show the exact argument order and return-value contract.
  4. README.md — often stale or empty for packages like this; trust the source over the README.

First use case: expose an extension point

The canonical flow is declare a hook name in core, let plugins register callbacks, apply the filter at the point of use:

use Blessing\Filter;

// 1. Core declares and later applies the filter
$title = $filter->apply('page.title', $title, [$page]);

// 2. A plugin registers against it
$filter->add('page.title', function ($title, $page) {
    return $title . ' — ' . $page->owner;
});

Get this round-trip working (register → apply → see the transformed value) before building any plugin-loading machinery on top of it.

Implementation Patterns

Namespace your hook names aggressively. Since the registry is a flat string map, collisions between plugins are the most common real-world failure. Use dot-separated, package-prefixed names: acme.shop.cart.total, not cart_total. Establish the convention in core before the first plugin ships — retrofitting is painful.

Always return the value from a filter callback. A filter is a transformation pipeline, not a notification. A callback that forgets return will inject null into the chain and silently break downstream logic. This is the single biggest source of "why is my value empty" bugs.

Use priority arguments for ordering, but don't over-rely on them. Most filter implementations accept an integer priority (lower runs first, WordPress-style) with registration order as a tiebreaker. Reserve distinct priority bands per concern (e.g. 10 = defaults, 20 = user config, 30 = overrides) rather than hand-tuning integers per plugin.

Wrap the registry in your own service. Do not scatter $filter->apply('...') strings across the codebase. Create a thin domain layer so hook names are discoverable and tests can mock one object:

final class Hooks
{
    public function __construct(private Filter $filter) {}

    public function pageTitle(string $title, Page $page): string
    {
        return $this->filter->apply('cms.page.title', $title, [$page]);
    }
}

This also gives you one place to add logging, timing, or a "strict mode" that throws on unknown hook names.

Load plugins through a bootstrap contract. Give plugins a single entry point (register(Filter $filter): void) and require them to register everything there. Loading order then becomes the only variable, and you can snapshot registered hooks per plugin for diagnostics.

Pass a context array as the last argument, not extra positional parameters. A [$model, $request] context array lets you add data later without breaking existing callbacks — the same reason Laravel's event payloads are associative.

Testing pattern: in tests, register stubs against the same hook names core uses, assert the transformed output, and call the removal method in tearDown() so state does not leak between tests. If the package uses a static/global registry (common in WordPress-likes), you must reset it — otherwise test order becomes load-bearing.

Laravel integration: bind the registry as a singleton in a service provider, register core filters during boot(), then load plugins after. The package almost certainly has no auto-discovery, so this wiring is yours to write.

Gotchas and Tips

  • Filters vs. events. This package filters values; if you need fire-and-forget side effects (send mail, write a log) you can abuse a filter with a dummy payload, but don't. Keep a separate listener mechanism or you will end up with callbacks returning meaningless values and confusing chain semantics.
  • Missing return = null chain break. Mentioned twice because it is the #1 bug. Consider a debug mode that wraps callbacks and throws if a non-void hook returns null.
  • Removal requires the same callable identity. remove() with a closure literal rarely works unless the implementation compares by reference. Store the closure in a property ($this->handler = fn(...)) so you can pass the identical instance back for removal, and always remove in tearDown/unregister.
  • No type safety. The registry stores callable loosely. A plugin returning an int where core expects string will not be caught until it reaches the view layer. Validate the return value at the apply() call site in core.
  • Object payloads are shared by reference. If a callback mutates an object passed in the context array, later callbacks and the caller see the mutation. Document whether your hooks are "pure transform" or "may mutate"; mixed expectations cause heisenbugs.
  • Recursion. A filter that calls apply() on its own hook name will loop until the stack blows. Guard with a re-entrancy flag in your wrapper if plugins can plausibly do this.
  • Priority collisions produce unstable order. If the implementation sorts with an unstable sort (e.g. usort with priority only), equal priorities may reorder between PHP versions. Add an incrementing sequence number as a secondary sort key if you need deterministic output.
  • Configuration quirks: check whether the class is namespaced under Blessing\Filter and whether the package ships a facade/service provider. With zero dependents and a low opportunity score, do not assume Laravel auto-discovery — verify extra.laravel in composer.json.
  • Debugging: build a small diagnostic command early that prints every registered hook name, its callbacks, and priorities. Without it, tracing "which plugin changed this value" is guesswork across an entire plugin folder.
  • Extension points to design for: hook name lists (so plugins can enumerate available hooks), a "did anything register?" check before expensive work, and per-plugin hook introspection for an admin panel. These cost little now and are hard to add later.
  • Pinning strategy: given 0 stars and 0 dependents, vendor the source or fork it internally if the plugin system becomes load-bearing. The bus factor is effectively one, and no upstream community will catch a breaking change.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin