How to separate eloquence from the level of service?

I am trying to create a clean service level in which the service level acts on one or more repositories, and each repository operates on its own eloquent model.

For example, I might have:

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

Each service defines the necessary dependencies through ioc . So something like:

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

My repositories are resolved through 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'); } } 

It all works great. Each service receives the required repositories.

To maintain a level of service without any particular dependence on eloquence, everything that a repo leaves is a simple, immutable data object.

Key points in everyday language:

  • Only repos communicate with their own models directly
  • Repo returns simple, immutable data objects
  • Services act to link multiple repos together and present simplified objects back to controllers and, ultimately, views.

However, I can’t come up with a clean template for associate eloquent models to each other at the service or repo level.

Given that the Post model has a belongsTo(User::class) relation, how can I cleanly create this relation at the Post repository level.

I tried:

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

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

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

But I feel that I am popping up an eloquent model in a repo that should not act on it.

+8
eloquent repository-pattern laravel service-layer
source share
5 answers

A simple way:

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

Now from the above it follows that you know the foreign key author_id relationship. To distract him a bit, use this:

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

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


Depending on the implementation of the simple, immutable data object that you use, you can also pass objects in place of the original identifiers. Something between the lines:

 public function assignToAuthor($postId, $authorId) { if ($postId instanceof YourDataOject) { $postId = $postId->getId(); } if ($authorId instanceof YourDataOject) { $authorId = $authorId->getId(); } // ... } 
+2
source share

What I did in the past that brought me some sanity in this situation was similar to what you do in the second associate method and the Eloquent repository prefix, so in case I use something other than Eloquent , I just create a new implementation repository.

So, in this case, I get the class EloquentUserRepository implements UserInterface . I usually get some public methods that only primitives take and return, and maybe some private methods that will be associated with Eloquent, so what I end up doing is drop those public methods in AbstractUserRepository or as sign if he does more to save the DRY code.

+2
source share

It really depends on the situation, I had a lot of thoughts about these actions, as well as about my repositories.

What I would suggest is simply not to use the associate function, you can simply do:

 $post->user_id = $userID; $post->save(); 

** Of course, you need to make sure that a user with this identifier exists.

A) You can do it outside using the special service for "associationUser" B) You can do it the same way using UserRepositoryInterface. I don’t see any problems adding the interface as a dependency.

Option A:

 class AssociateUserToPost { private $userRepo; private $postRepo; public function __construct(UserRepoInterface $userRepo, PostRepoInterface $postRepo) { $this->userRepo = $userRepo; $this->postRepo = $postRepo; } public function associate($userId, $postId) { $user = $this->userRepo->getUser($userId); if ( ! $user ) throw new UserNotExistException(); $post = $this->postRepo->getPost($postId); if ( ! $post ) throw new PostNotExistException(); $this->postRepo->AttachUserToPost($postId, $userId); } } 

option B (exactly the same code is just located in different places)

 class PostRepository implements PostRepoInterface { private $userRepo; public function __construct(UserRepoInterface $userRepo) { $this->userRepo = $userRepo; } public function associate($userId, $postId) { $user = $this->userRepo->getUser($userId); if ( ! $user ) throw new UserNotExistException(); $post = $this->getPost($postId); if ( ! $post ) throw new PostNotExistException(); $this->AttachUserToPost($postId, $userId); } } 
+2
source share

Humidification!

I assume that the other reason calling findEloquent in the mail service seems bad, because you may have already received this data in the controller. Simply put, you can access the same method that Eloquent uses to convert raw query results into fully functioning models.

 $userData = array( // simple, immutable data ); $userCollection = User::hydrate(array($userData)); $userModel = $userCollection->first(); 
0
source share

I think you really need an extra layer, this is what I call a manager. This will contain all the business logic and will only work with interfaces. Under the hood, it will call services (each of which knows that it is necessary to work with a specific resource / model)

0
source share

All Articles