Кстати, можете еще попробовать вытащить данные (email & full name) по всем клиентам, которые совершили транзакции (sales_payment_transaction, нужны данные из поля tnx_id), которые соответствуют определенным методам платежа (sales_order_payment.method) по заказам, созданным в определенный промежуток времени, используя всю мощь Magento ORM.
Вы немножко неправильно поняли назначение моего кода — он подменяет алиасы для дополнительных столбцов исходного SQL'а для WHERE-правила (да-да, в background'е "Magento ORM" спрятан самый обычный SQL, впрочем, как и в background'е других ORM framework'ов) полным значением имени столбца, с добавлением алиаса таблицы.
просто добавляет в список WHERE-правил еще одно условие. Этот код и так исполняется, когда Magento разбирает условия фильтрации данных грида, заданные пользователем через WebUI (трассировка от \Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool::applyFilters как раз и выведет на метод "addFieldToFilter"). У Magento-коллекции есть метод "addFilterToMap", который позволяет ввести карту преобразований, аналогичных тем, которые делаю я, и выполнять их перед тем, как добавить условие фильтрации (\Magento\Framework\Data\Collection\AbstractDb::_translateCondition, вызывается из addFieldToFilter), вот только нет возможности вклиниться в поток выполнения команд через событие (применение фильтров идет после создания коллекции и до генерации события "core_collection_abstract_load_before"). Можно использовать механизм плагинов и обернуть, например, метод \Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory::getReport, чтобы он выполнял те же самые действия, что и в обсервере, плюс добавлял маппинг.
Я хотел для примера воткнуть хоть какие-то более-менее правдоподобные зависимости в конструктор, чтобы не писать "ISomeService1", ..., "ISomeService4", а получилось, что я нарушил "принцип единственной ответственности".
то все, что касается декорирования, начиная с вашего первого коммента просто повиснет в воздухе, а суть моей статьи не изменится не то, что хоть как-нибудь — она вообще не изменится.
Я помню первоначальный вид статьи и помню ваши замечания, которые привели ее к текущему виду. И за это я вам благодарен. Но давайте мух подавать отдельно от котлет, даже если одно к другому липнет, как декорирование к тестированию.
Какой статьи? "DI, PHPUnit и setUp"? Там есть хоть слово про декораторы или интерфейсы? Каким образом вы связали декораторы и изложенное в статье, по каким ключевым словам? По слову "тестирование"?
А чего вы к декораторам привязались? Статья про тестирование, а не про полиморфизм. Тестирование без конструирования невозможно, а без полиморфизма — вполне. Еще раз, с точки зрения создания объекта очень сильно незначительно важно, сколько у него параметров в конструкторе — 2 или 3.
Весь остальной наш флейм к сути вопроса, освещаемого в статье, имеет весьма малое отношение. Даже еще меньшее, чем имеет конструктор класса и его параметры к имплементируемым классом интерфейсам.
Возможно, я плохо выражаю свои мысли. Если вы посмотрите на мои примеры, то я нигде не указывал, что конструктор соответствует классу, имплементирующему какой-то определенный интерфейс. Интерфейс, который имплементируется, вообще остается за рамками вопроса, рассматриваемого в статье. А если не придумывать лишнего, то как я и сказал, разница между конструктором с 3-мя параметрами, и конструктором с 2-мя параметрами — несущественная.
Я пересел на Magento с Java, на которой имел дело и с Hibernate, и с DataNucleus. То, что в статье говорится "It should be no surprise that Magento takes the ORM approach", не является основанием для заявления, что ORM в Magento присутствует. Для разминки изобразите средствами "Magento ORM" сущность с составным первичным ключом (состоящим из двух полей таблицы в БД), а затем попробуйте средствами "Magento ORM" извлечь коллекцию таких объектов. Для Hibernate и DataNucleus данная задача является тривиальной.
Ваша критика статьи станет конструктивной тогда, когда вы предложите свое решение описанной задачи, а не пронесетесь вихрем по комментам с шашкой наголо "так никто не делает, там делов-то на пару строк кода".
Хотел бы подчеркнуть такой момент — все эти телодвижения приводят к тому, что колонки в грид добавляются независимо. Т.е., разработчика модуля не заботит, какие еще модули будут стоять в приложении, и на какие еще таблицы пойдут JOIN'ы.
Спасибо за коммент. Данная статья задумывалась как краткая сводка доступных способов промежуточного сохранения информации не в БД. Да, имплементаций интерфейса SessionManagerInterface достаточно много (навскидку, штук 10), но полезных рекомендаций по их использованию я дать не могу — я так глубоко не копал. Могу только сказать, что такой механизм существует, что я и сделал (информация действительно сохраняется между запросами, я проверял).
А по регистру — я посмотрел, но не понял, что именно может быть "не так"? Регистр (реестр) позволяет сохранять данные на все время своей жизни (в пределах одного запроса). Через DI доступен практически в любом классе. Если в Magento 2 есть какой-то другой механизм для выполнения таких же задач, было бы интересно о нем узнать. Сообщите, пожалуйста, о нем, и я дополню статью.
По поводу коллеций я уже понял, и я нигде не настаивал на том, что коллекции нужно использовать в коде бизнес-логики :)
Первичный ключ — любой суррогат заменяет оригинал не на все 100%. Я конкатенировал в коллекциях два атрибута в один первичный ключ — для выборки данных это отработало, но манипуляцию (сортировка, фильтрация) я уже копать не стал. Как говорится, каждому овощу свое место, а коллекции — не то место, где можно использовать составные первичные ключи.
Если следовать примеру и вводить BaseRepositoryInrerface, то он может, но не обязательно должен в своих контрактах использовать BaseEntityInterface. Мне моя религия позволяет не указывать типы аргументов и возвращаемого результата, описывая только имена контрактов и набор входных аргументов (кол-во, порядок и соглашение по их наименованию). Моя религия говорит, что договор через код, гораздо более сильный чем договор через документацию, и уж куда более сильный, чем договор на словах. А раз уж PHP позволяет специализировать тип аргумента в производных интерфейсах, то я бы этой возможностью и воспользовался. Я не настаиваю, что это правильно в рамках какой-то другой религии (DDD, например), я просто говорю, что я бы сделал именно так просто потому, что это делает разработку кода с моей точки зрения несколько легче. У разработчиков Magento 2 другая точка зрения, и они действуют по-другому. Я не призываю менять свои религии, если что. Я просто поделился своей точкой зрения.
Спасибо за развернутый ответ, коллега. Насколько я понял, на данный момент альтернативы коллекциям в механизмах фильтрации нет, появится он с версии 2.1, там и посмотреть можно будет, как его использовать.
Что касается составного первичного ключа, то это чисто мои тараканы — я считаю что для описания предметной области хватает простого первичного ключа, а для описания отношений между объектами предметной области простого первичного ключа уже недостаточно. Если та же Доктрина считает по-другому — это ее право.
Общий интерфейс на репозитории? Ну, если API так важен для архитектуры Magento 2, если код бизнес-логики должен быть завязан на Repository Interface, если большинство таких интерфейсов описывают методы для базовой манипуляции сущностями, то с моей точки зрения вполне логично выделить типовой набор методов (save, get, getById, delete, deleteById, getList) и обозвать его как-то типа BaseRepositoryInrerface, наследуя от него все остальные репо-интерфейсы. По крайней мере это было бы отличным маркером для читающих код и нечитающих мануалы, что это связанное подмножество интерфейсов, имеющее весомое значение в архитектуре Magento 2. Плюс, это было удобным примером для объяснения новичкам, что собственно такое "репозиторий сущности (EntityRepositoryInterface) и его метод getList(SearchCriteria $searchCriteria)", не на словах, а в коде. Но разработчики Magento 2 считают по-другому, поэтому такого интерфейса нет.
Для интереса залез в код. Вот интерфейс \Magento\Catalog\Api\ProductRepositoryInterface, в нем описан метод
/**
* Get product list
*
* @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
* @return \Magento\Catalog\Api\Data\ProductSearchResultsInterface
*/
public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria);
Заглядываем в реализацию этого метода \Magento\Catalog\Model\ProductRepository::getList:
public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
{
/** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */
$collection = $this->collectionFactory->create();
$this->extensionAttributesJoinProcessor->process($collection);
foreach ($this->metadataService->getList($this->searchCriteriaBuilder->create())->getItems() as $metadata) {
$collection->addAttributeToSelect($metadata->getAttributeCode());
}
$collection->joinAttribute('status', 'catalog_product/status', 'entity_id', null, 'inner');
$collection->joinAttribute('visibility', 'catalog_product/visibility', 'entity_id', null, 'inner');
//Add filters from root filter group to the collection
foreach ($searchCriteria->getFilterGroups() as $group) {
$this->addFilterGroupToCollection($group, $collection);
}
/** @var SortOrder $sortOrder */
foreach ((array)$searchCriteria->getSortOrders() as $sortOrder) {
$field = $sortOrder->getField();
$collection->addOrder(
$field,
($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC'
);
}
$collection->setCurPage($searchCriteria->getCurrentPage());
$collection->setPageSize($searchCriteria->getPageSize());
$collection->load();
$searchResult = $this->searchResultsFactory->create();
$searchResult->setSearchCriteria($searchCriteria);
$searchResult->setItems($collection->getItems());
$searchResult->setTotalCount($collection->getSize());
return $searchResult;
}
Сразу же бросается в глаза, что, в отношении получения списка записей, "репозиторий" — это всего лишь обертка над той же старой доброй коллекцией со всеми ее достоинствами и недостатками (включая невозможность иметь композитный primary key, которая меня почему-то огорчает больше всего).
Если исполнение "технического долга" со стороны Magento 2 Team сводится к оборачиванию коллекций в код, имплементирующий некий общий EntityRepositoryInterface (которого, кстати, нет, хотя он просто обязан быть, если "разработчики мадженто за этим следят"), то все слова в отношении коллекций, сказанные коллегой Oxidant, верны также и для "обернутых коллекций". Если же в недрах Magento 2 есть "типовая" (или хотя бы "эталонная") имплементация "усредненного" интерфейса EntityRepositoryInterface (save, get, getById, delete, deleteById, getList) без использования коллекций внутри, то был бы весьма признателен, если бы коллега maghamed дал ссылку на эту имплементацию.
Ничего страшного. Вы можете считать, как вам удобнее. Я не настаиваю на том, что диаграмма верна. Просто она коррелирует с моим представлением о прекрасном, и поэтому она здесь.
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
$container = new ContainerBuilder();
// $container->register('time', Time::class);
$container->setDefinition('time', (new Definition())->setFactory(Time::class . '::fromValues'));
// $obj = Time::fromValues(2, 3);
$obj = $container->get('time');
Только warning вылетает, но это уже мелочи на общем фоне :)
PHP Warning: Missing argument 1 for ...\Time::fromValues()
Если дожать еще передачу в DI-фабрику default-параметров для "именованного конструктора", то можно будет снимать свой вопрос по поводу использования в DI-фреймворках объектов с приватным конструктором.
Там чуть выше картинке есть ссылка на статью, где объясняется сама картинка. А привел я ее к тому, что вы решили уточнить, что именно значит "объектов значений". Для наглядности, что я имел в виду под "POJO like" классами (в данной картинке они проходят под именем POCO, т.к. статья дотнетовская). Раз уж совмещать используемые термины, то наглядно.
Кстати, можете еще попробовать вытащить данные (email & full name) по всем клиентам, которые совершили транзакции (sales_payment_transaction, нужны данные из поля tnx_id), которые соответствуют определенным методам платежа (sales_order_payment.method) по заказам, созданным в определенный промежуток времени, используя всю мощь Magento ORM.
Вы немножко неправильно поняли назначение моего кода — он подменяет алиасы для дополнительных столбцов исходного SQL'а для WHERE-правила (да-да, в background'е "Magento ORM" спрятан самый обычный SQL, впрочем, как и в background'е других ORM framework'ов) полным значением имени столбца, с добавлением алиаса таблицы.
В вашем примере код
просто добавляет в список WHERE-правил еще одно условие. Этот код и так исполняется, когда Magento разбирает условия фильтрации данных грида, заданные пользователем через WebUI (трассировка от \Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool::applyFilters как раз и выведет на метод "addFieldToFilter"). У Magento-коллекции есть метод "addFilterToMap", который позволяет ввести карту преобразований, аналогичных тем, которые делаю я, и выполнять их перед тем, как добавить условие фильтрации (\Magento\Framework\Data\Collection\AbstractDb::_translateCondition, вызывается из addFieldToFilter), вот только нет возможности вклиниться в поток выполнения команд через событие (применение фильтров идет после создания коллекции и до генерации события "core_collection_abstract_load_before"). Можно использовать механизм плагинов и обернуть, например, метод \Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory::getReport, чтобы он выполнял те же самые действия, что и в обсервере, плюс добавлял маппинг.
Регистрация around-плагина:
etc/di.xml
Код самого плагина, вызывающий класс-модификатор для добавления JOIN'ов к выборке и маппинг полей для их преобразования в фильтрах:
Код, который модифицирует выбоку аналогичный тому, что в статье. Код для маппинга трививален:
Этот подход позволяет использовать механизмы Magento для замены алиасов истинными именами полей вместо "грязного хака".
На это я уже ответил
Если заменить в моей статье
на
то все, что касается декорирования, начиная с вашего первого коммента просто повиснет в воздухе, а суть моей статьи не изменится не то, что хоть как-нибудь — она вообще не изменится.
Я помню первоначальный вид статьи и помню ваши замечания, которые привели ее к текущему виду. И за это я вам благодарен. Но давайте мух подавать отдельно от котлет, даже если одно к другому липнет, как декорирование к тестированию.
Какой статьи? "DI, PHPUnit и setUp"? Там есть хоть слово про декораторы или интерфейсы? Каким образом вы связали декораторы и изложенное в статье, по каким ключевым словам? По слову "тестирование"?
А чего вы к декораторам привязались? Статья про тестирование, а не про полиморфизм. Тестирование без конструирования невозможно, а без полиморфизма — вполне. Еще раз, с точки зрения создания объекта очень сильно незначительно важно, сколько у него параметров в конструкторе — 2 или 3.
Весь остальной наш флейм к сути вопроса, освещаемого в статье, имеет весьма малое отношение. Даже еще меньшее, чем имеет конструктор класса и его параметры к имплементируемым классом интерфейсам.
Возможно, я плохо выражаю свои мысли. Если вы посмотрите на мои примеры, то я нигде не указывал, что конструктор соответствует классу, имплементирующему какой-то определенный интерфейс. Интерфейс, который имплементируется, вообще остается за рамками вопроса, рассматриваемого в статье. А если не придумывать лишнего, то как я и сказал, разница между конструктором с 3-мя параметрами, и конструктором с 2-мя параметрами — несущественная.
Ну и как будет выглядеть запрос на выборку объектов с составным первичным ключом в Magento ORM? А на обновление объекта?
Я пересел на Magento с Java, на которой имел дело и с Hibernate, и с DataNucleus. То, что в статье говорится "It should be no surprise that Magento takes the ORM approach", не является основанием для заявления, что ORM в Magento присутствует. Для разминки изобразите средствами "Magento ORM" сущность с составным первичным ключом (состоящим из двух полей таблицы в БД), а затем попробуйте средствами "Magento ORM" извлечь коллекцию таких объектов. Для Hibernate и DataNucleus данная задача является тривиальной.
Ваша критика статьи станет конструктивной тогда, когда вы предложите свое решение описанной задачи, а не пронесетесь вихрем по комментам с шашкой наголо "так никто не делает, там делов-то на пару строк кода".
ORM в Magento? Его там нет и никогда не было.
Возможно потому, что Magento — не CMS.
Хотел бы подчеркнуть такой момент — все эти телодвижения приводят к тому, что колонки в грид добавляются независимо. Т.е., разработчика модуля не заботит, какие еще модули будут стоять в приложении, и на какие еще таблицы пойдут JOIN'ы.
Спсибо за пояснения, внесу правки в текст статьи.
Спасибо за коммент. Данная статья задумывалась как краткая сводка доступных способов промежуточного сохранения информации не в БД. Да, имплементаций интерфейса SessionManagerInterface достаточно много (навскидку, штук 10), но полезных рекомендаций по их использованию я дать не могу — я так глубоко не копал. Могу только сказать, что такой механизм существует, что я и сделал (информация действительно сохраняется между запросами, я проверял).
А по регистру — я посмотрел, но не понял, что именно может быть "не так"? Регистр (реестр) позволяет сохранять данные на все время своей жизни (в пределах одного запроса). Через DI доступен практически в любом классе. Если в Magento 2 есть какой-то другой механизм для выполнения таких же задач, было бы интересно о нем узнать. Сообщите, пожалуйста, о нем, и я дополню статью.
По поводу коллеций я уже понял, и я нигде не настаивал на том, что коллекции нужно использовать в коде бизнес-логики :)
Первичный ключ — любой суррогат заменяет оригинал не на все 100%. Я конкатенировал в коллекциях два атрибута в один первичный ключ — для выборки данных это отработало, но манипуляцию (сортировка, фильтрация) я уже копать не стал. Как говорится, каждому овощу свое место, а коллекции — не то место, где можно использовать составные первичные ключи.
Если следовать примеру и вводить BaseRepositoryInrerface, то он может, но не обязательно должен в своих контрактах использовать BaseEntityInterface. Мне моя религия позволяет не указывать типы аргументов и возвращаемого результата, описывая только имена контрактов и набор входных аргументов (кол-во, порядок и соглашение по их наименованию). Моя религия говорит, что договор через код, гораздо более сильный чем договор через документацию, и уж куда более сильный, чем договор на словах. А раз уж PHP позволяет специализировать тип аргумента в производных интерфейсах, то я бы этой возможностью и воспользовался. Я не настаиваю, что это правильно в рамках какой-то другой религии (DDD, например), я просто говорю, что я бы сделал именно так просто потому, что это делает разработку кода с моей точки зрения несколько легче. У разработчиков Magento 2 другая точка зрения, и они действуют по-другому. Я не призываю менять свои религии, если что. Я просто поделился своей точкой зрения.
Спасибо за развернутый ответ, коллега. Насколько я понял, на данный момент альтернативы коллекциям в механизмах фильтрации нет, появится он с версии 2.1, там и посмотреть можно будет, как его использовать.
Что касается составного первичного ключа, то это чисто мои тараканы — я считаю что для описания предметной области хватает простого первичного ключа, а для описания отношений между объектами предметной области простого первичного ключа уже недостаточно. Если та же Доктрина считает по-другому — это ее право.
Общий интерфейс на репозитории? Ну, если API так важен для архитектуры Magento 2, если код бизнес-логики должен быть завязан на Repository Interface, если большинство таких интерфейсов описывают методы для базовой манипуляции сущностями, то с моей точки зрения вполне логично выделить типовой набор методов (save, get, getById, delete, deleteById, getList) и обозвать его как-то типа BaseRepositoryInrerface, наследуя от него все остальные репо-интерфейсы. По крайней мере это было бы отличным маркером для читающих код и нечитающих мануалы, что это связанное подмножество интерфейсов, имеющее весомое значение в архитектуре Magento 2. Плюс, это было удобным примером для объяснения новичкам, что собственно такое "репозиторий сущности (EntityRepositoryInterface) и его метод getList(SearchCriteria $searchCriteria)", не на словах, а в коде. Но разработчики Magento 2 считают по-другому, поэтому такого интерфейса нет.
Для интереса залез в код. Вот интерфейс
\Magento\Catalog\Api\ProductRepositoryInterface, в нем описан методЗаглядываем в реализацию этого метода
\Magento\Catalog\Model\ProductRepository::getList:Сразу же бросается в глаза, что, в отношении получения списка записей, "репозиторий" — это всего лишь обертка над той же старой
добройколлекцией со всеми ее достоинствами и недостатками (включая невозможность иметь композитный primary key, которая меня почему-то огорчает больше всего).Если исполнение "технического долга" со стороны Magento 2 Team сводится к оборачиванию коллекций в код, имплементирующий некий общий EntityRepositoryInterface (которого, кстати, нет, хотя он просто обязан быть, если "разработчики мадженто за этим следят"), то все слова в отношении коллекций, сказанные коллегой Oxidant, верны также и для "обернутых коллекций". Если же в недрах Magento 2 есть "типовая" (или хотя бы "эталонная") имплементация "усредненного" интерфейса EntityRepositoryInterface (save, get, getById, delete, deleteById, getList) без использования коллекций внутри, то был бы весьма признателен, если бы коллега maghamed дал ссылку на эту имплементацию.
Только warning вылетает, но это уже мелочи на общем фоне :)
Если дожать еще передачу в DI-фабрику default-параметров для "именованного конструктора", то можно будет снимать свой вопрос по поводу использования в DI-фреймворках объектов с приватным конструктором.
Вылетает исключение:
Все из-за этого: