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

Meliorstan Laravel Package

orrison/meliorstan

Meliorstan is a Laravel-focused PHPStan companion that simplifies static analysis setup with sensible presets and helpful rules, so teams can catch bugs earlier, enforce coding consistency, and keep applications safer with less configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Meliorstan is a convention-over-configuration package for Laravel route naming: instead of chaining ->name('users.show') onto every route, it derives the route name from the controller and method it points at, so [UserController::class, 'show'] becomes users.show automatically.

Minimal setup:

composer require orrison/meliorstan
php artisan vendor:publish --tag="meliorstan-config"   # if a config is shipped

The service provider is auto-discovered. The first thing to read is the published config file — it is the contract for how names are generated (controller suffix stripping, separator character, namespace-to-prefix mapping). Second, run php artisan route:list and look at the Name column: that is the ground truth for what strings you can pass to route() and redirect()->route().

Your first use case should be a single, boring CRUD controller:

Route::get('/users/{user}', [UserController::class, 'show']);
// route('users.show', $user) now resolves without an explicit ->name()

Start in one controller, verify the generated name with route:list, then wire the Blade route() calls. Do not migrate the whole app on day one — the value is in the naming contract matching your existing conventions, and that is worth validating on a small surface first.

Implementation Patterns

Migrate to convention lazily, controller by controller. Explicit ->name() calls should win over inferred ones. That gives you a safe incremental path: leave existing named routes alone, drop the ->name() only where route:list already shows the string you want. Nothing breaks, and the diff stays reviewable.

Treat the controller namespace as the route prefix. The main design decision is whether nested namespaces map to dot-separated prefixes (App\Http\Controllers\Admin\UserController@index → admin.users.index). If your app already uses namespace-based grouping for an admin area, align the config to that and you get free prefixing for every new controller in the directory.

Pair it with Route::resource/Route::apiResource carefully. Resource routes are already named by Laravel. Either exclude them from inference or accept that resource naming and Meliorstan naming may disagree on pluralisation (user.index vs users.index). Pick one source of truth per controller and stick to it.

Use it as a guardrail in tests. Because names are inferred, a rename or refactor can silently change a route name. Add a small test that hits the routes you care about via route() — it will fail loudly if a controller is moved or renamed, which is exactly the signal you want.

it('keeps public route names stable', function () {
    expect(route('users.show', 1))->toContain('/users/1');
});

Watch ordering with URL defaults and signed URLs. If you use URL::defaults() or signed route generation, the names must already exist at boot. Anything that generates URLs inside a service provider's boot() is risky — defer to a middleware or a deferred provider.

Pair with route:list --json in CI. A snapshot of inferred names is cheap and catches accidental renames across a large refactor far earlier than a 404 in staging.

Gotchas and Tips

Name collisions are the failure mode. Two controllers with the same basename in different namespaces (Admin\UserController, Api\UserController) will fight over the same inferred prefix unless the config maps namespaces to distinct prefixes. Check route:list for duplicate names after adding any controller — Laravel will not always complain at boot, but route() will resolve to whichever registered last.

Closures and invokable controllers are edge cases. A closure route has no controller to infer from, so it stays unnamed. Invokable controllers (__invoke) need the package to special-case them — verify whether you get users.invoke or users.__invoke and whether that matches your expectations; if not, name those explicitly.

Route caching is fine, but only if inference is deterministic. Names must be derivable purely from the route definition at boot, with no runtime state. If you ever see names differ between php artisan serve and a cached production build, that is the cause.

Laravel's route:list is your debugger. When a name doesn't resolve, php artisan route:list --name=users tells you instantly whether the name was inferred at all, or inferred differently (user.show, users.show, app.users.show). Almost every "route not defined" issue with this package is a naming-convention mismatch, not a bug.

Vendor and package routes. Third-party packages register routes from their own namespaces. If inference applies globally you can end up with surprising names on package routes; scope inference to your app's controllers rather than enabling it for everything.

Explicit ->name() is your escape hatch and your migration tool. Keep using it for anything unusual — legacy URLs, pluralisation you disagree with, redirects from old marketing routes. There is no shame in it; the package should be reducing boilerplate, not enforcing purity.

Small package, small blast radius. At ~20 stars this is a single-maintainer package. Pin the version in composer.json, read the provider source (it is short), and be prepared to fork if the inference rules don't match your conventions. Avoid depending on undocumented behaviour — anything not covered by the config file is fair game to change between releases.

Document the convention in your repo. A one-paragraph note in the README or an ADR ("route names are inferred from controllers; explicit names only for legacy") is what stops a new team member from re-adding ->name() everywhere and quietly creating two naming systems.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment