JarekTkaczyk's avatar

@cm Ask Jeff why he does it :) I would say he does it because he is a teacher, Laracasts is his teaching business and he talks about many different approaches, that you never use all at once. Building reliable application is different from playing with different patterns and ideas.

Anyway, you didn't answer my question - what were your referring to by model in your previous posts? I understand you have Eloquent models defined for all your entities and your domain is thus tightly coupled to the persistence layer, a DB. That's the case for most small (or even bigger) applications and there's nothing bad in this approach, but it has pretty much nothing to do with Model in MVC pattern whatsoever.

nolros's avatar

@cm I think you have it the other way around.

seems that there's no need to use repositories at all as long as your models rely on Eloquent.

I would argue that you most likely need an repository if you using Eloquent. I think what Shawn is speaking about are persistent models like Doctrine i.e. active record models, where a repository would probably not make sense. The closer you get to the DB transaction the less likely you are to use a repo.

ATOM-Group's avatar

@cm

But why would I use a repository class for that if I could make a static function on my Eloquent model and use it like so User::findUserByEmailOrPhone();?

Three reasons, not all of which are always going to be a concern every project:

  1. It can start adding some bloat to the responsibility of the Eloquent model itself. When I work with a Repository, I know that object has one purpose in life: to give me objects. Meanwhile that means I treat Eloquent objects as representations of single entities, rather than as a hybrid of an entity representation, and some kind of table gateway that gets me new entities. By using a repository, I only need to treat Eloquent like a gateway within the boundaries of that repository, and no place else.

  2. Sometimes (rarely, but sometimes), you need to able to query data from two different models, and you cannot easily do dependency injection with Eloquent models. This means you either need to use App::make() service location in your Eloquent models, or be really nasty and tightly couple a class with new.

  3. You can encapsulate querying with methods, but not necessarily saving or updating. So if you want stricter separation such that you never call $User->save(), but rather $UserRepository->save($User), then you can't easily do that by treating Eloquent directly as a repository.

A repository is a layer that gives me the flexibility to do dependency injection of whatever I need to operate on data before I save it with Eloquent, or before I return it to my client code.

Like always, a repository is a trade-off. Do you need it? No. Is it helpful? Maybe. Maybe not. It's really up to you how you would prefer to use it.

Some call active record repositories an anti-pattern, but I disagree with that. However, it is a lot of work to try and "de-ActiveRecord" your ActiveRecord (which defeats the purpose of ActiveRecord!), so it's not always worth it.

2 likes
nolros's avatar

@tag @cm

agreed, the problem is Eloquent as a collection model creates confusion around repo and models because you can pretty much do anything in the model that you can do in a repo so it becomes somewhat of academic conversation. That said, I believe, in my opinion, is that the line of demarcation is data-centric or transactional-centric. The model to me provide predictability as to how the data is stored and can have has much logic as it needs to do so. However, if the logic does not result in data manipulation / formatting then I move it to repo. Example, I cannot store userOfEmail($email) and it does not format or manipulate data so that would go into my repo versus let's say emailConcatCompanyName($email, CompanyName $companyName ) would be in my model even though it has another dependency . Whereas setEmailAttribute would be a data manipulation method and as such would be in model.

Which is why I'm unsure that model is dependent injection is a criteria. I do get what you saying in that you don't want to cross boundaries with models i.e. single responsibility, but we can manipulate and, in my opinion, should manipulate data in the model. Example below. Eloquent are just classes and as such pretty much can do anything.

Bad example, but don't want to think right now :)

    // example in some controller or service class 
    public function UserRegister()
    {
        $user = new User();

        $username = new UserName('MyNameIsBob');

        // OR phone number
        $phone = new Phone('555-5555');
        
        $usernameValueObject = $user->UserNameObject($username);

        // could also add other value objects like phone number
        $phoneValueObject = $user->PhoneObject($phone);
        
        $string = $usernameValueObject->toString();
        
        dd($string);
        
        // >>'MYNAMEISBOB'

    }
    

In my model:

    public function UserName(Username $name)
    {
        return $name;
    }   

