Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to decouple eloquent from the service layer?

I am attempting to create a clean cut service layer, whereby the service layer acts upon one or more repositories, and each repositories acts on its own eloquent model.

For example, I may have:

ForumService
  |
  +-- PostRepo extends PostInterface
  |     |
  |     +-- Post (Eloquent)
  |
  +-- UserRepo extends UserInterface
        |
        +-- User (Eloquent)

Each service defines it's required dependencies via ioc. So, something like:

// MessageService
// ..
public function __construct(UserInterface $userRepository, 
                            MessageInterface $messageRepository) {
    // ..
}

My repositories are resolved via their bindings in their respective service providers, such as:

class UserRepositoryServiceProvider extends ServiceProvider 
{
    public function register()
    {
        $this->app>bind(
            'App\Models\Repositories\User\UserInterface',
            'App\Models\Repositories\User\UserRepository');
    }
}

This all works just fine. Each service gets the repositories it requires.

To keep the service layer clear of any specific dependency on eloquent, anything that leaves a repo is a simple, immutable, data object.

Key points in everyday language:

  • Only the repo's talk to their own models directly
  • Repo's return simple, immutable, data objects
  • Services act to tie multiple repo's together and present simplified objects back to the controllers, and ultimately the views.

However I can't come up with a clean pattern to associate eloquent models to each other at the service or repo layer.

Given the Post model has a belongsTo(User::class) relationship, how do I cleanly create that relationship at the Post repository layer.

I have tried:

public function associate($authorId) 
{
    $post->author()->associate($authorId);
}

But associate expects a user eloquent object, not just an id. I could do:

public function associate($authorId) 
{
    $post->from()->associate($userRepo->findEloquent($authorId));
}

But I feel like I am surfacing a eloquent model up into a repo that shouldn't be acting on it.

like image 397
Chris Avatar asked May 11 '15 12:05

Chris


2 Answers

The easy way:

public function assignToAuthor($postId, $authorId) 
{
    $post = $this->find($postId); // or whatever method you use to find by id

    $post->author_id = $authorId;
}

Now, the above implies that you know the foreign key author_id of the relation. In order to abstract it just a bit, use this:

public function assignToAuthor($postId, $authorId) 
{
    $post = $this->find($postId);

    $foreignKey = $post->author()->getForeignKey();

    $post->{$foreignKey} = $authorId;
}

Mind, that you still need to save the $post model, but I suppose you already know that.


Depending on your implementation of the simple, immutable, data object that you use, you could also allow passing the objects instead of raw ids. Something between the lines:

public function assignToAuthor($postId, $authorId) 
{
    if ($postId instanceof YourDataOject) {
       $postId = $postId->getId();
    }

    if ($authorId instanceof YourDataOject) {
       $authorId = $authorId->getId();
    }

    // ...
}
like image 175
Jarek Tkaczyk Avatar answered Jan 08 '23 20:01

Jarek Tkaczyk


What I've done in the past that has brought some sanity to this situation for me was do things similar to what you are doing in your second associate method and prefix the repository with Eloquent so in the event I use something besides Eloquent, I just create a new implementation of the repository.

So in this case, I'd end up with class EloquentUserRepository implements UserInterface. I usually end up with some public methods which take and return only primitives and possibly some private methods which would be coupled to Eloquent so what I end up doing then is dropping those public methods into a AbstractUserRepository, or a trait if it makes more sense, to keep the code DRY.

like image 44
user1669496 Avatar answered Jan 08 '23 21:01

user1669496