Комментарии 20
Также хочу добавить что для коллекций товаров есть атрибуты, которые загружаются по-умолчанию, если не указаны в addAttributeToSelect.
Такие атрибуты берутся из настроек атрибутов каталога в админке и файле etc/config.xml модуля по пути config/frontend/product/collection/attributes.
Разработчики тем порой об этом не знают и им проще сделать выбор коллекции и заново загрузить модель, у которой будут все ее атрибуты.
А почему вы это написали? Особенно то, что касается кода (кода коллекций в ваших примерах), я бы сказал, что не валидно по отношению к М2 чуть более чем полностью.
Ну и ваши рекомендации, они фактически не про Мадженто, они про Базу Данных.
Select * from table
работает медленней чем
select field from table
Поэтому не доставайте ненужные поля.
Считайте каунт не загружая записей из БД.
Magento 2 Service Layer
Service Contracts in Magento
Magento 2 Service Contracts Patterns
Github What is the goal of the service layer in Magento 2
То, что в М2 есть коллекция — вас должно интересовать в самую последнюю очередь, если вы вдруг не найдете Сервис Контракта, удовлетворяющего ваши нужды. Во всех же обычных случаях вы будете использовать репозиторий сущности (EntityRepositoryInterface) и его метод getList(SearchCriteria $searchCriteria).
Потому что в противном случае вы зависите не на интерфейс, а на реализацию и соответственно fragility такого кода очень высока. С зависимостью на интерфейс, реализация может кастомизироваться и подменяться, но ваш код это не поломает.
Также в самой системе он используется достаточно много, а те места где он не используется — можно считать техническим долгом, который постепенно будет рефакториться. Основная идея — модули могут «общаться» друг с другом только по средствам сервис контрактов.
Экстеншены, которые пишут партнеры используют сервис леер, и разработчики мадженто за этим следят.
Поэтому исходя из всего вышеперечисленного, Коллекции — это вынужденная необходимость, которую можно использовать, если нет необходимого сервис контракта. Но никак не рекомендованный механизм для разработчика.
Для интереса залез в код. Вот интерфейс \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 дал ссылку на эту имплементацию.
Репозиторий это не «всего лишь обертка над коллекцией», репозиторий разделяет уровень доменных сущностей от слоя дата маппинга. Фактически это реализация классического Фаулеровского паттерна http://martinfowler.com/eaaCatalog/repository.html
Можно сказать, что репозитории представляют из себя коллекцию сущностей (Агрегейшен Рутов http://martinfowler.com/bliki/DDD_Aggregate.html в понятии DDD), которая агностична к тому, где эти сущности сохраняются и как.
Все репозитории являются частью публичного АПИ, предполагается, что репозитории будут выполнять роль Entry Point-a для работы с доменными сущностями. Поэтому именно репозитории будут плагинизироваться больше всего.
Сейчас для имплементации ф-ии getList практически все (вероятно даже все) репозитории используют механизм коллекций для реализации механизма фильтрации.
Более того, когда вы будете реализовывать АПИ для своего модуля, в реализации репозитория для сущности вашего модуля вы будете использовать коллекцию тоже. Потому что пока в Мадженто нет другого механизма, он должен появиться с версии 2.1
Но главное тут то, что код бизнес логики у вас (также как и в коде Мадженто) должен зависеть на RepositoryInterface, а не на коллекцию. Использование коллекции — это деталь реализации, которая постепенно будет меняться. Поэтому вы сами, как разработчик собственного модуля заинтересованы в том, чтобы изолировать код, который впоследствии станет деприкейтед и не плодить зависимости на него.
Композитный primary key не нужен, та же Доктрина не рекомендует использовать композитный составной ключ.
> Если исполнение «технического долга» со стороны Magento 2 Team сводится к оборачиванию коллекций в код,
Идея сводиться к тому, что нужно ввести стабильные АПИ, которые будут использовать и расширять. И иметь возможность безболезненно менять реализацию, которая кроется за этими АПИ. В данном случае Коллекции — реализация, и будет меняться на EntityManager который появится в версии 2.1
>EntityRepositoryInterface (которого, кстати, нет, хотя он просто обязан быть, если «разработчики мадженто за этим следят»)
Вот этого не понял. Почему в Мадженто должен быть общий интерфейс на репозитории? Его не должно быть и его нет.
>то все слова в отношении коллекций, сказанные коллегой Oxidant, верны также и для «обернутых коллекций».
Тут важно понимать следующее, основной посыл Oxidant сводился к тому, что программисты с недостаточным объемом знаний Мадженто, используют коллекции неправильно, когда пишут бизнес логику. Мой посыл в том, что эти программисты не должны использовать коллекции вообще. Они должны использовать репозитории, валидное использование коллекций сейчас может быть в реализации репозитория для собственного модуля, в остальном использования коллекций стоит избегать.
Спасибо за развернутый ответ, коллега. Насколько я понял, на данный момент альтернативы коллекциям в механизмах фильтрации нет, появится он с версии 2.1, там и посмотреть можно будет, как его использовать.
Что касается составного первичного ключа, то это чисто мои тараканы — я считаю что для описания предметной области хватает простого первичного ключа, а для описания отношений между объектами предметной области простого первичного ключа уже недостаточно. Если та же Доктрина считает по-другому — это ее право.
Общий интерфейс на репозитории? Ну, если API так важен для архитектуры Magento 2, если код бизнес-логики должен быть завязан на Repository Interface, если большинство таких интерфейсов описывают методы для базовой манипуляции сущностями, то с моей точки зрения вполне логично выделить типовой набор методов (save, get, getById, delete, deleteById, getList) и обозвать его как-то типа BaseRepositoryInrerface, наследуя от него все остальные репо-интерфейсы. По крайней мере это было бы отличным маркером для читающих код и нечитающих мануалы, что это связанное подмножество интерфейсов, имеющее весомое значение в архитектуре Magento 2. Плюс, это было удобным примером для объяснения новичкам, что собственно такое "репозиторий сущности (EntityRepositoryInterface) и его метод getList(SearchCriteria $searchCriteria)", не на словах, а в коде. Но разработчики Magento 2 считают по-другому, поэтому такого интерфейса нет.
Да, альтернативы коллекциям для реализации фильтрации сейчас нет. Но это попрежнему не значит, что ими можно пользоваться в коде бизнес логики.
Относительно первичного ключа — суррогатный ключ решает проблемы отношений.
Теперь самое важное — общий интерфейс. Разработчики Magento в данном вопросе считают правильно. А именно — что общий интерфейс BaseRepositoryInrerface вводить не нужно, более того это не правильно.
Взять, например интерфейс категории:
namespace Magento\Catalog\Api;
/**
* @api
*/
interface CategoryRepositoryInterface
{
/**
* Create category service
*
* @param \Magento\Catalog\Api\Data\CategoryInterface $category
* @return \Magento\Catalog\Api\Data\CategoryInterface
* @throws \Magento\Framework\Exception\CouldNotSaveException
*/
public function save(\Magento\Catalog\Api\Data\CategoryInterface $category);
/**
* Get info about category by category id
*
* @param int $categoryId
* @param int $storeId
* @return \Magento\Catalog\Api\Data\CategoryInterface
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function get($categoryId, $storeId = null);
/**
* Delete category by identifier
*
* @param \Magento\Catalog\Api\Data\CategoryInterface $category category which will deleted
* @return bool Will returned True if deleted
* @throws \Magento\Framework\Exception\InputException
* @throws \Magento\Framework\Exception\StateException
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function delete(\Magento\Catalog\Api\Data\CategoryInterface $category);
....
Как видите каждый из методов в своем контракте завязан на дата интерфейс категории \Magento\Catalog\Api\Data\CategoryInterface, т.е. все методы: get, getList, save, delete завязаны на контракт специфичного дата интерфейса.
Если представить, что последовать вашему примеру и ввести BaseRepositoryInrerface, то он должен в своих контрактах использовать BaseEntityInterface. Маркерный интерфейс, который расширят все Дата интерфейсы в системе. Это антиконцептуально с точки зрения доменного дизайна. Более того — это исключительно маркерный интерфейс, т.к. у нас нет ни одного метода, который бы мы могли туда положить. Даже getId не можем, потому что у сущностей с натуральным первичным ключем (например, продукт) в роли ID выступает SKU, который типа string, а не int. Естественно у такой BaseEntityInterface не можем быть реализации. Это просто маркерный интерфейс.
Поэтому Magento программисты решили договориться, и использовать во всех *RepositoryInterface одинаковый набор методов с одинаковыми параметрами, но завязанными на определенный тип сущности, как в моем примере выше на CategoryInterface
По поводу коллеций я уже понял, и я нигде не настаивал на том, что коллекции нужно использовать в коде бизнес-логики :)
Первичный ключ — любой суррогат заменяет оригинал не на все 100%. Я конкатенировал в коллекциях два атрибута в один первичный ключ — для выборки данных это отработало, но манипуляцию (сортировка, фильтрация) я уже копать не стал. Как говорится, каждому овощу свое место, а коллекции — не то место, где можно использовать составные первичные ключи.
Если следовать примеру и вводить BaseRepositoryInrerface, то он может, но не обязательно должен в своих контрактах использовать BaseEntityInterface. Мне моя религия позволяет не указывать типы аргументов и возвращаемого результата, описывая только имена контрактов и набор входных аргументов (кол-во, порядок и соглашение по их наименованию). Моя религия говорит, что договор через код, гораздо более сильный чем договор через документацию, и уж куда более сильный, чем договор на словах. А раз уж PHP позволяет специализировать тип аргумента в производных интерфейсах, то я бы этой возможностью и воспользовался. Я не настаиваю, что это правильно в рамках какой-то другой религии (DDD, например), я просто говорю, что я бы сделал именно так просто потому, что это делает разработку кода с моей точки зрения несколько легче. У разработчиков Magento 2 другая точка зрения, и они действуют по-другому. Я не призываю менять свои религии, если что. Я просто поделился своей точкой зрения.
А какие проблемы у вас возникают с фильтрацией, которые вы не можете решить с помощью SearchCriteriaBuilder?
Я выше просил вас предоставить пример реального использования сервис лаера, но давайте будем более детальными. Предоставьте пожалуйста пример создания кастомного модуля, со своей таблицей в бд и с реализацией сервиса для доступа к моделям для работы с этой бд с учетом различных фильтров (Search criteria). Это поможет нам говорить о более детально примере, а не об абстрактных вещах. Все ссылки выше предоставляют общую информацию и нигде нет примера реального использования этого для чего-то выходящего за рамки нативного функционала, кроме вырванных из контекса примеров и обещаний в будущем все переписать. То что в мадженте есть они и они используются для доступа к данным — это определенно так ( продукты, цмс,… ). Но они не заменяют коллекции повсевместно. По крайней мере на данный момент.
На данный момент коллекции есть и это факт, который признали даже вы, в сторонних модулях и внутри мадженты они используются. Чтобы их правильно использовать надо понимать как же они работают и статья именно об этом. Нельзя просто сказать про новый подход «а вот в мадженте есть… да не везде… да не всегда применимо… да в будущем будет иначе… но есть». Пока что это не «везде» и это надо учитывать.
Увеличение производительности Magento