likerRr
likerRr

Reputation: 1306

Repository pattern implementation with Laravel

Recently i start to study Laravel 4 and it's capabilities. I want to implement Repository pattern to move model logic there. And at this point i faced with a number of inconvenience or misunderstanding of how to organize it. General question I have goes something like this: is it possible to implement and apply this pattern in Laravel without headaches, and whether it's worth?

The question would be divided into several parts, which caused my confusion.

1) Laravel provides convenient way to bind model as controller parameter, e.g. i do it this way:

// routes.php
Route::bind('article', function($slug)
{
    return Article::where('slug', $slug)->first();
});

Route::get('articles/{article}', 'ArticlesController@getArticle');

// controllers/ArticlesController.php
class ArticlesController extends BaseController {

    public function getArticle(Article $article)
    {
        return View::make('article.show', compact('article'));
    }
}

If I want to use the Repository pattern, then I can't use this approach, since in this case the controller will clearly be aware of the existence of models Article? Whether it will be correct to re-write this example using Repository pattern this way:

// routes.php
Route::get('articles/{slug}', 'ArticlesController@getArticle');

// controllers/ArticlesController.php
class ArticlesController extends BaseController {

    private $article;

    public function __construct(ArticleRepository $article) {
        $this->article = $article;
    }

    public function getArticle($slug)
    {
        $article = $this->article->findBySlug($slug);

        return View::make('article.show', compact('article'));
    }
}

2) Suppose, my code above with the use of Repository is correct. Now I want to increment article views counter each time it will be showed, however, I want to make this processing in the Event. That is, the code is as follows:

// routes.php
Route::get('articles/{slug}', 'ArticlesController@getArticle');

// controllers/ArticlesController.php
class ArticlesController extends BaseController {

    private $article;

    public function __construct(ArticleRepository $article) {
        $this->article = $article;
    }

    public function getArticle($slug)
    {
        $article = $this->article->findBySlug($slug);
        Events::fire('article.shown');

        return View::make('articles.single', compact('article'));
    }
}

// some event subscriber
class ArticleSubscriber {

    public function onShown()
    {
        // why implementation is missed described bellow
    }

    public function subscribe($events)
    {
        $events->listen('article.shown', 'ArticleSubscriber@onShown');
    }

}

At this point I was puzzled again about how to implement event processing. I can't pass $article model directly to event, because, again, it's violates the principles of OOP and my subscriber will know about the existence of article model. So, i can't do so:

// controllers/ArticlesController.php
...
\Events::fire('article.shown', $article);
...

// some event subscriber
...
public function onShown(Article $article)
{
    $article->increment('views');
}
...

On the other hand I don't see any sense to introduce into subscriber repository ArticleRepository (or to inject it in subscriber's contructor), because first I should to find an article, and then update the counter, in the end, i will get extra query (cause previously in constructor i do the same) to the database:

// controllers/ArticlesController.php
...
Events::fire('article.shown', $slug);
...

// some event subscriber
...
private $article;

public function __construct(ArticleRepository $articleRepository)
{
    $this->article = $articleRepository;
}

public function onShown($slug)
{
    $article = $this->articleRepository->findBySlug($slug);
    $article->increment('views');
}
...

Moreover, after the Event handled (i.e. increased views count), it is necessary that the controller knew about the updated model, because in the view i want to display the updated views counter. It turns out that somehow I still need to return a new model from Event, but I would not want to Event has become a common method for processing a particular action (for this there are the repository) and return some value. In addition, you may notice that my last onShow() method again contrary to the rules of Repository pattern, but I don't understand how to put this logic to the repository:

public function onShown($slug)
{
    $article = $this->articleRepository->findBySlug($slug);
    // INCORRECT! because the Event shouldn't know that the model is able to implement Eloquent
    // $article->increment('views');
}

Can I somehow pass the found model back to the repository and to increase her counter (does it contradict this approach to Repository pattern?)? Something like this:

public function onShown($slug)
{
    $article = $this->articleRepository->findBySlug($slug);
    $this->articleRepository->updateViews($article);
}

// ArticleRepository.php
...
public function updateViews(Article $article) {
    $article->increment('views');
}
...

As a result, I will try to formulate all more compact:

  1. I'll have to refuse to pass models directly to controller and other comforts provided by DI, if i'll use Repository pattern?

  2. Is it possible to use the repository for keeping the state of the model and pass it between entities (e.g., from the filter to the controller from the controller to Event and back) avoiding obscene repeated calls to db and is this approach will be correct (model persistence)?

Such things, these are my questions. I would like to hear the answers, thoughts, comments. Maybe, I incorrect approach to apply the pattern? Now it causes more headaches than it solves issue of data mapping.

Also i've read some articles about Repository implementation:

  1. http://heera.it/laravel-repository-pattern#.VFaKu8lIRLe
  2. http://vegibit.com/laravel-repository-pattern

but it doesn't solve my misunderstanding

Upvotes: 7

Views: 3392

Answers (4)

Yousha Aleayoub
Yousha Aleayoub

Reputation: 5715

Engineering note

Implementing a "true" repository pattern in Laravel applications is challenging (waste or only partial class) because Laravel's Eloquent itself is designed to handle much of the data and logic access layer.

Eloquent models follows ActiveRecord pattern, where each model instance directly interacts with database and encapsulates both data and behavior! So making it hard to strictly separate concerns into repositories without encountering some drawbacks.

