Creating Facades for Repositories in Laravel
Background
I've been using Laravel 4 heavily recently and part of my enjoyment in using this framework is the testing utilities available for developers to use. After getting on the TDD methodology, I started looking for best practices with regard to testing in Laravel. Two of the most useful resources I found is Jeffrey Way's tutorial on Testing Laravel Controllers and Philip Brown’s How to structure testable Controllers in Laravel 4
. For this tip, we’ll be using Jeffrey’s article as our main reference, but I do suggest that you also have a look at Philip’s post as well. For the examples below, we’ll be referencing Jeffrey’s examples for the sake of continuity.
Continuation
After discussing how to set up your first controller test, down to creating repositories to mock the database, Jeffrey ended his tutorial with mocking from the Eloquent model. Now, we’ll attempt to decouple the test logic from the model.
<?php
## app/models/Post.php
class Post extends Eloquent {
public static function shouldReceive()
{
$class = get_called_class();
$repo = "Way\\Storage\\{$class}\\{$class}RepositoryInterface";
$mock = Mockery::mock($repo);
App::instance($repo, $mock);
return call_user_func_array(
[$mock, 'shouldReceive'], func_get_args());
}
}
Given the example above from Jeffrey, the purpose of adding shouldReceive()
to your Eloquent models is to make your tests more readable. However, as he puts it, "Should I be embedding test logic into production code?" There is another way for us to remove the test logic and that is through implementing facades.
Creating the Facade
The reason for creating a facade for our repository is that facades were created primarily for testing. So, instead of creating the mock objects ourselves, we can call the facade we created and mock that instead. Remember, shouldReceive()
is inherent to facades, so let's use that to our advantage. First, let's remove the shouldReceive()
function from our model, then create our facade:
<?php namespace Way\Support\Facades;
# app/lib/Way/Support/Facades/Post.php
use Illuminate\Support\Facades\Facade;
class Post extends Facade {
protected static function getFacadeAccessor()
{
return 'Way\Storage\Post\PostRepositoryInterface';
}
}
To make this facade simpler to use, let's add an alias for it. There are two ways that we can do this. One is to add it on our facade to the alias
array in the app/config/app.php
configuration file. This is a straightforward approach, but I prefer to centralize our alias to our service provider. This way, we can easily find all of the aliases associated with our facades. Thanks, Chris Fidao, for the snippet!
<?php namespace Way\Storage;
# app/lib/Way/Storage/StorageServiceProvider.php
use Illuminate\Support\ServiceProvider;
class StorageServiceProvider extends ServiceProvider {
// Triggered automatically by Laravel
public function register()
{
$this->app->bind(
'Way\Storage\Post\PostRepositoryInterface',
'Way\Storage\Post\EloquentPostRepository'
);
$this->app->booting(function()
{
$loader = \Illuminate\Foundation\AliasLoader::getInstance();
$loader->alias('Post', 'Way\Support\Facades\Post');
});
}
}
Notice that our model and facade both have the same class names. For consistency with naming conventions, let's refactor our model to PostModel
; don't forget to rename the model references in EloquentPostRepository
, too. Note, however, that this doesn't follow the naming conventions of Eloquent, so you may have to override some arguments especially for model relationships.
However, we're not yet done. Our controller will now be injected with the facade instead of the model, and we no longer need this since the facade should handle this for us. Below is the modified PostsController
, which no longer depends on the Post
to be injected in the constructor:
<?php
# app/controllers/PostsController.php
class PostsController extends BaseController {
public function index()
{
$posts = Post::all();
return View::make('posts.index', ['posts' => $posts]);
}
public function store()
{
$input = Input::all();
// We'll run validation in the controller for convenience
// You should export this to the model, or a service
$v = Validator::make($input, ['title' => 'required']);
if ($v->fails())
{
return Redirect::route('posts.create')
->withInput()
->withErrors($v->messages());
}
Post::create($input);
return Redirect::route('posts.index')
->with('flash', 'Your post has been created!');
}
}
Testing
Since Jeffrey has already used shouldReceive()
for his tests, there's no need for us to modify our tests. We simply changed the reference of Post
from a model to a facade.
That's it! I hope this approach works for you, especially if you're concerned with maximum flexibility with your interface.
Written by Zennon Gosalvez
Related protips
2 Responses
Why is it better to do the validtion of data in the model class? Isnt the controller ment to do this kind of stufff?
It's going to be better if you use the model from multiple locations in your application. Implementing the validation in the model allows you to validate all incoming data, once, from anywhere in your application, thus keeping your codebase D.R.Y. I highly recommend using Laravelbook\Ardent for this type of implementation.