【问题标题】:Repository and Data Mapper Coupling存储库和数据映射器耦合
【发布时间】:2018-07-26 02:53:15
【问题描述】:

我有一个控制器,它获取数据以传递给视图。向其中注入(通过 pimple 容器)一个服务,该服务使用许多域模型 + 业务逻辑来创建数据。

服务本身有一个“存储库”类注入其中,该类具有创建数据映射器和返回域模型实例的方法。

我知道我可能没有理解存储库的概念,因为Martin Fowler 将其称为“在映射层上构建另一层抽象”和“存储库在域和数据映射层之间进行调解,就像内存中的域对象集合一样。”所以我可能用错了这个词。

服务:

class InputService
    {
        private $repos;

        public function __construct($repo) {
            $this->repos = $repo;
        }

        public function getInitialData()
        {
            $product = $this->repo->getProduct();
            $country = $this->repo->getCountry();
            $spinalPoint = $this->repo->getPoint();

            /*business logic with model instances to produce data array*/

            return //array of data
        }
    }

存储库:

class InputRepository
    {
        private $db;

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

        public function getCountry()
        {
            $mapper = new CountryMapper($this->db);
            $country = $mapper->fetch();
            return $country; //returns country object
        }
        // lots of other methods for returning different model objects
    }

映射器:

class CountryMapper
    {
        private $db;

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

        public function fetch()
        {
            $data = //code to grab data from db;

            $obj = new Country($data);
            return $obj;
        }
    }

如您所见,映射器与存储库类紧密耦合,但我看不到绕过它的方法。

我想知道是否有一种方法可以实现这个存储库,为数据映射器类提供更松散的耦合?

总体而言,这个应用程序相当小,因此必须在两者之间更新代码不会是灾难性的,但你永远不会现在事情会发展!