SImple value object, but it could be any object or collection that I inject.

    class Username 
    {

        /**
         * @var string
         */
        private $value;

        /**
         * Create a new Space name
         *
         * @param   string $name
         * @return  mixed
         */
        public function __construct($name)
        {

            Assertion::regex($name, '/[A-Z]\.?\w*\-?[A-Z]?\w*s?/');

            $this->value = strtoupper($name);

        }

        /**
         * Return the object as a string
         *
         * @return string
         */
        public function __toString()
        {
            return $this->value;
        }


        /**
         * Return the object as a string
         *
         * @return string
         */
        public function toString()
        {
            return $this->value;
        }

    }

In mu opinion all of this should be model related as ultimately I'm manipulating the username to uppercase. (yes much better ways to do get a string to uppercase ... lol, but you get the point).

Versus userOfUsername($username) is a query and not a data manipulation i.e. even if I injected Username value object it still would not be a model method e.g. userOfUsername(Username $username)

Anyway that is my last 2 cents on this ... as always it has been great to read and hear others opinions / thoughts.

Thoughts?

Many Thanks

JarekTkaczyk's avatar

@blackbird I don't want to criticise it in any way, but this article is bs. I can't find any eloquent trick there, these repositories don't add any value to eloquent - with such examples I agree with @cm in 100% that it doesn't make sense and moreover there are errors..

Watch this:

/**
 * Find an entity by id and include the posts
 *
 * @param int $id
 * @return Illuminate\Database\Eloquent\Model
 */
public function getByIdWithPosts($id)
{
  return $this->model->find($id)->with(array('posts'))->first();
}

// it does;
$this->model->find($id) // 1st query, fetch row with id = $id
    ->with(...)->first(); // 2nd query, fetch 'random' - first row with related posts..

it doesn't find entity with id = $id, it returns first entity after 2 queries..

It is inconsistent:

getByIdWithSomething -> model->with(...)
// but somewhere else
getPostsWithComment -> model->has(...)
3 likes
bobbybouwmann's avatar

Fair enough. Then is my question: What is the best way to use the repository pattern?

Everyone is using the repository pattern the way Jeffrey showed and to be honest I do the same.

JarekTkaczyk's avatar

@This whole thread is about it. Make your opinion :) And read my first post on this page concerning contents of Jeffrey's videos.

cm's avatar

@tag

Meanwhile that means I treat Eloquent objects as representations of single entities, rather than as a hybrid of an entity representation, and some kind of table gateway that gets me new entities.

I guess this is exactly where a lot of confusion about Eloquent and models come from.

By using a repository, I only need to treat Eloquent like a gateway within the boundaries of that repository, and no place else.

But then, do you still let your domain models extend Eloquent?

2+3: Good points, thanks a lot.

@JarekTkaczyk

Anyway, you didn't answer my question - what were your referring to by model in your previous posts?

I meant the "domain model". I probably want to use Eloquent, but I didn't check yet, which features described in the Eloquent docs are merely features of L5's base model and which are strictly tied to Eloquent.

Do you generally use Eloquent for your models?

@nolros

you can pretty much do anything in the model that you can do in a repo so it becomes somewhat of academic conversation

Well put.

Versus userOfUsername($username) is a query and not a data manipulation i.e. even if I injected Username value object it still would not be a model method e.g. userOfUsername(Username $username)

Not sure, I have to think about this. I wonder if a repository can manipulate some data, too. Maybe not, maybe it's a good definition.

Thank you all for your thoughts. So, to anyone using repositories: Do you have models that live in your domain only without using Eloquent? If so, do your repositories use Eloquent and how? Or do you simply use the Query Builder in a repository?

2 likes
JarekTkaczyk's avatar

@cm That's what I'm talking about - there is no other model in Laravel at all. Model = Illuminate\Database\Eloquent\Model, so you either use it, or you define your own entities, nothing in between. And yes, I use Eloquent, I'm a big fan of it, but still I use also repositories wrapping them.

4 likes
philthathril's avatar

Thanks to all for the great input and conversation thus far. I was definitely hoping for a black and white answer, but I can now clearly see that it's only a bunch of grey because there is no single "right" way to code a solution. As a newbie to laravel, this is frustrating, but it does get me to think a lot about the direction I want to take my app(s).

