У меня есть код, например, так:
/* Part of Controller::saveAction() */
//create new object instance
$item = new Item();
//populate the Item
$item->setDescription($description);
$item->setQuantity($quantity);
$item->setPrice($price);
//once we have a fully populated object,
//send it to Repository pattern,
//which saves it to persistent storage
$this->repository->saveItem($item);
Представьте себе несколько блоков, как указано выше, в одном методе / методе, и вы увидите мою проблему … Я очень доволен своим repository
строка кода, но я не знаю, где разместить всю «подготовительную работу», которая выполняется до вызова Repository
,
Вопрос:
Где я могу разместить большую часть кода, который создает &заселяет Item
экземпляр объекта? Это загромождает мой метод Контроллера, и я не могу представить себе другое место для его установки.
Цель
Моя цель — найти хорошую структуру / дизайн, а не просто уменьшить или свести к минимуму количество строк «подготовки предмета».
В зависимости от контроллера у меня примерно 5-7 Item
экземпляры, каждый из которых имеет 10-16 строк кода, создающих и заполняющих экземпляры.
наблюдение
Индивидуальные ценности, такие как $description, $quantity, $price
должен прийти откуда-то. Это может быть GET, POST, SESSION, COOKIES, база данных или внешние методы. Давайте назовем это $_SOMEWHERE
для ясности. Затем мы получаем:
$description = $_SOMEWHERE['description'];
$quantity = $_SOMEWHERE['quantity'];
$price = $_SOMEWHERE['price'];
Определить вход&Подготовительный класс, который делает работу за вас и возвращает подготовленный Item
,
class AcquireItem
{
function getItem()
{
$item = new Item();
$item->setDescription($_SOMEWHERE['description']);
$item->setQuantity($_SOMEWHERE['quantity']);
$item->setPrice($_SOMEWHERE['price']);
return $item;
}
}
контроллер
$item = (new AcquireItem())->getItem();
$this->repository->saveItem($item);
Контроллер становится короче, эффективно «запихивая» громоздкий код беспорядка в класс, который занимается чтением и подготовкой ввода, а также очисткой контроллера. Код должен где-то существовать, но может быть и вне поля зрения, и в другом месте.
Для разных типов Item
Вы можете варьировать метод, т.е. getItemA()
, getItemB()
,
Как вы упомянули о правильном способе достижения
так .. как насчет использования делегаторов и разбиения контроллера (блоков) на делегаторы? По терминологии это будет Фасад [Адаптер]
добавьте следующее в ./module/MyModule/config/module.config.php:
'controllers' => array(
'invokables' => array(
'MyModule\CreateController' => 'MyModule\Controller\MyController',
'MyModule\ReadController' => 'MyModule\Controller\MyController',
),
'delegators' => array(
'MyModule\CreateController' => array(
'MyModule\Controller\Delegator\CreateItemDelegatorFactory'
),
'MyModule\ReadController' => array(
'MyModule\Controller\Delegator\ReadItemDelegatorFactory'
),
),
),
'form_elements' => array(
'invokables' => array(
'item_create' => 'MyModule\Form\CreateForm',
),
),
Create Delegator загружает форму, заполняет ее, проверяет и пытается сохранить данные
./module/MyModule/src/MyModule/Controller/Delegator/CreateItemDelegatorFactory.php:
namespace MyModule\Controller\Delegator;
use Zend\ServiceManager\DelegatorFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use MyModule\Entity\Item as Entity;
/**
* Class loads the form, checks if form been posted and if is valid.
* If form is valid it tries to save the item with repository service
* Class sets the Form per controller, such solution keeps form
* validation messages
*/
class CreateItemDelegatorFactory implements DelegatorFactoryInterface
{
/**
* Determines name of the form to be loaded with formElementManager
*
* @var string
*/
private $form_name = "item_create";
/**
* Name of repository service. It may be database, api or other
*
* @var string
*/
private $service_repository_name = "service.repository";
public function createDelegatorWithName(
ServiceLocatorInterface $serviceLocator,
$name,
$requestedName,
$callback
) {
// assign serviceManager locally
$parentLocator = $serviceLocator->getServiceLocator();
// assign services locally
$routerService = $parentLocator->get('router');
$requestService = $parentLocator->get('request');
// get repository service
$repositoryService = $parentLocator->get($this->service_repository_name);
// read the CreateForm with formElementManager and bind the Entity
$callback->setForm(
$parentLocator->get('FormElementManager')->get($this->form_name)
);
$entity = new Entity;
$callback->getForm($this->form_name)->bind($entity);
// check if data been posted
if($requestService->isPost()) {
$postData = $requestService->getPost($this->form_name);
$callback->getForm($this->form_name)->setData($postData);
// validate form
if($callback->getForm($this->form_name)->isValid()) {
// form is valid
$repositoryService->saveItem($entity);
}
}
}
}
При использовании вышеупомянутого делегатора вашему контроллеру (MyModule \ Controller \ MyController) потребуется дополнительное свойство и два метода:
/**
* Holds the form object
* @var object
*/
private $form
public function setForm($form=null)
{
$this->form = $form;
return $this;
}
public function getForm()
{
return $this->form;
}
./module/MyModule/src/MyModule/Controller/Delegator/ReadItemDelegatorFactory.php:
namespace MyModule\Controller\Delegator;
use Zend\ServiceManager\DelegatorFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use MyModule\Entity\Item as Entity;
/**
* Creates Delegator which tries read item's id from the (segment type) route
* and read the Item from the repository service
*
*/
class ReadItemDelegatorFactory implements DelegatorFactoryInterface
{
/**
* Item's ID from route
*
* @var string
*/
private $route_identifier = "item_id";
/**
* Name of repository service. It may be database, api or other
*
* @var string
*/
private $service_repository_name = "service.repository";
public function createDelegatorWithName(
ServiceLocatorInterface $serviceLocator,
$name,
$requestedName,
$callback
) {
// assign serviceManager locally
$parentLocator = $serviceLocator->getServiceLocator();
// assign services locally
$routerService = $parentLocator->get('router');
$requestService = $parentLocator->get('request');
// get repository service
$repositoryService = $parentLocator->get($this->service_repository_name);
// get the router match and the item_id
$routerMatch = $routerService->match($requestService);
$itemId = $routerMatch->getParam($this->route_identifier);
// set the data for the target controller
$callback->setItem($repositoryService->readItem($itemId));
return $callback;
}
При использовании вышеупомянутого делегатора вашему контроллеру (MyModule \ Controller \ MyController) потребуются дополнительные свойство и метод:
/**
* Holds the Item object
* @var object \MyModule\Entity\Item
*/
private $item
public function setItem($item=null)
{
$this->item = $item;
return $this;
}
Такой способ использования контроллера помогает коду оставаться СУХИМЫМ и кажется возможным управлять потоком. Делегаторы загружаются как LIFO, поэтому можно предварительно сконфигурировать контроллер ($ callback) перед передачей его другому делегатору.
Если ReadController читает элемент, а CreateController загружает форму, это короткий путь для UpdateItemDelegator для обработки задачи обновления элемента.
'controllers' => array(
'invokables' => array(
'MyModule\UpdateController' => 'MyModule\Controller\MyController',
'delegators' => array(
'MyModule\ReadController' => array(
'MyModule\Controller\Delegator\UpdateItemDelegatorFactory',
'MyModule\Controller\Delegator\CreateItemDelegatorFactory',
'MyModule\Controller\Delegator\ReadItemDelegatorFactory'
),
),
),
Делегаты объяснили:
http://ocramius.github.io/blog/zend-framework-2-delegator-factories-explained/
Редактировать:
Контроллер, подготовленный для обоих делегатов (Create и Read), будет выглядеть так:
namespace MyModule\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class CreateController extends AbstractActionController
{
/**
* Holds the Item object
* @var object \MyModule\Entity\Item
*/
private $item
/**
* Holds the form object
* @var mixed null|object
*/
private $form;
/**
* Item's details. It reads item by the `item_id` which is param set in route with same name
* @return \Zend\View\Model\ViewModel
*/
public function readAction()
{
$v = new ViewModel();
// set item, access it in template as `$this->item`
$v->setVariable('item',$this->getItem());
return $v;
}
/**
* The Form preconfigured with CreateItemDelegatorFactory should be av. in template as `$this->form`
* @return \Zend\View\Model\ViewModel
*/
public function createAction()
{
$v = new ViewModel();
// set form, access the in template as `$this->form`
$v->setVariable('form',$this->getForm());
return $v;
}
/**
* Sets the Item object
* @var $item \MyModule\Entity\Item
* @return $this
*/
public function setItem($item=null)
{
$this->item = $item;
return $this;
}
/**
* Gets the Item object
* @return object \MyModule\Entity\Item
*/
public function getItem()
{
return $this->item;
}
public function setForm($form=null)
{
$this->form = $form;
return $this;
}
/**
* Returns form defined in config and CreateItemDelegatorFactory::form_name
* @return \Zend\Form\Form
*/
public function getForm()
{
return $this->form;
}
}
Я лично считаю, что create-методы должны идти в хранилище.
Это потому, что я ожидал бы, что репозитории будут содержать все методы CRUD (создание, чтение, обновление, удаление).
Это просто моя личная мысль на эту тему …