Eloquent ORM
When most of us started interacting with databases in Laravel, we started using the Eloquent ORM.
The Eloquent ORM included with Laravel provides a beautiful, simple ActiveRecord implementation for working with your database. Each database table has a corresponding "Model" which is used to interact with that table.
A simple model might look like this:
1<?php namespace App; 2 3use Illuminate\Database\Eloquent\Model; 4 5class Post extends Model { 6 7 public function scopePublished($query) 8 { 9 return $query->whereNotNull('published_at');10 }11 12}
For a lot of CRUD-based applications, with simple operations and perhaps for several of your applications, using this approach will likely be good enough. You can make the calls directly in your controllers and achieve results easily enough.
1<?php namespace App\Http\Controllers; 2 3use App\Post; 4 5class PostsController extends Controller { 6 7 /** 8 * List all published posts. 9 *10 * @return Response11 */12 public function index()13 {14 $posts = Post::published()->get();15 16 return view('posts.index', compact('posts'));17 }18 19 /**20 * Display a specific post21 *22 * @param int $id23 *24 * @return Response25 */26 public function show($id)27 {28 $post = Post::find($id);29 30 return view('posts.show', compact('post'));31 }32 33}
Route Model Binding
Then you might learn about route model binding. What this allows you to do is bind a parameter in your route - something like {post}
in the route /blog/{post}
- directly to its corresponding Eloquent model.
1<?php namespace app\Providers; 2 3use Illuminate\Routing\Router; 4use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; 5 6/** 7 * app/Providers/RouteServiceProvider.php 8 */ 9class RouteServiceProvider extends ServiceProvider {10 11 // Boilerplate omitted for brevity12 public function boot(Router $router)13 {14 parent::boot($router);15 16 $router->model('post', '\App\Post');17 }18 19}
This means that any time you access a route that has a {post}
parameter in it, Laravel will perform a find
query using the variable as the parameter and inject an instance of App\Post
directly into your controller. If a matching record can't be found, you'll get a NotFoundHttpException
.
Using route model binding is incredibly powerful and allows you to make your controller methods really lightweight. You know that your show
method will always get a valid Post
model and if one can't be found, Laravel will render your application's 404 page.
1<?php namespace app\Http\Controllers; 2 3use App\Post; 4 5class PostsController extends Controller { 6 7 /** 8 * Show a single post 9 *10 * @param Post $post11 *12 * @return Response13 */14 public function show(Post $post)15 {16 return view('posts.show', compact('post'));17 }18 19}
Repositories
One of the more advanced methods of interacting with a database you might come across is the repository pattern. The repository pattern allows you to define your interactions with the underlying persistence layer in a way that keeps your dependency on any given persistence mechanism (database, API) abstracted behind a common interface. You can create methods that are meaningful such as getAllPublishedPosts
or getPublishedPostsPaginated
. These are easy to understand at a quick glance, without needing to look at exactly how that data is being obtained.
1<?php namespace App\Repositories; 2 3use App\Post; 4 5class PostRepository { 6 7 /** 8 * @var Post 9 */10 private $model;11 12 /**13 * Instantiate the post repository14 *15 * @param Post $model16 */17 public function __construct(Post $model)18 {19 $this->model = $model20 }21 22 /**23 * Get all published posts, ordered by most recently published.24 *25 * @return Collection26 */27 public function getAllPublishedPosts()28 {29 return $this->model->published()->latest('published_at')->get();30 }31 32 /**33 * Get all published posts, ordered by most recently published, and paginated.34 *35 * @return Collection36 */37 public function getPublishedPostsPaginated($paginate = 10)38 {39 return $this->model->published()->latest('published_at')->simplePaginate($paginate);40 }41 42}
1<?php namespace App\Http\Controllers; 2 3use App\Repositories\PostRepository; 4 5class PostsController extends Controller { 6 7 /** 8 * @var PostRepository 9 */10 protected $postRepository;11 12 13 /**14 * Instantiate the posts controller15 *16 * @param PostRepository $postRepository17 */18 public function __construct(PostRepository $postRepository)19 {20 $this->postRepository = $postRepository;21 }22 23 /**24 * Show all posts25 *26 * @return Response27 */28 public function index()29 {30 $posts = $this->postRepository->getPublishedPostsPaginated();31 32 return view('posts.index', compact('posts'));33 }34 35}
Conclusion
The question now is; when do I choose the Eloquent ORM, route model binding, or repository pattern?
The answer? It depends! It depends entirely on your use case and what you're comfortable with.
Using the Eloquent ORM directly within your controller methods (or even within route closures) is perfectly acceptable for a lot of smaller CRUD-based applications. Your first todo or blog app probably isn't going to need the full flexibility afforded to you by the repository pattern.
Route model binding is a good way to easily load a given model directly into your route. If all you want it to do is a simple Model::find($id)
, then using Route::model
binding is fine. If it's a simple condition like Model::with('relationship')->where('condition', 'value')->first()
you could probably comfortably use Route::bind
.
If you find that your bindings are getting large and/or complex, or you're using the same query in multiple places, then it might be time to drop it into a repository method.
You can, of course, use any, all, or a combination of these methods. You may only need a simple Route::model
to access your show/edit routes and return a model, but elsewhere in your application, you may need more complex lookups - for example, you might want to getAllPublishedPosts
and have some conditions around that to display in your index route.
Before you start implementing the complexities of the repository pattern, ask yourself if the overhead is necessary. Do you need to spend the extra time writing and implementing your repositories where a simple call to the ORM or use of route model binding will suffice?
Remember one thing: try and keep things as simple as possible for as long as possible.
Further reading
- Eloquent ORM - Laravel Docs
- Route Model Binding - Laravel Docs
- The Repository Pattern - Shawn McCool
- Active Repository is an Anti-Pattern - Adam Wathan