# Actions as controllers

# How are attributes filled?

By default, all input data from the request and the route parameters will be used to fill the action's attributes.

You can change this behaviour by overriding the getAttributesFromRequest method. This is its default implementation:

public function getAttributesFromRequest(Request $request)
{
    return array_merge(
        $this->getAttributesFromRoute($request),
        $request->all()
    );
}

Note that, since we're merging two sets of data together, a conflict is possible when a variable is defined on both sets. As you can see, by default, the request's data takes priority over the route's parameters. However, when resolving dependencies for the handle method's argument, the route parameters will take priority over the request's data.

That means in case of conflict, you can access the route parameter as a method argument and the request's data as an attribute. For example:

// Route endpoint: PATCH /comments/{comment}
// Request input: ['comment' => 'My updated comment']
public function handle(Comment $comment)
{
    $comment;        // <- Comment instance matching the given id.
    $this->comment;  // <- 'My updated comment'
}

# Registering actions in routes files

Because your actions are located by default in the \App\Action namespace and not the \App\Http\Controller namespace, you have to provide the full qualified name of the action if you want to define them in your routes/web.php or routes/api.php files.

// routes/web.php
Route::post('articles', '\App\Actions\PublishANewArticle');

Note that the initial \ here is important to ensure the namespace does not become \App\Http\Controller\App\Actions\PublishANewArticle.

Alternatively you can place them in a group that re-defines the namespace.

// routes/web.php
Route::namespace('\App\Actions')->group(function () {
    Route::post('articles', 'PublishANewArticle');
});

Laravel Actions provides a Route macro that does exactly this:

// routes/web.php
Route::actions(function () {
    Route::post('articles', 'PublishANewArticle');
});

Another solution would be to create a new route file routes/action.php and register it in your RouteServiceProvider.

// app/Providers/RouteServiceProvider.php
Route::middleware('web')
     ->namespace('App\Actions')
     ->group(base_path('routes/action.php'));

// routes/action.php
Route::post('articles', 'PublishANewArticle');

# Registering routes directly in actions

Another way to register actions as controllers is to define the routes directly within the actions using the static routes method.

class GetArticlesFromAuthor extends Action
{
    public static function routes(Router $router)
    {
        $router->get('author/{author}/articles', static::class);
    }

    public function handle(User $author)
    {
        return $author->articles;
    }
}

WARNING

This will work out-of-the-box for actions defined in the app/Actions folder (or subfolders). If some of your actions live outside this folder, you will need to call Actions::paths([...]) in a service provider to let the library know where to find your actions in order to register these routes.

See the "Registering actions" page for more details.

# Registering middleware

You can register middleware using the middleware method.

public function middleware()
{
    return ['auth'];
}

Alternatively, if you decide to register your routes directly within your actions, you can specify the middleware in the routes static method.

public static function routes(Router $router)
{
    $router->middleware(['web', 'auth'])->get('author/{author}/articles', static::class);
}

# Returning HTTP responses

It is good practice to let the action return a value that makes sense for your domain. For example, the article that was just created or the filtered list of topics that we are searching for.

However, you might want to wrap that value into a proper HTTP response when the action is being ran as a controller. You can use the response method for that. It provides the result of the handle method as first argument and the HTTP request as second argument.

public function response($result, $request)
{
    return view('articles.index', [
        'articles' => $result,
    ])
}

If you want to return distinctive responses for clients that require HTML and clients that require JSON, you can respectively use the htmlResponse and jsonResponse methods.

public function htmlResponse($result, $request)
{
    return view('articles.index', [
        'articles' => $result,
    ]);
}

public function jsonResponse($result, $request)
{
    return ArticleResource::collection($result);
}
Last Updated: 12/23/2020, 10:25:04 AM