【问题标题】:Using models in a controller alongside with repository在控制器中使用模型和存储库
【发布时间】:2020-10-18 06:38:31
【问题描述】:

据我了解,使用存储库会限制控制器访问数据库层,并且所有查询都通过存储库进行。但是控制器可以使用模型(laravel 可以在控制器中注入模型而不是 ID)将其传递给存储库或服务 - 例如在用户之间进行事务?或者最好将 ID 发送到存储库,以查找用户并应用业务逻辑(用户是否有钱,或者他是否被禁止)。

还有更一般的问题,你能在存储库之外使用模型吗,因为如果你将一些表从 postgres 或 mysql 更改为其他东西,你的模型也会改变。这意味着您的存储库应该有 get 方法来发回一些 DTO 对象?

【问题讨论】:

    标签: php database laravel model-view-controller repository


    【解决方案1】:

    这是一个自以为是的答案,但这是我的看法。我建议不要为了在 Laravel 中拥有存储库而添加存储库层。无论您需要什么方法,都将它们添加到模型类中,当它们膨胀/预计它会膨胀时,只需考虑存储库(很可能您需要一个服务类或其他一些抽象)。

    由于所有这些雄辩的模型类都可以从容器中解析出来,因此它们易于使用。它可以在任何地方访问,甚至可以在您提到的控制器中进行注入,这提供了极大的便利。

    并且存储库有助于更改例如底层数据库,但 eloquent 已经为我们提供了这种灵活性。而且当您计划更改数据库时,我认为这不会是一个简单的更改,所以为什么将逻辑包装在另一层抽象中(不必要地)。

    至少根据我的经验,存储库模式不能很好地与 Active Record 模式配合使用。 Laravel 紧随其后。存储库非常适合数据映射器模式(例如 Symfony 使用它)。这就是为什么在 laravel 文档中您看不到它们采用存储库模式的原因。您可以在 symfony 文档中看到它。

    所以我建议拥抱框架而不是反对它

    【讨论】:

    • 没想到,谢谢解答!
    【解决方案2】:

    注意:这是对此事的一般看法,适用于任何基于 MVC 的应用程序,不仅适用于 Laravel。

    一个基于MVC模式的应用应该由三部分组成:

    • 交付机制:UI 逻辑(用户请求处理和服务器响应创建),
    • 服务层:应用逻辑,
    • 领域模型:业务逻辑。

    以下是一些图形表示(我自己制作的):

    如上所示(并在下面的资源中详细描述),控制器视图交付机制的一部分。他们应该仅通过 service layer 对象(服务)与 domain model 交互。因此,他们不应该了解域模型组件(实体 - 也称为 域对象data mappersrepositories 等)。更重要的是,控制器应该只有一项职责:将用户请求的值传递给服务层,以便它更新模型。

    所以,回答你的第一个问题:不,控制器不应该能够创建域模型元素的任何实例(所以你称之为“模型”的实例 - 尊重Laravel 的Active Record),甚至可以将这些对象传递给其他组件(如存储库、服务等)。相反,控制器应该只将请求的值(例如 user id)传递给相应的服务。然后,这些服务将创建适当的域模型对象并使用适当的存储库、数据映射器等,以便在数据库中保存/获取。

    至于 第二个问题(如果我理解正确的话):存储库将被视为 entities 的集合 - 它们是域模型组件。因此,可以从元素(例如实体实例)中获取、存储、更改或删除元素。因此,根据定义,entities 必须与存储库分开定义/使用。对于 Laravel,同样适用:“模型”应该与存储库分开定义/使用。

    “通用”MVC 实现(为了更清晰):

    控制器:

    <?php
    
    namespace MyApp\UI\Web\Controller\Users;
    
    use MyApp\Domain\Service\Users;
    use Psr\Http\Message\ServerRequestInterface;
    
    /**
     * Add a user.
     */
    class AddUser {
    
        /**
         * User service.
         * 
         * @var Users
         */
        private $userService;
    
        /**
         * 
         * @param Users $userService User service.
         */
        public function __construct(Users $userService) {
            $this->userService = $userService;
        }
    
        /**
         * Invoke.
         * 
         * @param ServerRequestInterface $request Request.
         * @return void
         */
        public function __invoke(ServerRequestInterface $request) {
            // Read request values.
            $username = $request->getParsedBody()['username'];
    
            // Call the corresponding service.
            $this->userService->addUser($username);
        }
    
    }
    

    服务:

    <?php
    
    namespace MyApp\Domain\Service;
    
    use MyApp\Domain\Model\User\User;
    use MyApp\Domain\Model\User\UserCollection;
    use MyApp\Domain\Service\Exception\UserExists;
    
    /**
     * Service for handling the users.
     */
    class Users {
    
        /**
         * User collection (a repository).
         * 
         * @var UserCollection
         */
        private $userCollection;
    
        /**
         * 
         * @param UserCollection $userCollection User collection.
         */
        public function __construct(UserCollection $userCollection) {
            $this->userCollection = $userCollection;
        }
    
        /**
         * Find a user by id.
         * 
         * @param int $id User id.
         * @return User|null User.
         */
        public function findUserById(int $id) {
            return $this->userCollection->findUserById($id);
        }
    
        /**
         * Find all users.
         * 
         * @return User[] User list.
         */
        public function findAllUsers() {
            return $this->userCollection->findAllUsers();
        }
    
        /**
         * Add a user.
         * 
         * @param string $username Username.
         * @return User User.
         */
        public function addUser(string $username) {
            $user = $this->createUser($username);
    
            return $this->storeUser($user);
        }
    
        /**
         * Create a user.
         * 
         * @param string $username Username.
         * @return User User.
         */
        private function createUser(string $username) {
            $user = new User();
    
            $user->setUsername($username);
    
            return $user;
        }
    
        /**
         * Store a user.
         * 
         * @param User $user User.
         * @return User User.
         */
        private function storeUser(User $user) {
            if ($this->userCollection->userExists($user)) {
                throw new UserExists('Username "' . $user->getUsername() . '" already used');
            }
    
            return $this->userCollection->storeUser($user);
        }
    
    }
    

    存储库:

    <?php
    
    namespace MyApp\Domain\Infrastructure\Repository\User;
    
    use MyApp\Domain\Model\User\User;
    use MyApp\Domain\Infrastructure\Mapper\User\UserMapper;
    use MyApp\Domain\Model\User\UserCollection as UserCollectionInterface;
    
    /**
     * User collection.
     */
    class UserCollection implements UserCollectionInterface {
    
        /**
         * User mapper (a data mapper).
         * 
         * @var UserMapper
         */
        private $userMapper;
    
        /**
         * 
         * @param UserMapper $userMapper User mapper.
         */
        public function __construct(UserMapper $userMapper) {
            $this->userMapper = $userMapper;
        }
    
        /**
         * Find a user by id.
         * 
         * @param int $id User id.
         * @return User|null User.
         */
        public function findUserById(int $id) {
            return $this->userMapper->fetchUserById($id);
        }
    
        /**
         * Find all users.
         * 
         * @return User[] User list.
         */
        public function findAllUsers() {
            return $this->userMapper->fetchAllUsers();
        }
    
        /**
         * Store a user.
         * 
         * @param User $user User.
         * @return User User.
         */
        public function storeUser(User $user) {
            return $this->userMapper->saveUser($user);
        }
        
        /**
         * Check if the given user exists.
         * 
         * @param User $user User.
         * @return bool True if user exists, false otherwise.
         */
        public function userExists(User $user) {
            return $this->userMapper->userExists($user);
        }
    
    }
    

    实体:

    <?php
    
    namespace MyApp\Domain\Model\User;
    
    /**
     * User.
     */
    class User {
    
        /**
         * Id.
         * 
         * @var int
         */
        private $id;
    
        /**
         * Username.
         * 
         * @var string
         */
        private $username;
    
        /**
         * Get id.
         * 
         * @return int
         */
        public function getId() {
            return $this->id;
        }
    
        /**
         * Set id.
         * 
         * @param int $id Id.
         * @return $this
         */
        public function setId(int $id) {
            $this->id = $id;
            return $this;
        }
    
        /**
         * Get username.
         * 
         * @return string
         */
        public function getUsername() {
            return $this->username;
        }
    
        /**
         * Set username.
         * 
         * @param string $username Username.
         * @return $this
         */
        public function setUsername(string $username) {
            $this->username = $username;
            return $this;
        }
    
    }
    

    数据映射器:

    <?php
    
    namespace MyApp\Domain\Infrastructure\Mapper\User;
    
    use PDO;
    use MyApp\Domain\Model\User\User;
    use MyApp\Domain\Infrastructure\Mapper\User\UserMapper;
    
    /**
     * PDO user mapper.
     */
    class PdoUserMapper implements UserMapper {
    
        /**
         * Database connection.
         * 
         * @var PDO
         */
        private $connection;
    
        /**
         * 
         * @param PDO $connection Database connection.
         */
        public function __construct(PDO $connection) {
            $this->connection = $connection;
        }
    
        /**
         * Fetch a user by id.
         * 
         * Note: PDOStatement::fetch returns FALSE if no record is found.
         * 
         * @param int $id User id.
         * @return User|null User.
         */
        public function fetchUserById(int $id) {
            $sql = 'SELECT * FROM users WHERE id = :id LIMIT 1';
    
            $statement = $this->connection->prepare($sql);
            $statement->execute([
                'id' => $id,
            ]);
    
            $data = $statement->fetch(PDO::FETCH_ASSOC);
    
            return ($data === false) ? null : $this->convertDataToUser($data);
        }
    
        /**
         * Fetch all users.
         * 
         * @return User[] User list.
         */
        public function fetchAllUsers() {
            $sql = 'SELECT * FROM users';
    
            $statement = $this->connection->prepare($sql);
            $statement->execute();
    
            $data = $statement->fetchAll(PDO::FETCH_ASSOC);
    
            return $this->convertDataToUserList($data);
        }
    
        /**
         * Check if a user exists.
         * 
         * Note: PDOStatement::fetch returns FALSE if no record is found.
         * 
         * @param User $user User.
         * @return bool True if the user exists, false otherwise.
         */
        public function userExists(User $user) {
            $sql = 'SELECT COUNT(*) as cnt FROM users WHERE username = :username';
    
            $statement = $this->connection->prepare($sql);
            $statement->execute([
                ':username' => $user->getUsername(),
            ]);
    
            $data = $statement->fetch(PDO::FETCH_ASSOC);
    
            return ($data['cnt'] > 0) ? true : false;
        }
        
        /**
         * Save a user.
         * 
         * @param User $user User.
         * @return User User.
         */
        public function saveUser(User $user) {
            return $this->insertUser($user);
        }
        
        /**
         * Insert a user.
         * 
         * @param User $user User.
         * @return User User.
         */
        private function insertUser(User $user) {
            $sql = 'INSERT INTO users (username) VALUES (:username)';
    
            $statement = $this->connection->prepare($sql);
            $statement->execute([
                ':username' => $user->getUsername(),
            ]);
    
            $user->setId($this->connection->lastInsertId());
    
            return $user;
        }
        
        /**
         * Update a user.
         * 
         * @param User $user User.
         * @return User User.
         */
        private function updateUser(User $user) {
            $sql = 'UPDATE users SET username = :username WHERE id = :id';
    
            $statement = $this->connection->prepare($sql);
            $statement->execute([
                ':username' => $user->getUsername(),
                ':id' => $user->getId(),
            ]);
    
            return $user;
        }
    
        /**
         * Convert the given data to a user.
         * 
         * @param array $data Data.
         * @return User User.
         */
        private function convertDataToUser(array $data) {
            $user = new User();
    
            $user
                    ->setId($data['id'])
                    ->setUsername($data['username'])
            ;
    
            return $user;
        }
    
        /**
         * Convert the given data to a list of users.
         * 
         * @param array $data Data.
         * @return User[] User list.
         */
        private function convertDataToUserList(array $data) {
            $userList = [];
    
            foreach ($data as $item) {
                $userList[] = $this->convertDataToUser($item);
            }
    
            return $userList;
        }
    
    }
    

    查看:

    <?php
    
    namespace MyApp\UI\Web\View\Users;
    
    use MyApp\UI\Web\View\View;
    use MyApp\Domain\Service\Users;
    use MyLib\Template\TemplateInterface;
    use Psr\Http\Message\ResponseInterface;
    use Psr\Http\Message\ResponseFactoryInterface;
    
    /**
     * Add a user.
     */
    class AddUser extends View {
    
        /**
         * User service.
         * 
         * @var Users
         */
        private $userService;
    
        /**
         * 
         * @param ResponseFactoryInterface $responseFactory Response factory.
         * @param TemplateInterface $template Template.
         * @param Users $userService User service.
         */
        public function __construct(ResponseFactoryInterface $responseFactory, TemplateInterface $template, Users $userService) {
            parent::__construct($responseFactory, $template);
    
            $this->userService = $userService;
        }
        
        /**
         * Display a form for adding a user.
         * 
         * @return ResponseInterface Response.
         */
        public function index() {
            $body = $this->template->render('@Template/Users/add-user.html.twig', [
                'activeMainMenuItem' => 'addUser',
                'action' => '',
            ]);
            
            $response = $this->responseFactory->createResponse();
            $response->getBody()->write($body);
    
            return $response;
        }
    
        /**
         * Add a user.
         * 
         * @return ResponseInterface Response.
         */
        public function addUser() {
            $body = $this->template->render('@Template/Users/add-user.html.twig', [
                'activeMainMenuItem' => 'addUser',
                'message' => 'User successfully added.',
            ]);
    
            $response = $this->responseFactory->createResponse();
            $response->getBody()->write($body);
    
            return $response;
        }
    
    }
    

    资源:

    【讨论】:

    • 据我了解,您的示例更像是一种 Symfony 方法,您声明了几乎所有层,但在 Laravel 中,其中很多层都是开箱即用的?
    • @Stazis 可能是这样,但不是有意的。通过应用 Active Record 模式,Laravel 似乎在“模型”中合并了实体和数据映射器/存储库功能。而 Symfony 将它们定义为独立的组件。
    • @Stazis 的问题是“准备好”对于所有意图和目的也意味着“一成不变”。这是快速原型设计框架中的主要问题之一。
    • @dakis 你确定要在映射器中放置new User 吗?你以这种方式失去了多态性。此外,恕我直言,存储库确实需要一个内部 identity map,否则您也可以将所有逻辑留在服务中,而不是添加另一个抽象层。
    • @tereško 感谢您的两个建议。通过失去多态性,你到底是什么意思?也许,用户对象不能被子类实例(LSP)替换?在您看来,应该在哪里创建实体 - 服务或存储库(假设每个存储库也有一个内部身份映射)?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-01
    • 1970-01-01
    • 2021-07-31
    • 2011-11-07
    • 2014-08-24
    • 1970-01-01
    相关资源
    最近更新 更多