FWIW, I believe the direction I will go is to use a combination of repositories and the command bus. This will allow enough abstraction from the controller, which will help keep all my business rules and complex logic silo'd. Using the command bus is a good solution when using L5, but I'm curious as to what solution I would use outside of L5 - meaning from a framework-agnostic perspective?

Also, I still haven't completely wrapped my head around what "services" are and how I'd use them. What sort of logic do they contain? Business? App? To be clear, I'm not referring to service providers....... unless they are actually the same thing and I'm just getting caught up on terminology.

cm's avatar

@philthathril

Using the command bus is a good solution when using L5, but I'm curious as to what solution I would use outside of L5 - meaning from a framework-agnostic perspective?

The same. Command Bus isn't L5 specific, but just a design pattern. In L4 it wasn't part of the framework and Jeff built it on top. You can find it here: https://laracasts.com/series/commands-and-domain-events

Much of the code is framework-agnostic.

Also, I still haven't completely wrapped my head around what "services" are and how I'd use them.

Laravel is developed with Domain Driven Design in mind. You might want to look that up: http://en.wikipedia.org/wiki/Domain-driven_design

Quote:

Service: When an operation does not conceptually belong to any object. Following the natural contours of the problem, you can implement these operations in services. See also Service (systems architecture).

Surprisingly, I found the explanation in the German Wikipedia a bit better. Not a direct translation:

If you have some functionality in your domain that belongs to several objects / models, you create this functionality as service objects. However, service objects are usually stateless (read: static). You pass entities to services which use these entities to do something.

The Validator is a service, because it works in a stateless way, accepts some model / rules as input and processes them.

Another example might be a CSV exporter I guess. You pass in an entity and the service serializes the entity.

What sort of logic do they contain? Business? App? To be clear, I'm not referring to service providers....... unless they are actually the same thing and I'm just getting caught up on terminology.

Both, business and app. Depends entirely on the service. Some services are just to process some stuff in the app, other services are for maintaining some sort of business logic in your domain.

From my understanding, the service provider registers a service within your app. It's just a wrapper. See the docs: http://laravel.com/docs/5.0/providers

Note how all services themselves are labeled "Services" on the left in the table of contents.

4 likes
ghprod's avatar

really good conversation in here .. Thanks :+1:

2 likes
cristian9509's avatar

Apologize for writing to this thread after so much time but I've been reading about repositories a lot lately and the more I read the more confusing things are. Shawn post is pretty nice and explains some things about how to use repositories but it still left me with some confusing things.

As @cm was saying, using Repositories and tying them to Eloquent by either accepting Eloquent Models and returning an Eloquent Model or Collection seems to be the purpose of the Repository.

Even if swapping a backend is usually not something that will happen often creating a repository that can do that seems good design.

Here are two examples, one of tying a repo to Eloquent and the other one using plain models and collections.

Example one. User and UserRepository that comes with two backends: eloquent (mysql) and redis. The problem with this approach is that in order to make the redis repository work I need to return eloquent models and collections. That means that I will have to create the model from the redis data and return it for the controller to use. This really does not make any sense to me since Eloquent is Active Record and the way I will use the model with redis would be more like a non active record model. That means that a high percentage of what the model brings will not be useful if I switch the backend to redis.

interface RepositoryInterface
{
    public function findById($id): \Illuminate\Database\Eloquent\Model;

    public function all(): \Illuminate\Database\Eloquent\Collection;
}

interface UserRepositoryInterface extends RepositoryInterface
{
    public function getPosts($userId): \Illuminate\Database\Eloquent\Collection;
}

class UserEloquentRepository implements UserRepositoryInterface
{
    public function findById($id): \Illuminate\Database\Eloquent\Model
    {
        return User::find($id);
    }

    public function all(): \Illuminate\Database\Eloquent\Collection
    {
        return User::all();
    }

    public function getPosts($userId): \Illuminate\Database\Eloquent\Collection
    {
        return User::find($userId)->posts;
    }
}

