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

Single Action Resource Controllers For Laravel Laravel Package

clean-bandits/single-action-resource-controllers-for-laravel

Organize Laravel resourceful routes into single-action (invokable) controllers. This package gives each resource action—index, show, store, update, destroy—its own focused controller class, keeping your app clean, explicit, and easy to maintain.

View on GitHub
Deep Wiki
Context7

Getting Started

The package’s premise is simple: instead of one fat PostController with seven resource methods, you get one invokable controller per action (IndexPostController, StorePostController, ShowPostController, etc.), plus a route helper to wire them up with resource-style URLs and names.

Minimal steps:

  1. Install and let Laravel auto-discover the provider:
    composer require clean-bandits/single-action-resource-controllers-for-laravel
    php artisan vendor:publish --tag=single-action-resource-controllers   # if config/stubs are offered
    
  2. Read the README’s two key sections first: the artisan generator signature and the route registration macro. Those are the entire API surface you’ll use daily. Confirm the exact command name (e.g. make:resource-controller vs make:single-action-resource) before scripting it into your workflow.
  3. Skim the service provider in vendor/clean-bandits/.../src/ to see what is registered — route macros, stub paths, and any base controller/trait.
  4. First use case: scaffold one resource end-to-end.
    php artisan make:single-action-resource Post --model=Post
    
    You should end up with seven invokable classes and a registration snippet.
  5. Register routes in routes/web.php (or api.php) using the package macro, then verify:
    php artisan route:list --name=posts
    
    You want the same seven URIs and route names you’d get from Route::resource('posts', PostController::class) — that’s the compatibility contract.

Start with a read-only resource (index + show) before converting a write-heavy one. It lets you validate routing, model binding, and view responses without touching forms or validation.

Implementation Patterns

One class, one verb, one job. Each generated controller should contain only the logic for its action. Anything shared (query scoping, response formatting) belongs in a base controller, a trait, or a dedicated action/service class. A typical thin controller:

final class StorePostController extends Controller
{
    public function __invoke(StorePostRequest $request, CreatePost $createPost): RedirectResponse
    {
        $post = $createPost->handle($request->validated());

        return redirect()->route('posts.show', $post);
    }
}

Pair each controller with its own FormRequest. This is where the pattern pays off: StorePostRequest and UpdatePostRequest are already separate, so per-action validation rules stop leaking into each other. Same for policies — authorize() calls live in exactly one place per action.

Keep the route contract identical. The migration path from a classic resource controller should be invisible to clients. The package’s route helper should preserve URIs, verbs, and names. If it doesn’t, register the routes manually to guarantee parity:

Route::get('posts', IndexPostController::class)->name('posts.index');
Route::post('posts', StorePostController::class)->name('posts.store');
Route::get('posts/{post}', ShowPostController::class)->name('posts.show');
// ...

Declare route model binding per controller. Unlike a resource controller, there is no shared show(Post $post) signature handed to you — implicit binding comes from the route definition. If the macro accepts a model parameter, use it; otherwise type-hint consistently and let Laravel resolve {post}. Do this the same way across all seven classes or you’ll get inconsistent 404s.

Middleware per action. This is a genuine advantage: apply auth only to Store*/Update*/Destroy*, or throttle Index* differently from Show*, without only()/except() arrays drifting out of sync.

Testing. Each controller is independently instantiable, so feature tests map one-to-one to classes. Build a small helper that boots a controller with a fake request, or just hammer the routes — either way, coverage gaps are obvious because every action is a named file.

Beneath the controllers, use invokable actions or services. The controller layer becomes a transport adapter; business logic lives in classes like CreatePost, ListPosts, DeletePost. This makes the single-action controllers trivially replaceable later.

Gotchas and Tips

  • File count explodes. Seven classes per resource instead of one is the deal. Lean hard on the generator and, if the package supports publishable stubs, customize them to inject your base controller, declare(strict_types=1), and namespace conventions once.
  • Route names are the migration risk. If the package’s macro produces different names (posts.index vs post.index), every route() call and redirect silently breaks. Run route:list before and after on one resource and diff.
  • No shared constructor. Middleware or shared setup defined in a resource controller’s __construct won’t exist unless all seven extend a common base. Create an abstract ResourceController and have the stubs extend it.
  • Generator assumptions. Confirm the generator’s model resolution — it may not infer the model from the name, and it may overwrite or skip existing files. Run it in a clean branch first.
  • Cache after generating. New controller classes are invisible until php artisan route:clear / route:cache (and config:clear if you published config) are run in cached environments.
  • Debugging order. 404 on a valid URL usually means binding/route parameter mismatch; 419 means the CSRF middleware isn’t reaching the Store/Update controller; 405 means the macro assigned the wrong verb. Check route:list -v first.
  • Package maturity is the real caveat. Three stars, zero dependents, and a very recent release date mean you are an early adopter. Pin the version in composer.json, and treat the value as the convention, not the code — if the macro disappears, you can replace it with ten lines of Route::get/Route::post and keep the controllers. The controllers themselves are plain invokable classes, so there’s essentially no lock-in.
  • Extension points to look for: publishable stubs, a configurable base controller/namespace, and whether the route macro accepts middleware/name-prefix arguments. If those exist, standardize them in a project-specific wrapper (e.g. your own Route::resourceActions() helper) so the rest of the team never touches the raw package API.
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.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log