All streams
Search
Write a publication
Pull to refresh
49
-0.6
Alex Gusev @flancer

Я кодирую, потому что я кодирую…

Send message

Спсибо за пояснения, внесу правки в текст статьи.

Спасибо за коммент. Данная статья задумывалась как краткая сводка доступных способов промежуточного сохранения информации не в БД. Да, имплементаций интерфейса 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-фреймворках объектов с приватным конструктором.
Проверил.
use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container->register('time', Time::class);
$obj = $container->get('time');

Вылетает исключение:
PHP Fatal error:  Uncaught exception 'ReflectionException' with message 'Access to non-public constructor of class ...\Time

Все из-за этого:
    // Не удаляем пустой конструктор, т.к. это защитит нас от возможности создать объект извне
    private function __construct()
    {
    }
Там чуть выше картинке есть ссылка на статью, где объясняется сама картинка. А привел я ее к тому, что вы решили уточнить, что именно значит "объектов значений". Для наглядности, что я имел в виду под "POJO like" классами (в данной картинке они проходят под именем POCO, т.к. статья дотнетовская). Раз уж совмещать используемые термины, то наглядно.
Моим заблуждением было, что я читаю статью, посвященную применению вместо конструкторов с параметрами статических фабричных методов при создании различных объектов. Как оказалось, статья относится только к конструированию "объектов-значений".

IMHO, эпиграф было бы лучше поставить такой:

tl; dr — Не ограничивай себя одним конструктором в классе Time. Используй статические фабричные методы.

Вполне возможно, в таком случае у меня бы не возникло вопроса, как инжектить объект класса Time (или integer, как вы резонно заметили).
Спасибо за пояснение. Насколько я понял, предлагается делать "фабричные методы" для "POJO like" классов ("объектов значений" по вашей терминологии) в самих классах, а не выносить фабричные методы в отдельные фабрики. Такой подход не применим к классам, имплементирующим преобразование данных и используемым другими классами ("сервисов" по вашей терминологии). В случае необходимости использовать в конструкторе какого-либо класса "объектов значений" нужно не создавать фабрику этих "объектов значений", а пересмотреть необходимость использования "объектов значений" в конструкторе класса или переквалифицировать "объект значение" в другую роль и создать ему публичный конструктор.

Границы применимости предложенного Матиасом решения для меня стали довольно очевидны. Спасибо еще раз.
В его создании DI-фреймворком, "если конструктор недоступен".
Прошел по ссылке на оригинальную статью. Насколько я понял в "вопросах-ответах" Матиас считает, что этот подход хорош в самых простых объектах, которые и тестировать-то нет неообходимости. Ну что ж, каждое решение имеет свою область применения.
Меня интересовало, что будет инжектиться в класс, завязанный на класс Time, если его конструктор недоступен:

class DependedClass {

    public function __construct(Time $time) {
        // ...
    }
}

Т.е., данный подход предполагает в таких случаях создавать фабрики, которые будут создавать объекты с использованием их собственных статических фабричных методов, и уже эти фабрики инжектить в зависящие от объектов классы (или, как в случае с Symfony, указывать в настройках DI, что для создания экземпляров Time нужно использовать "factory: [TimeFactory, create]"):

class TimeFactory {
    public function create() {
        $result = Time::fromValues(0, 0);
        return $result;
    }
}

class DependedClass {

    public function __construct(TimeFactory $factory) {
        // ...
    }
}

Вполне возможно, в этом есть какой-то сакральный смысл, но я бы все-таки конструктор не прятал. Ну или очень сильно ограничил бы применение "именованных конструкторов" — все-таки статика к тестам совсем не friendly. Вообщем, мне эти "именованные конструкторы" как-то не совсем по душе. Но смотрятся красиво, не отрицаю.
А как этот подход уживается с Dependency Injection (Zend, Symfony, ...)?
Массивна уж очень. Гибкость достигается за счет сложности. Плюс высокий порог вхождения, неочевидность некоторых решений, размазывание функционала между уровнями (например, формирование в PHP-скриптах JS-кода для фронта). Все это и приводит к тому, что решение нельзя назвать "изящным". Гибким, мощным — можно. Изящным — я бы поостерегся. Хотя вторая версия, на мой взгляд, более "прямая" (а может я уже привык думать "Magento style"). Это я как разработчик говорю, а не как конечный пользователь.
Не плохое. "Все есть яд, и все есть лекарство. Зависит от дозы." (с) Парацельс

Просто у angular'а своя ниша, и ее границы достаточно хорошо видны. Инвестировать свое время в разработку прототипа на angular'е, если твое web-приложение строится на чем-то другом — не самое оптимальное решение, IMHO.
Замечательная статья! Как "Матрица" или "Шестое чувство"! Интригует, зовет узнать, как получить все эти увлекательные плюшки и незадорого, а ближе к концу раз — и angular. Ощущение, как доской по голове дали. Зато сразу отрезвление наступило и морок пропал. Да, "серебряной пули" не существует, каждое решение имеет свои границы применимости, angular ничуть не хуже ember/knockout/backbone/smartclient, а твои проекты — это те проекты, в которых прототипирование такой ценой не нужно. Нет, разумеется, есть такие по настоящему крупные проекты, в которых построение прототипа на angular'е дает все эти бонусы и плавно перетекает в production (особенно, если production тоже на angular'е), но вот для твоих задач (разработка модулей для Magento) и бумаги хватит (или Enterprise Architect'а, если нужно красиво). В любом случае, написано здорово, а то, что цена высока — так никто не говорил, что это для всех.
Пожалуйста. На хабре не любят неинформативные комменты — такова специфика ресурса. Поэтому и минусят.

Information

Rating
Does not participate
Location
Рига, Латвия, Латвия
Date of birth
Registered
Activity

Specialization

Fullstack Developer
Lead
From 3,000 €
JavaScript
HTML
CSS
Node.js
Vue.js
Web development
Progressive Web Apps
PostgreSQL
MySQL
GitHub