class UserRedisRepository implements UserRepositoryInterface
{
    public function findById($id): \Illuminate\Database\Eloquent\Model
    {
        $redisUser = json_decode(Redis::get("user-{$id}"), true);

        return new User($redisUser);
    }

    public function all(): \Illuminate\Database\Eloquent\Collection
    {
        $redisUsers = json_decode(Redis::hGetAll('user'), true);
        $eloquentUsers = array_map(function ($user) { return new User($user); }, $redisUsers);

        return new \Illuminate\Database\Eloquent\Collection($eloquentUsers);
    }

    public function getPosts($userId): \Illuminate\Database\Eloquent\Collection
    {
        $redisPosts = json_decode(Redis::get("user-{$userId}-posts"), true);
        $eloquentUserPosts = array_map(function ($user) { return new User($user); }, $redisPosts);

        return new \Illuminate\Database\Eloquent\Collection($eloquentUserPosts);
    }
}

class UserController extends Controller
{
    protected $userRepository;

    public function __construct(UserRepositoryInterface $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function show($id): \Illuminate\Database\Eloquent\Model
    {
        return $this->userRepository->findById($id);
    }

    public function index(): \Illuminate\Database\Eloquent\Collection
    {
        return $this->userRepository->all();
    }

    public function userPosts($userId): \Illuminate\Database\Eloquent\Collection
    {
        return $this->userRepository->getPosts($userId);
    }
}

Now for example two, I am doing a pretty similar thing but this time for an Account model. One thing to notice is that Account model does not extend Eloquent's model. It's just a simple plain model with no persistence. There's also a plain collection used to store a collection of account records. That Collection is pretty simple right now but a ton of features can be added there. With this approach I am sure that even if I need to switch the backend from the Eloquent driver to Redis, the app will be using plain models which can be passed back to the repository to be saved, updated, etc without worrying what backend they have. The eloquent account repository does use an Eloquent Account Model to pull all the data. This means that my app will need to have two Account Models. A plain one and an eloquent one but only when using the Eloquent driver for the backend.

Looking at this second example I really fell that one can create (not sure how that will work) an Eloquent Model directly as a Repository. class AccountEloquentRepository extends \Illuminate\Database\Eloquent\Model implements AccountRepositoryInterface and inside there we implement all the logic we need. Not sure how this will play out and maybe we still need to have two models just of how Eloquent works but I still think this feels a cleaner approach then the first one when all our repositories are highly coupled with Eloquent.

interface AccountRepositoryInterface
{
    public function findById($id): \App\PlainModel;

    public function all(): \App\PlainCollection;
}

class AccountEloquentRepository implements AccountRepositoryInterface
{
    public function findById($id): \App\PlainModel
    {
        /** @var \Illuminate\Database\Eloquent\Model $account */
        $account = EloquentAccount::find($id);

        return new Account($account->toArray());
    }

    public function all(): \App\PlainCollection
    {
        /** @var \Illuminate\Database\Eloquent\Collection $accounts */
        $accounts = EloquentAccount::all();

        return new PlainCollection($accounts->toArray());
    }
}

class AccountRedisRepository implements AccountRepositoryInterface
{
    public function findById($id): \App\PlainModel
    {
        $redisAccount = json_decode(Redis::get("account-{$id}"), true);

        return new Account($redisAccount);
    }

    public function all(): \App\PlainCollection
    {
        $redisAccounts = json_decode(Redis::hGetAll('account'), true);
        $accounts = array_map(function ($account) { return new Account($account); }, $redisAccounts);

        return new \App\PlainCollection($accounts);
    }
}

class AccountController extends Controller
{
    protected $accountRepository;

    public function __construct(AccountRepositoryInterface $accountRepository)
    {
        $this->accountRepository = $accountRepository;
    }

    public function show($id): \App\PlainModel
    {
        return $this->accountRepository->findById($id);
    }

    public function index(): \App\PlainCollection
    {
        return $this->accountRepository->all();
    }
}

My question in the end is: if we write repositories but have them highly coupled to Eloquent are we gaining anything except for cleaner controllers and more testable code?

Previous

Please or to participate in this conversation.