存储库和数据映射器耦合
Repository and Data Mapper Coupling
我有一个控制器获取数据以传递给视图。向其中注入(通过 pimple 容器)一项服务,该服务使用多个域模型 + 业务逻辑来创建数据。
服务本身有一个 'repository' class 注入其中,它有创建数据映射器和返回域模型实例的方法。
我知道我可能没有理解存储库的概念,因为 Martin Fowler 把它放在 "build another layer of abstraction over the mapping layer" & "A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection." 所以我可能错误地使用了这个术语。
服务:
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;
}
}
如您所见,映射器与存储库紧密耦合 class,但我看不出解决它的方法。
我想知道是否有一种方法可以实现此存储库,从而提供与数据映射器 classes 更松散的耦合?
在宏伟的计划中,此应用程序相当小,因此必须更新两者的代码不会造成灾难性的后果,但您永远不会知道事情何时会发展壮大!
您可以在构造函数中注入 class 个名称或实例:
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
}
显然,您可能希望使接口检查更加可靠。
- 数据库操作应通过适配器(MySqliAdapter、PdoAdapter 等)执行。因此,数据库连接被注入适配器,而不是映射器。当然不在存储库中,因为那样存储库的抽象目的将毫无意义。
- 映射器接收适配器作为依赖项,也可以接收其他映射器。
- 映射器作为依赖项传递给存储库。
- 存储库名称在语义上与域层名称相关,而不是真正与服务层名称相关。例如:"InputService":好的。 "InputRepository":错了。 "CountryRepository":正确。
- 一个服务可以接收更多的存储库。或者映射器,如果你不想应用额外的存储库层。
- 在代码中,唯一紧密耦合的结构是 Country 对象(实体或域对象)- 为每个获取的 table 行动态创建。即使这可以通过使用域对象工厂来避免,但我个人认为没有必要。
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...
}
我有一个控制器获取数据以传递给视图。向其中注入(通过 pimple 容器)一项服务,该服务使用多个域模型 + 业务逻辑来创建数据。
服务本身有一个 'repository' class 注入其中,它有创建数据映射器和返回域模型实例的方法。
我知道我可能没有理解存储库的概念,因为 Martin Fowler 把它放在 "build another layer of abstraction over the mapping layer" & "A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection." 所以我可能错误地使用了这个术语。
服务:
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;
}
}
如您所见,映射器与存储库紧密耦合 class,但我看不出解决它的方法。
我想知道是否有一种方法可以实现此存储库,从而提供与数据映射器 classes 更松散的耦合?
在宏伟的计划中,此应用程序相当小,因此必须更新两者的代码不会造成灾难性的后果,但您永远不会知道事情何时会发展壮大!
您可以在构造函数中注入 class 个名称或实例:
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
}
显然,您可能希望使接口检查更加可靠。
- 数据库操作应通过适配器(MySqliAdapter、PdoAdapter 等)执行。因此,数据库连接被注入适配器,而不是映射器。当然不在存储库中,因为那样存储库的抽象目的将毫无意义。
- 映射器接收适配器作为依赖项,也可以接收其他映射器。
- 映射器作为依赖项传递给存储库。
- 存储库名称在语义上与域层名称相关,而不是真正与服务层名称相关。例如:"InputService":好的。 "InputRepository":错了。 "CountryRepository":正确。
- 一个服务可以接收更多的存储库。或者映射器,如果你不想应用额外的存储库层。
- 在代码中,唯一紧密耦合的结构是 Country 对象(实体或域对象)- 为每个获取的 table 行动态创建。即使这可以通过使用域对象工厂来避免,但我个人认为没有必要。
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...
}