Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ADR out of the box for Brandon
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Collection;
use Illuminate\Database\Connection;
use Illuminate\Contracts\Routing\ResponseFactory;
/**
* Resolve an action out of the container and call it, injecting method dependencies.
*/
function marshal($action)
{
return App::call([App::make($action), 'handle']);
}
/**
* A repository...
*/
class PostRepository
{
protected $connection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
public function all()
{
return $this->connection->table('posts')->get();
}
}
/**
* The responder...
*/
class ListPostsResponder
{
protected $response;
public function __construct(ResponseFactory $response)
{
$this->response = $response;
}
public function handle(Collection $data)
{
if (count($data) === 0) {
return $this->response->make('Not found.', 404);
} else {
return $this->response->view('posts.index', ['posts' => $data->all()]);
}
}
}
/**
* The action...
*/
class ListPosts
{
public function handle(Request $request,
ListPostsResponder $responder,
PostRepository $posts)
{
return $responder->handle(
$posts->all()
);
}
}
/**
* The route...
*/
$router->get('/posts', function () {
return marshal(ListPosts::class);
});
@pmjones

This comment has been minimized.

Copy link

commented Jun 3, 2016

As far as I can tell, the separations look like they conform to Action-Domain-Responder. The dispatch mechanism (called "marshal" here?) creates the action class (ListPosts) and invokes it. The action then calls a domain element and gives the results to a responder, which builds the response. (I have quibbles about the use of "method injection" for the action when plain old constructor injection would do, but that's minor.)

Nice to see this! As I have said elsewhere, ADR is not a dramatic departure from what we think of as MVC on the server-side; it is a refinement, not a revolution. The main difference is that the "controllers" are not responsible for any part of building the response, nor for any part of the domain work. This example is a good demonstration of that.

N.b. -- One thing for readers to be aware of for later: be careful not to pollute the domain-level work with HTTP elements. For example, at some point users will have to pass input elements from HTTP into the domain callable for it to do its work; e.g., a page number for paginated results. At that point, be sure to have the action collect the specific inputs and pass them to the domain in a non-HTTP-specific way, instead of giving the HTTP request object to the domain directly.

@taylorotwell

This comment has been minimized.

Copy link
Owner Author

commented Jun 3, 2016

For future readers, you can do constructor injection on the action by moving those dependencies to the constructor. No other changes will be required and Laravel's IoC container will automatically do the constructor injection.

@joshbrw

This comment has been minimized.

Copy link

commented Jun 3, 2016

(As an aside, you don't need an else if you return in the first half of the conditional: https://gist.github.com/taylorotwell/68f614deb9538f2e30108c2698266fda#file-gistfile1-php-L63)

@AkenRoberts

This comment has been minimized.

Copy link

commented Jun 3, 2016

For what it's worth, I extended Laravel's router to support an invoke parameter in the route definition, so that I could define invokable Action classes without needing to specify the @__invoke method suffix on every item.

$router->get('posts', [
    'as' => 'posts.list',
    'invoke' => ListPosts::class,
]);

It would be nice if the router could determine if an item passed is callable rather than looking for only \Closure so extending is not needed, however I understand some of the holes or gotchas that come with doing so.

@jacobhenke

This comment has been minimized.

Copy link

commented Jun 3, 2016

@pmjones your link was broken: http://pmjones.io/adr/

@pmjones

This comment has been minimized.

Copy link

commented Jun 4, 2016

@jacobhenke fixed; thanks!

@pmoust

This comment has been minimized.

Copy link

commented Jun 4, 2016

I got prompted to visit this gist without much context.
Was there any doubt that Laravel could not conform to ADR?

@joshhornby

This comment has been minimized.

Copy link

commented Jun 5, 2016

@cryode Any chance you could share this code?

@rapliandras

This comment has been minimized.

Copy link

commented Sep 12, 2018

@AkenRoberts

I'm working on the same thing.

I read somewhere that callables were fixed in 5.3 but they definitely don't work in 5.7.

ADR makes you create a separate route for everything, route config becomes a mess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.