eloquent 如何与服务层解耦?

How to decouple eloquent from the service layer?

我正在尝试创建一个清晰的服务层,服务层作用于一个或多个存储库,每个存储库作用于它自己的 eloquent 模型。

例如,我可能有:

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

每个服务都通过 ioc 定义其所需的依赖项。所以,像这样:

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

我的存储库是通过它们在各自服务提供商中的绑定来解析的,例如:

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

一切正常。每个服务都会获得它需要的存储库。

为了使服务层远离对 eloquent 的任何特定依赖,任何离开 repo 的东西都是一个简单的、不可变的数据对象。

日常用语要点:

但是 我无法想出一个清晰的模式来 associate eloquent 在服务或回购层相互建模。

鉴于 Post 模型具有 belongsTo(User::class) 关系,我如何在 Post 存储库层干净地创建该关系。

我试过:

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

但是 associate 需要一个 user eloquent 对象,而不仅仅是一个 id。我能做到:

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

但我觉得我正在将一个 eloquent 模型呈现到一个不应该对其进行操作的回购中。

我过去所做的让我对这种情况保持理智的做法是做类似于您在第二个 associate 方法中所做的事情,并在存储库前加上前缀 Eloquent 所以如果我使用 Eloquent 以外的东西,我只是创建一个新的存储库实现。

所以在这种情况下,我会得到 class EloquentUserRepository implements UserInterface。我通常最终会得到一些 public 方法,这些方法只接受 return 原语,可能还有一些私有方法会耦合到 Eloquent 所以我最终做的就是放弃那些 public 方法转换为 AbstractUserRepository,或者如果更有意义的话,转换为特征,以保持代码干燥。

这真的取决于情况,我对这些操作以及我的存储库有很多想法。

我的建议是不要使用 "associate" 函数,你可以简单地做:

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

** 当然,你需要确保具有该 ID 的用户存在。

A) 您可以在室外进行 "associatingUser" 的特殊服务 B) 你可以像使用 UserRepositoryInterface 那样做, 我认为将接口添加为依赖项没有问题。

选项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);
}

}

选项B(完全相同,代码只是位置不同)

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);
}

}

简单的方法:

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

    $post->author_id = $authorId;
}

现在,上面的内容意味着您知道关系的外键 author_id。为了抽象一点,使用这个:

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

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

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

请注意,您仍然需要 save $post 模型,但我想您已经知道了。


根据您使用的 简单、不可变的数据对象 的实现,您还可以允许传递对象而不是原始 ID。字里行间:

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

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

    // ...
}

保湿!

我假设在 post 服务中调用 findEloquent 的另一个原因似乎令人作呕,因为您可能已经在控制器中检索到该数据。简而言之,您可以访问 Eloquent 用于将原始查询结果转换为功能齐全的模型的相同方法。

$userData = array(
    // simple, immutable data
);

$userCollection = User::hydrate(array($userData));

$userModel = $userCollection->first();

我认为您实际上需要一个额外的层,我称之为管理器。这将包含所有业务逻辑并且仅适用于接口。在引擎盖下,它将调用服务(每个服务都知道使用特定的 resource/model)