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

Simorgh Laravel Package

aminsamadzadeh/simorgh

Simorgh is a Laravel package that provides a flexible, developer-friendly toolkit for building PHP applications. It offers reusable components and simple configuration to reduce boilerplate, streamline common backend tasks, and help teams ship maintainable Laravel projects faster.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and sanity-check compatibility first. The package targets the Laravel 5/6/7 era (last release Nov 2020), so before running composer require aminsamadzadeh/simorgh, open its composer.json on GitHub and check the illuminate/* and php constraints. On Laravel 9+ you will typically need a fork or a "replace"/platform-config workaround:

"config": { "platform": { "php": "7.4.0" } }

Read the source before the README. With ~11 stars the entire package is a handful of files — realistically a service provider, a config file, and one trait or query-builder class. That trait is the public API: it will tell you the exact query-parameter names it recognizes (commonly filter, sort, include/with, search, per_page, page) and how it parses them. Fifteen minutes there beats an hour of guessing.

First use case: a list endpoint. The idiomatic flow is trait-on-model, then hand the request to it in the controller:

// app/Models/Post.php
use AminSamadzadeh\Simorgh\Traits\Filterable; // verify exact namespace in src/

class Post extends Model
{
    use Filterable;

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

// app/Http/Controllers/Api/PostController.php
public function index(Request $request)
{
    return PostResource::collection(Post::query()->filter($request)->paginate());
}

Start with one model and one endpoint, confirm the SQL it produces, and only then roll it out — this package is a convenience layer over Builder, and convenience layers are exactly where surprising SQL hides.

Implementation Patterns

Allowlist per model, not per controller. The most maintainable pattern is declaring $filterable, $sortable, $searchable, and $includeable (relation names) as model properties. It keeps the contract next to the schema, so a column rename forces you to update the allowlist instead of silently leaking the new column.

Validate the query string like any other input. Put query-param rules in a FormRequest so per_page, sort, and filter keys are constrained before they reach the builder:

class PostIndexRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'filter.status'  => ['nullable', 'in:draft,published'],
            'sort'           => ['nullable', 'string', 'max:64'],
            'per_page'       => ['nullable', 'integer', 'min:1', 'max:100'],
        ];
    }
}

Cap pagination. Always override per_page server-side (min($request->per_page ?? 15, 100)) — an unbounded ?per_page=100000 is a trivial DoS on any list endpoint.

Compose with scopes, don't fight them. Apply authorization and tenancy scopes before handing off to the package:

Post::query()
    ->where('tenant_id', $request->user()->tenant_id)   // non-negotiable, not user-controlled
    ->filter($request)                                   // cosmetic/user-controlled
    ->paginate($perPage);

Anything security-relevant belongs in a scope or policy; anything the client can toggle belongs in the package. Never let a filter param reach a column that determines visibility.

Eager-load deliberately. If the package supports an include/with param, allowlist relations and validate them against a fixed array — arbitrary relation loading is both an N+1 generator and an information-disclosure vector. Pair it with ->withCount() for list badges so you don't lazy-load inside resources.

Wrap it in a reusable base controller or a dedicated QueryFilters object if you find yourself repeating the same three lines across twenty controllers. A small App\Support\IndexQuery that calls the package and applies your pagination default keeps the dependency swappable — which matters a lot for a package that hasn't shipped since 2020.

Test the contract, not the internals. Feature tests hitting /api/posts?filter[status]=draft&sort=-created_at and asserting the JSON shape plus assertDatabaseCount give you a regression net that survives replacing the package entirely.

Gotchas and Tips

Maintenance risk is the headline gotcha. Last release 2020, 11 stars, single maintainer. Treat it as vendored code you own: pin the exact version in composer.json, and if you depend on it in production, budget an afternoon to copy the trait into app/ and drop the dependency. That is often cheaper than fighting constraints on every Laravel upgrade.

Unvalidated params → SQL surface. If the package interpolates column names or sort directions from the request, an empty allowlist (or a forgotten one) can mean ?sort=password or worse. Grep the source for orderBy, whereRaw, DB::raw, and any ->{$column} dynamic call. If you find a raw path with no allowlist check, add one at the call site.

Direction handling. -created_at vs created_at desc vs sort=created_at,direction=desc — conventions differ and the package may support only one. Confirm by reading the parser, then document the convention in your API docs so front-end developers aren't guessing.

Operator support is inconsistent across packages. filter[created_at][gte]=2024-01-01 may or may not map to >=. Test each operator you plan to expose (gte, lte, like, in) with a real request and read the generated SQL — silence here usually means the operator is ignored, not that it's working.

Parameter naming collisions with Laravel. page is consumed by paginate(), per_page is not a framework concept, and array-style params (filter[status]) arrive as nested arrays — make sure your FormRequest rules use dot notation or filter.*.

Debugging recipe. When results look wrong, isolate package vs. your code:

$q = Post::query()->filter($request);
dump($q->toSql(), $q->getBindings());

If toSql() looks right but JSON is wrong, the problem is in your Resource or the pagination call, not the filter. Add a temporary DB::listen(fn ($q) => logger($q->sql)) in a service provider when you need the full request lifecycle.

Don't call get() before the builder finishes. Order matters: scope → package filter → package sort → paginate()/cursorPaginate(). Calling ->get() in the middle silently loses everything applied afterward.

Extension points to check. Whether the trait methods are protected and overridable determines how much you can customize without forking. If they're private or the config isn't publishable, assume zero extension surface and wrap rather than inherit.

Have an exit plan. spatie/laravel-query-builder covers the same ground with active maintenance, documented allowlists, and Laravel 11 support. If you're starting a new project today, evaluate it side-by-side; adopt Simorgh only if you specifically want its smaller footprint or already run an older Laravel version where it's proven.

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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
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