Связь репозитория и отображения данных

У меня есть контроллер, который собирает данные для передачи в представление. В него вводится (через контейнер прыщиков) сервис, который использует ряд моделей предметной области + бизнес-логику для создания данных.

Сама служба имеет внедренный в нее класс «репозиторий», в котором есть методы для создания сопоставителей данных и возврата экземпляра модели предметной области.

Я понимаю, что, возможно, я не имел представления о концепции репозитория, как указывает Мартин Фаулер он предназначен для «создания еще одного уровня абстракции над уровнем сопоставления» и «Репозиторий является посредником между уровнями сопоставления домена и данных, действуя как коллекция объектов домена в памяти». Так что я могу использовать этот термин ошибочно.

услуга:

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

Как видите, картографы тесно связаны с классом репозитория, однако я не вижу способа обойти это.

Мне было интересно, есть ли способ реализовать этот репозиторий, который обеспечивает более слабую связь с классами сопоставления данных?

По большому счету, это приложение довольно мало, поэтому необходимость обновления кода для обоих не будет катастрофой, но вы никогда не будете сейчас, когда оно будет расти!


person Gothic_Anatomist    schedule 15.02.2018    source источник


Ответы (2)


  • Операции с БД должны выполняться через адаптеры (MySqliAdapter, PdoAdapter и т. Д.). Таким образом, подключения к базе данных вводятся в адаптеры, а не в сопоставители. И уж точно не в репозиториях, потому что тогда абстрагирование репозиториев было бы бессмысленным.
  • Картограф получает адаптер (ы) как зависимости, а также может получать другие картографы.
  • Картографы передаются репозиториям как зависимости.
  • Имя репозитория семантически связано с именами уровня домена, а не с именами уровня сервиса. Например: "InputService": ок. "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();
    }

}

Адаптер db

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...
}
person Community    schedule 16.02.2018
comment
Большое спасибо за подробный ответ! Я понимаю, что вы имеете в виду, говоря о том, что репозиторий связан с одним именем уровня домена - причина, по которой я вообще создал репо, заключалась в том, чтобы динамически создать экземпляр запрошенного объекта, поскольку в противном случае это потребовало бы внедрения более 10 сопоставителей в службу, где только 1 или 3 будет использоваться перед следующим запросом ajax. Итак, если я отбрасываю репозиторий и выполняю обслуживание исключительно с картографами, должен ли я просто вводить все 10 каждый раз, каждый из которых вводится с адаптером db? - person Gothic_Anatomist; 16.02.2018
comment
@Gothic_Anatomist Добро пожаловать. Называя репозитории, я хотел указать направление, в котором вы должны искать их. Например, поскольку они абстрагируются от операций сопоставления данных, они должны называться сопоставителями - так сказать, не службами, которые используют репозитории te. Поскольку репо может использоваться в нескольких сервисах ... С другой стороны, у вас может быть сервис, имеющий дело с SearchRepository, соответственно SearchMapper, который может получать коллекцию SearchModel сущности. - person ; 16.02.2018
comment
@Gothic_Anatomist Каждая сущность будет содержать свойства (продукт, страна и т. Д.) Со значениями из нескольких таблиц, основанных на выборе и критериях, которые вы делаете на стороне клиента. SearchMapper будет иметь метод find, получающий массив критериев и возвращающий результаты в форме массива сущностей SearchModel, каждая из которых содержит значения выбранной записи из базы данных. - person ; 16.02.2018
comment
@Gothic_Anatomist Итак, в принципе, когда вы заполняете поля со списком, вы используете InputService с конкретными репозиториями / мапперами / объектами. Но когда вы публикуете критерии, выбранные пользователем, вы используете другую службу, SearchService, с одним репо / картографом / объектом. - person ; 16.02.2018
comment
@Gothic_Anatomist Здесь подробно рассматривается компоненты. И прочтите 4 или 5 статей об объектах домена, средствах сопоставления данных, репозиториях. и услуги. Пока. - person ; 16.02.2018

Вы можете вставить имена классов ИЛИ экземпляры в конструктор:

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
}

Очевидно, вы, вероятно, захотите сделать проверку интерфейса более надежной.

person prodigitalson    schedule 15.02.2018