Pragmatism in the real world

Using Eloquent in Slim Framework

Eloquent is one of the easier ORMs to get started with, so let’s look at how to use and test it within a Slim Framework application.

Set up

You can add Eloquent to your Slim project via composer:

composer require illuminate/database

You now need to bootstrap it. To do this we need some settings, so lets do that in our standard Slim configuration:

$config = [
    'settings' => [
        'db' => [
            // Eloquent configuration
            'driver'    => 'mysql',
            'host'      => 'localhost',
            'database'  => 'bookshelf',
            'username'  => 'rob',
            'password'  => '123456',
            'charset'   => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',
        ],
    ],
]

We then instantiate the capsule manager and boot Eloquent , probably in index.php:

$container = $app->getContainer()
$capsule = new Illuminate\Database\Capsule\Manager;
$capsule->addConnection($container->get('settings')['db']);
$capsule->bootEloquent();

From this point, you can simply follow the documentation to create your Eloquent models and use them within your route callables.

Testing your models

At first glance, it seems quite hard to test your models as the find method is a static.

For example, consider this route callback within an action class:

public function __invoke($request, $response, $args)
{
    return $this->view->render($response, 'author/list.twig', [
        'authors' => Author::all(),
    ]);
});

To test it, I followed a tip from Taylor Otwell and wrapped the find method in a class that I could then mock:

namespace Bookshelf;

class AuthorRepository
{
    public function all()
    {
        return Author::all();
    }

    public function find($id, $columns = ['*'])
    {
        return Author::find($id, $columns);
    }
}

The callback then becomes:

public function __invoke($request, $response, $args)
    return $this->view->render($response, 'author/list.twig', [
        'books' => $this->authorRepository->all()
    ]);
});

Like the view, the authorRepository is injected into the constructor and so can be mocked out. The full class looks like this:

namespace Bookshelf;

use Slim\Views\Twig;
use Bookshelf\AuthorRepository;

final class ListAuthorsAction
{
    private $view;
    private $authorRepository;

    public function __construct(
        Twig $view,
        AuthorRepository $authorRepository
    ) {
        $this->view = $view;
        $this->authorRepository = $authorRepository;
    }

    public function __invoke($request, $response, $args)
    {
        return $this->view->render($response, 'author/list.twig', [
            'authors' => $this->authorRepository->all()
        ]);
    }
}

To test using PHPUnit, we start by creating a mock for the AuthorRepository:

$authorRepository = $this->getMockBuilder(AuthorRepository::class)
    ->setMethods(['all'])
    ->disableOriginalConstructor()
    ->getMock();

Then we set the expectation that the all method will return a collection of Authors:

use Illuminate\Database\Eloquent\Collection;
$authors = new Collection([
    new Author(['name' => 'test1']),
    new Author(['name' => 'test2']),
    new Author(['name' => 'test3']),
]);

$authorRepository->expects($this->once())
    ->method('all')
    ->willReturn($authors);

We do the same for the view object:

$view = $this->getMockBuilder(Twig::class)
    ->setMethods(['render'])
    ->disableOriginalConstructor()
    ->getMock();

$response = new Response();
$response->write('Rendered text on page');

$this->view->expects($this->once())
    ->method('render')
    ->with($response, 'author/list.twig', ['authors' => $authors])
    ->willReturn($response);

Now we can instantiate the action class with our two mock objects. We then create a request and response objects and run the action and test the returned response:

$action = new ListAuthorsAction($view, $authorRepository);
$request = Request::createFromEnvironment(Environment::mock());
$response = new Response();

$ReturnedResponse = $action($request, $response);

$this->assertSame((string)$ReturnedResponse->getBody(), 'Rendered text on page');

Summary

Eloquent is quite a nice implementation of the Active Record pattern. If that meets your needs, then it is easy to use within a Slim Framework application, so go for it!

4 thoughts on “Using Eloquent in Slim Framework

  1. Thanks for sharing such a straightforward and easy-to-understand introduction!

    Even so, I have a suggestion: don't set an expectation on the queries in your tests, unless the point of the test is to show that the client (controller, I imagine) is issuing that specific query based on the incoming request (example: for a report run on April 2, 2016, verify that the controller calls "find all orders pending as of April 2, 2016", because getting that parameter right matters). If, instead, you simply want to check that the client handles the various results of the query (0 authors, 1 author, 1.4 million authors, raising an error), then merely stub the query. Setting expectations on queries is one path that leads toward the kind of brittle tests that make programmers hate test doubles. :) "Stub queries; expect actions/commands".

    Recommended reading:

    http://www.jmock.org/yoga.html

    http://stackoverflow.com/a/4615016

    http://blog.thecodewhisperer.com/permalink/i-like-mocks

Comments are closed.