Eloquent already has powerful querying, relationships, and eager-loading capabilities which make repositories feel unnecessary... For example, you can easily perform complex queries with methods like with(), where(), find(), and orWhere() directly on the model.

Ok, in applications with complex query logic, repositories can be useful to encapsulate that logic, BUT: Eloquent's scopes, Policy and query builder capabilities can often handle complex queries just as efficiently within the model itself.

So when to implement "true" repository?

IMO, abstracting third-party APIs or external/swappable data sources.

How about acceptable separate concerns without overengineering?

Use service classes or trait-based repositories.

Upvotes: 0

Khuram
Khuram

Reputation: 1850

@likerRr you asked:

Whether it will be correct to re-write this example using Repository pattern this way:

First of all, you should think why do we use Desing Patterns and specifically the Repository Pattern? We use the Repository pattern to implement the SOLID principles (all or few). The first thing is one should not access the data source/database in controllers. Doing this you are:

  1. violating the Single Responsibility Principle (S in SOLID). Your controller must not be aware of the data source. It is only responsible to respond to HTTP Request or meditate b/w your Application and HTTP.
  2. you are violating the Liskov Substitution Principle
  3. you are violating the Dependency Inversion Principle.

So, that's why you should use not only the Repository Patterns but implement the SOLID Principles. how to do then? Wrap your data source access in somewhere else and the Repository is the best place. Suppose you are getting a user using the following code:

User::where('id', $id)->where('company_id', $companyId)->get();

If you write this code in all your controllers where you needed, you can not do the following:

  1. You can not change your data source without changing that line
  2. You can not test your code
  3. and eventually this will be hard to maintain

2: Can I somehow pass the found model back to the repository and to increase her counter (does it contradict this approach to Repository pattern?)

You are doing right in your snippet. Actually, you want to gain both the ease provided by the Laravel and the benefits of Patterns. You probably know that you have to sacrifice something for another thing. Drivers driving on the easy to drive roads can not become good drivers. So, I will suggest you follow the design patterns and SOLID Principles and leave the "ease" provided by the Laravel. Otherwise, this so-called "ease" will create so many troubles for you and you couldn't even maintain your project and all would get vanished.

Last Things about Using Events:

Events are nothing but Observer Pattern. In your case, there seems no need to use the Observer Patterns, so you should avoid it.

The best candidate for the observer patterns/Events would be that you are charging your client and after your successful charge, you want to send the currently charged amount detail along with the previous heavy calculation via email. As this will take time and you don't want to show the user a page reload while all this heavy processing is being done. So you can charge the user, fire an event, redirect the user to your site with a success message and let the event handler do the heavy lifting and your user can do other stuff.

You can ask any other questions if you want to!

Upvotes: 2

Alex
Alex

Reputation: 1644

It works fine!

api.php

Route::get('/articles/{article}', 'ArticleController@getArticle')->middleware('can:get,article');

ArticleController.php

class ArticleController extends BaseController {

    protected $repo;

    public function __construct(ArticleRepository $repository) {

        $this->repo = $repository;
    }

    public function getArticle(int $id)
    {
        $articleRepo = $this->repo->find($id);
        return View::make('article.show', compact('articleRepo'));
    }
}

Upvotes: 0

Everon
Everon

Reputation: 3879

The repository pattern has it's pros and cons.

From my relatively recent adoption of the pattern it allows for a much easier testing experience - especially when inheritance and polymorphism are leveraged.

Below is an excerpt of a near catch-all repository contract I use.

interface EntityRepository
{
    /**
     * @param $id
     * @return array
     */
    public function getById($id);

    /**
     * @return array
     */
    public function getAll();

    /**
     * @param array $attr
     * @return array
     */
    public function save(array $attr);

    /**
     * @param $id
     */
    public function delete($id);

    /**
     * Checks if a record with the given values exists
     * @param array $attr
     * @return bool
     */
    public function exists(array $attr);

    /**
     * Checks if any records with any of these values exists and returns true or false
     * @param array $attr
     * @return bool
     */
    public function unique(array $attr);
}

The contract is relatively self explanatory, save() manages both inserting and updating entities (models).

From here I'll create an abstract class that implements all the functionality for the vendor(s) I want to use - such as Eloquent or Doctrine.

It's worth noting, this contract wouldn't catch every thing and I am currently in the process of creating a separate implementation that handles many to many relationships but that's another story.

To create my individual repository classes, to do that I create another contract for each repository that extends the EntityRepositoryContract and states what functionality is exclusive to them. In the case of a user - registerUser(...) and disableUser(...) etc etc.

The final classes will then extend the EloquentEntityRepository and implement the relevant contract for the repository. The class signature for the EloquentUserRepository would be some thing like:

class EloquentUserRepository extends EloquentEntityRepository implements UserRepositoryContract
{
...
}

In my own implementation to make the class names less verbose I leverage namespaces to alias each implementation like so:

use Repo\Eloquent\UserRepo; //For the Eloquent implementation
use Repo\Doctrine\UserRepo; //For the Doctrine implementation

I try not to bunch all of my repositories together and instead group my application by feature to keep my directory structure less cluttered.

I'm skipping out on a lot of details but I don't want to throw too much in so experiment with inheritance and polymorphism to see what you can achieve for a better workflow with Repositories.

With my current workflow, my tests have their own abstract classes exclusively for the base repository contract that all entity repositories implement making testing a breeze after the first few hurdles.

Best of luck!

Upvotes: 0

Related Questions