【问题讨论】:

    标签: php model-view-controller repository-pattern datamapper decoupling


    【解决方案1】:

    您可以在构造函数中注入类名或实例:

    class InputRepository
    {
        private $db;
        protected $mappers = array();
    
        public function __construct($db, array $mappers) {
            $this->db = $db;
            $this->mappers = $mappers;
        }
    
        public function getMapper($key) {
            if (!isset($this->mappers[$key]) {
               throw new Exception('Invalid mapper "'. $key .'"');
            }
    
            if (!$this->mappers[$key] instanceof MapperInterface) {
               $this->mappers[$key] = new $this->mappers[$key]($this->db);
            }
    
            return $this->mappers[$key];
        }
    
        public function getCountry()
        {
            $mapper = $this->getMapper('country');
            $country = $mapper->fetch();
            return $country; //returns country object
        }
        // lots of other methods for returning different model objects
    }
    

    显然,您可能希望使接口检查更加健壮。

    【讨论】:

      【解决方案2】:
      • db 操作应通过适配器(MySqliAdapter、PdoAdapter 等)执行。因此,数据库连接被注入适配器,而不是映射器。当然不在存储库中,因为那样存储库的抽象目的将毫无意义。
      • 映射器接收适配器作为依赖项,也可以接收其他映射器。
      • 映射器作为依赖项传递给存储库。
      • 存储库名称在语义上与域层名称相关,而不是真正与服务层名称相关。例如:“输入服务”:好的。 “InputRepository”:错误。 “CountryRepository”:正确。
      • 服务可以接收更多的存储库。或映射器,如果您不想应用额外的存储库层。
      • 在代码中,唯一紧密耦合的结构是 Country 对象(实体或域对象) - 为每个提取的表行动态创建。甚至可以通过使用域对象工厂来避免这种情况,但我个人认为没有必要这样做。

      P.S:很抱歉没有提供更多文档化的代码。

      服务

      class InputService {
      
          private $countryRepository;
          private $productRepository;
      
          public function __construct(CountryRepositoryInterface $countryRepository, ProductRepositoryInterface $productRepository) {
              $this->countryRepository = $countryRepository;
              $this->productRepository = $productRepository;
          }
      
          public function getInitialData() {
              $products = $this->productRepository->findAll();
              $country = $this->countryRepository->findByName('England');
      
              //...
      
              return // resulted data
          }
      
      }
      

      存储库

      class CountryRepository implements CountryRepositoryInterface {
      
          private $countryMapper;
      
          public function __construct(CountryMapperInterface $countryMapper) {
              $this->countryMapper = $countryMapper;
          }
      
          public function findByPrefix($prefix) {
              return $this->countryMapper->find(['prefix' => $prefix]);
          }
      
          public function findByName($name) {
              return $this->countryMapper->find(['name' => $name]);
          }
      
          public function findAll() {
              return $this->countryMapper->find();
          }
      
          public function store(CountryInterface $country) {
              return $this->countryMapper->save($country);
          }
      
          public function remove(CountryInterface $country) {
              return $this->countryMapper->delete($country);
          }
      
      }
      

      数据映射器

      class CountryMapper implements CountryMapperInterface {
      
          private $adapter;
          private $countryCollection;
      
          public function __construct(AdapterInterface $adapter, CountryCollectionInterface $countryCollection) {
              $this->adapter = $adapter;
              $this->countryCollection = $countryCollection;
          }
      
          public function find(array $filter = [], $one = FALSE) {
              // If $one is TRUE then add limit to sql statement, or so...
              $rows = $this->adapter->find($sql, $bindings);
      
              // If $one is TRUE return a domain object, else a domain objects list.
              if ($one) {
                  return $this->createCountry($row[0]);
              }
      
              return $this->createCountryCollection($rows);
          }
      
          public function save(CountryInterface $country) {
              if (NULL === $country->id) {
                  // Build the INSERT statement and the bindings array...
                  $this->adapter->insert($sql, $bindings);
      
                  $lastInsertId = $this->adapter->getLastInsertId();
      
                  return $this->find(['id' => $lastInsertId], true);
              }
      
              // Build the UPDATE statement and the bindings array...
              $this->adapter->update($sql, $bindings);
      
              return $this->find(['id' => $country->id], true);
          }
      
          public function delete(CountryInterface $country) {
              $sql = 'DELETE FROM countries WHERE id=:id';
              $bindings = [':id' => $country->id];
      
              $rowCount = $this->adapter->delete($sql, $bindings);
      
              return $rowCount > 0;
          }
      
          // Create a Country (domain object) from row.
          public function createCountry(array $row = []) {
              $country = new Country();
      
              /*
               * Iterate through the row items.
               * Assign a property to Country object for each item's name/value.
               */
      
              return $country;
          }
      
          // Create a Country[] list from rows list.
          public function createCountryCollection(array $rows) {
              /*
               * Iterate through rows.
               * Create a Country object for each row, with column names/values as properties.
               * Push Country object object to collection.
               * Return collection's content.
               */
      
              return $this->countryCollection->all();
          }
      
      }
      

      数据库适配器

      class PdoAdapter implements AdapterInterface {
      
          private $connection;
      
          public function __construct(PDO $connection) {
              $this->connection = $connection;
          }
      
          public function find(string $sql, array $bindings = [], int $fetchMode = PDO::FETCH_ASSOC, $fetchArgument = NULL, array $fetchConstructorArguments = []) {
              $statement = $this->connection->prepare($sql);
              $statement->execute($bindings);
              return $statement->fetchAll($fetchMode, $fetchArgument, $fetchConstructorArguments);
          }
      
          //...
      }
      

      域对象集合

      class CountryCollection implements CountryCollectionInterface {
      
          private $countries = [];
      
          public function push(CountryInterface $country) {
              $this->countries[] = $country;
              return $this;
          }
      
          public function all() {
              return $this->countries;
          }
      
          public function getIterator() {
              return new ArrayIterator($this->countries);
          }
      
          //...
      }
      

      域对象

      class Country implements CountryInterface {
          // Business logic: properties and methods...
      }
      

      【讨论】:

      • 非常感谢您的详细回复!我理解您将存储库链接到单个域层名称的意思 - 我创建存储库的原因是动态实例化请求的对象,否则它将涉及向服务中注入 10 多个映射器,其中只有 1 或 3将在下一个 ajax 请求之前使用。因此,如果我删除存储库并让服务仅与映射器打交道,我是否应该每次都注入所有 10 个,而它们自己每个都注入了 db 适配器?
      • @Gothic_Anatomist 欢迎您。通过命名存储库,我想指出您应该寻找命名它们的“方向”。例如,由于它们抽象了数据映射器操作,它们应该由映射器命名——也就是说,不是由使用 te repos 的服务命名。因为一个 repo 可以在多个服务中使用...另一方面,您可以有一个服务处理 SearchRepository,分别是一个 SearchMapper,它可以获取一个集合SearchModel 实体。
      • @Gothic_Anatomist 每个实体将根据您在客户端所做的选择和标准,保存具有来自多个表的值的属性(产品、国家等)。 SearchMapper 将有 find 方法接收条件数组并以 SearchModel 实体数组的形式返回结果,每个实体都保存从 db 获取的记录的值。
      • @Gothic_Anatomist 因此,原则上,当您填充组合框时,您可以使用带有特定 repos/mappers/entities 的 InputService。但是,当您发布用户选择的条件时,您正在使用另一种服务,即 SearchService 与一个 repo/mapper/entity。
      • @Gothic_Anatomist Here 是组件的详细透视图。并阅读 the 4 or 5 articles 关于域对象、数据映射器、存储库和服务的内容。再见。
      猜你喜欢
      • 2017-04-07
      • 1970-01-01
      • 2021-11-17
      • 1970-01-01
      • 2015-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多