Наилучший способ переопределения классов в стороннем модуле

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Аватара пользователя
jilizart
Сообщения: 82
Зарегистрирован: 2010.04.22, 18:56
Контактная информация:

Наилучший способ переопределения классов в стороннем модуле

Сообщение jilizart » 2015.04.05, 20:21

Давайте подискутируем на данную тему, какой способ более правильный и какие средства для этого у нас есть.

Задача
- Переопределить модель стороннего модуля для добавления или изменения существующей логики
- Переопределить контроллер стороннего модуля для добавления еще одного действия

С контроллерами вроде все понятно. У класса Module имеется $controllerMap который можно переопределить.

Для моделей соответственно приходит на ум такое же решение. Сделать свойство $modelMap, в котором можно будет указать модель и новый класс для нее.

Какие способы используете вы?


zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение zelenin » 2015.04.05, 23:03

Код: Выделить всё

Yii::$container->set(\common\test\UserModel::className(), \common\test\UserModelCustom::className()); 

dmeroff
Сообщения: 101
Зарегистрирован: 2012.06.05, 14:32
Откуда: Петрозаводск
Контактная информация:

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение dmeroff » 2015.04.06, 12:52

Через контейнер. При этом нужно учитывать, что если разработчик модуля не озаботился возможностью переопределения моделей (или других классов), то придется перепопределять контроллеры и менять классы вручную. Что я имею в виду:

Код: Выделить всё

public function actionIndex()
{
    // вот такое переопределить нельзя - хардкод
    $model = new Model();
    // а вот такое переопределить можно с помощью контейнера
    $model = Yii::createObject(Model::className());
}
 
Отдельную сложность вызывают модели ActiveRecord, ведь они во работают через использование статических методов (::findOne(), ::find()), что переопределить через контейнер нельзя. В модуле, который я разрабатываю, я разрулил это введением как раз $modelMap и компонента Finder, который дергает метод find() соответствующей модели (можете глянуть код и доки).

На истину в последней инстанции не претендую, просто как вариант, используемый мной.

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение zelenin » 2015.04.06, 12:58

dmeroff писал(а):Через контейнер. При этом нужно учитывать, что если разработчик модуля не озаботился возможностью переопределения моделей (или других классов), то придется перепопределять контроллеры и менять классы вручную. Что я имею в виду:

Код: Выделить всё

public function actionIndex()
{
    // вот такое переопределить нельзя - хардкод
    $model = new Model();
    // а вот такое переопределить можно с помощью контейнера
    $model = Yii::createObject(Model::className());
}
 
Отдельную сложность вызывают модели ActiveRecord, ведь они во работают через использование статических методов (::findOne(), ::find()), что переопределить через контейнер нельзя. В модуле, который я разрабатываю, я разрулил это введением как раз $modelMap и компонента Finder, который дергает метод find() соответствующей модели (можете глянуть код и доки).

На истину в последней инстанции не претендую, просто как вариант, используемый мной.
можно получить модель через

Код: Выделить всё

$modelObject = Yii::$container->get('modelName'); 
и сделать запрос

Код: Выделить всё

$modelObject::findOne(); 
такой вариант работает, более прозрачен, но создает ненужный объект при запросе из контейнера.

dmeroff
Сообщения: 101
Зарегистрирован: 2012.06.05, 14:32
Откуда: Петрозаводск
Контактная информация:

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение dmeroff » 2015.04.06, 13:14

Нашел способ и без создания лишнего объекта:

Код: Выделить всё

if (isset(\Yii::$container->getDefinitions()['modelName'])) {
    $class = \Yii::$container->getDefinitions()['modelName']['class'];
} else {
    $class = 'modelName';
}
$search = $class::findOne(1);
Не уверен, правда насчет прозрачности вот такого подхода, но право на жизнь он тоже имеет.

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение zelenin » 2015.04.06, 13:18

dmeroff писал(а):Нашел способ и без создания лишнего объекта:

Код: Выделить всё

if (isset(\Yii::$container->getDefinitions()['modelName'])) {
    $class = \Yii::$container->getDefinitions()['modelName']['class'];
} else {
    $class = 'modelName';
}
$search = $class::findOne(1);
 
Не уверен, правда насчет прозрачности вот такого подхода, но право на жизнь он тоже имеет.
буквально вчера обсуждали на хабре все это http://habrahabr.ru/post/254179/#comment_8361591 в том числе getDefinitions(). Этот способ имхо чересчур костылен.

dmeroff
Сообщения: 101
Зарегистрирован: 2012.06.05, 14:32
Откуда: Петрозаводск
Контактная информация:

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение dmeroff » 2015.04.06, 14:50

На мой вкус не хуже создания лишнего объекта и вызова у него статического метода. В любом случае красивого решения не получается.

На хабре говорили о проблемах с реляциями, но вот такого примера никто не привел: Допустим стороннее расширение имеет две модели User и Profile.

Код: Выделить всё

class User extends ActiveRecord
{
    public static function tableName() {return 'user';}
    
    public function getProfile()
    {
        return $this->hasOne(Profile::className(), ['user_id' => 'id']);
    }
} 

Код: Выделить всё

class Profile extends ActiveRecord
{
    public static function tableName() {return 'profile';}
    
    public function getUser()
    {
        return $this->hasOne(User::className(), ['id' => 'user_id']);
    }
} 
Теперь мы решили переопределить Profile:

Код: Выделить всё

class MyProfile extends Profile
{
    // ...
} 

Код: Выделить всё

Yii::$container->set(Profile::className, MyProfile::className()); 
Но при этом модель User совершенно ничего не знает о том, что мы переопределили Profile, поэтому при вызове $user->profile мы получим инстанс Profile, вместо MyProfile. Поэтому нам придется перепопределить еще и метод getProfile модели User - имхо лишнее действие. Поправте меня, если я ошибаюсь здесь.

У себя в модуле я обхожу это именно использованием modelMap, с помощью которой классы регистрируются в контейнере и плюс modelMap прокидывается в модели, где уже используется таким образом:

Код: Выделить всё

public function getProfile()
{
    return $this->hasOne($this->module->modelMap['Profile'], ['user_id' => 'id']);
} 

lynicidn
Сообщения: 2221
Зарегистрирован: 2014.05.24, 15:12

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение lynicidn » 2015.04.06, 15:58

$this->module
это завязка на модуль - id там статичен у вас, т.е. модуль под другим id не заюзать

Аватара пользователя
jilizart
Сообщения: 82
Зарегистрирован: 2010.04.22, 18:56
Контактная информация:

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение jilizart » 2015.05.11, 11:29

В общем где то надо держать имена классов моделей.
В модуле не вариант, т.к привязка к id.
Через контейнер не получится пользоватся статическими методами.
Более менее универсального метода я так понимаю нет

nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение nepster » 2015.05.11, 12:49

Вот это переопределение определенных частей стороннего модуля полнейший тупняк. При использовании кучи сторонних модулей и компонентов и глобальном использовании переопределений, событий и тп. проект превращается в полную свалку, а когда разрастается то этот весь мусор будет прилично тормозить дело. Не говоря уже о том, что если зайдет новый разработчик в проект, как долго он будет разбираться в этой тонне конфигов.

За время пока я отписывал вопросы по каждому чиху на форме, изучал каждый бит символа в коде yii2, решая достаточно редкие и интересные задачи, я понял, что практически все готовые сторонние модули, которые предлагают разработчики годятся только для создания простого проекта, самого простого.

Все доки и примеры типа "Пишем просто блог на yii2" или "Пишем просто магазин на yii2", показывают написание "Hello World", так как в 99% случаях обладают только 1% нужного функционала.

За время практики я столкнулся со следующими проблемами сторонних модулей. Для примера давайте разберем любой модуль пользователей на гите, берем самый первый модуль в поиске по запросу "Yii2 users" - https://github.com/amnah/yii2-user

Проблема номер 1
Если необходимо добавить свой функционал, нужно создавать 100500 контейнеров, переопределять модели, добавлять методы, переопределять контроллеры добавлять экшины, переопределять виды (тоесть уже обязательно нужно использовать тему) и др.

Проблема номер 2
Есть экшин авторизации https://github.com/amnah/yii2-user/blob ... er.php#L70, была такая у меня задача в сфере сетевого маркетинга, что нужно было сделать следующее: Пользователь покупает фин. пакет и для него регистрируется 10 аккаунтов (все аккаунты на его почту с его паролем). Тоесть авторизоваться можно на 10 аккаунтов под одними и теме-же данным. Вкратце делаем префиксы к email (1|a@mail.ru, 2|a@mail.ru, 3|a@mail.ru) и при авторизации выдаем лист аккаунтов, чтобы пользователь выбрал под каким зайти. Сделано для того, что бы потом пользователь мог их продавать. Так вот, мне нужно перед авторизацией пропустить username через фильтр, а после авторизации сделать определенные действия.

Тут конечно спасают события, а что если событий нет в нужном месте ? И задача редкая и автор не захочет добавить событие чисто для тебя.
Тоесть нужно будет переопределять целый экшин, из-за одной строчки?

Проблема номер 3
С темой отдельная история, если пользоваться твигом, то всплывает вот такой баг https://github.com/yiisoft/yii2/issues/7984. Я точно не могу сказать всплывет ли эта проблема, если файлы видов в модуле на php, а вы используете твиг, тема может не подхватить. Далее необходимо перекрывать переводы, если нужно добавить или изменить фразы на свои и выходит, чтобы костомизировать модуль нужно хорошо попотеть и сделать свалку конфигов, переопределений и еще 1 слой непонятно чего.

Проблема 4
Порог вхождения. Когда в проекте свалка из 10 000 DI, 100500 событий, разбросов файлов видов и переводов, попробуйте объяснить новому разработчику весь этот поток.

andrei.obuhovski
Сообщения: 610
Зарегистрирован: 2015.07.16, 10:50

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение andrei.obuhovski » 2016.01.19, 09:37

Подниму старую тему.
Чем плоха подмена через Yii::$classMap?

Аватара пользователя
ElisDN
Сообщения: 5428
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение ElisDN » 2016.01.19, 10:03

nepster писал(а):За время практики я столкнулся со следующими проблемами сторонних модулей...
Да, отсутствие единого стандарта конфигурации и, как говорил zelenin, низкая компетентность сообщества во многих вопросах приводят к полнейшему разноброду. Даже введение наследования модулей (как бандлов в Symfony) перед этим бессильно.

nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение nepster » 2016.01.19, 22:16

ElisDN писал(а):
nepster писал(а):За время практики я столкнулся со следующими проблемами сторонних модулей...
Да, отсутствие единого стандарта конфигурации и, как говорил zelenin, низкая компетентность сообщества во многих вопросах приводят к полнейшему разноброду. Даже введение наследования модулей (как бандлов в Symfony) перед этим бессильно.

Интересно, а есть смысл подумать в сторону так сказать стандартов написания расширений для yii и навязать более строгий стиль кодирования ?

Аватара пользователя
ElisDN
Сообщения: 5428
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение ElisDN » 2016.01.20, 01:33

nepster писал(а):Интересно, а есть смысл подумать в сторону так сказать стандартов написания расширений для yii и навязать более строгий стиль кодирования ?
Навязать что? SOLID, GRASP, модульность и прочий здравый смысл? Для Yii это слишком сложно, поэтому изначально лишнее и никогда этого не будет. Позиционирование Yii - максимальная простота любой ценой для удобства RAD разработки в стиле "фигак, фигак и в продакшен". В большей степени за счёт замкнутой экосистемы и отбрасывания всего чужеродного.

Сам фреймворк для удобства напичкан статическими методами ::find() и ::getDb() (что делает классы моделей неподменяемыми и практически нетестируемыми стандартными моками), не предоставляет ни одного легального лёгкого способа переопределить модуль (кроме DI и костыльного classMap) и несовместим "из коробки" со внешним миром. Так что в таком "многообразии возможностей" просто подвиг сделать любой более-менее работающий вариант с минимумом костылей. А Вы про стиль кода...

andrei.obuhovski
Сообщения: 610
Зарегистрирован: 2015.07.16, 10:50

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение andrei.obuhovski » 2016.01.20, 09:15

Nepster видимо имел ввиду, что раз уж так получилось, что без костылей модели расширений не переопределить, то хотябы определить единый костыль переопределения, и прописать его в документации.

Аватара пользователя
ElisDN
Сообщения: 5428
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение ElisDN » 2016.01.20, 09:19

andrei.obuhovski писал(а):хотябы определить единый костыль переопределения, и прописать его в документации.
Изображение

nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение nepster » 2016.01.20, 20:59

ElisDN писал(а):
nepster писал(а):Интересно, а есть смысл подумать в сторону так сказать стандартов написания расширений для yii и навязать более строгий стиль кодирования ?
Навязать что? SOLID, GRASP, модульность и прочий здравый смысл? Для Yii это слишком сложно, поэтому изначально лишнее и никогда этого не будет. Позиционирование Yii - максимальная простота любой ценой для удобства RAD разработки в стиле "фигак, фигак и в продакшен". В большей степени за счёт замкнутой экосистемы и отбрасывания всего чужеродного.

Сам фреймворк для удобства напичкан статическими методами ::find() и ::getDb() (что делает классы моделей неподменяемыми и практически нетестируемыми стандартными моками), не предоставляет ни одного легального лёгкого способа переопределить модуль (кроме DI и костыльного classMap) и несовместим "из коробки" со внешним миром. Так что в таком "многообразии возможностей" просто подвиг сделать любой более-менее работающий вариант с минимумом костылей. А Вы про стиль кода...
По сути самое плохое в yii2 это формы, модели и виджеты. Причем это все обладает как плюсами так и минусами и если задача стоит быстренько завести, чтобы хоть как-то работало, то тут проблем никаких нет. Однако в долгосрочной перспективе многое выливается в проблемы. С другой стороны команда Александра Макарова не жалуется и разрабатывает достаточно большой проект на yii.

Что касается тестирования тут у меня вопросы, зачем тестировать модели, работа которых на 100% ложится на yii. Тоесть нет смысла тестировать find() так как можно предположить, что это часть инструмента, которая работает. Тоже самое, что нет смысла тестировать yii helper`ы. Еще что касается тестирования, даже если это нельзя протестировать просто, то есть возможность использовать например рефлекшин апи, пусть это и сложнее, но техническая возможность существует.

С другой стороны, очень много идей включая ar одолжили у рельсов, а я честно говоря не разу не видел не рельсы ни то, чтобы их гнобили за сильную связанность.

Вернемся к yii. По сути использование виджетов это дело добровольное, я например использую их в админке, на самом сайте только кастомные штуки. Тут никаких проблем нет. А вот с моделями нужно что-то делать. Так как 90% разработчиков делают 1 модель, 50 сценариев и всю логику туда-же и это в лучшем случае.

Тут нужно подумать в сторону того, чтобы сделать актив рекорд например final классом и заставлять всех уходить от статики в пользу композиции.

Если по большому счету разобраться у меня претензии в основном к моделям.

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение zelenin » 2016.01.20, 22:17

с точки зрения архитектуры проекта формы и виджеты вообще ничто. они могут быть во фреймворке, но могут и не быть, что вернее. эти типа ты про дворника говоришь, типа дворник херовый, потому что у него прическа немодная - для человека было бы неплохо иметь нормальную прическу, а для дворника надо иметь метлу и ведро.

Аватара пользователя
ElisDN
Сообщения: 5428
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Наилучший способ переопределения классов в стороннем модуле

Сообщение ElisDN » 2016.01.20, 22:18

nepster писал(а):Что касается тестирования тут у меня вопросы, зачем тестировать модели, работа которых на 100% ложится на yii.
Как раз об этом призадумался при подготовке своего скринкаста о тестировании. Может я перфекционист, но всё-таки... Вот о чём думал:

С логикой фреймворка проблем нет. Другое дело, когда мы вмешиваем свою логику в процесс сохранения. В модели часто навешаны поведения и прочий код в beforeSave()/afterSave(). Многое работает с changedAttributes и прочим. И надо вызвать $model->save() и проверить, что все timestamps расставлены, поля отфильтрованы, файлы загружены и все счётчики и связи обновлены. В крайнем случае, чтобы элементарно найти баг, что забыли вернуть true из beforeSave() и модель не сохраняется.

Комбинаций для проверки может быть десятки. Сотни раз записывать в базу и вычищать порой долго. Можно вместо вызова save() вручную эмулировать работу фреймворка, дёргая эти методы beforeSave()/afterSave(), beforeDelete()/afterDelete() и расставлять dirtyAttributes, setIsNewRecord и прочее. Или смириться с необходимостью работы с базой. А хотелось бы для ускорения тестов просто заглушить код сохранения или удаления из БД и вызвать save() или delete(), но там в https://github.com/yiisoft/yii2/blob/50 ... d.php#L451 такой код:

Код: Выделить всё

$primaryKeys = static::getDb()->schema->insert($this->tableName(), $values)) 
И здесь начинаются пляски, что простым PHPUnit мы конкретно для этой модели статический getDb() никак не замокаем. Нужно либо искать библиотеки для мока статики (или самому возиться с рефлекшенами) чтобы как-то замокать этот getDb() или insert(), либо наследоваться от моделей и переопределять insertInternal(), либо подменять глобально весь Yii::$app->db вместе со схемами таблиц и возвращать обратно в каждом setUp()/tearDown(). Тогда и понял, что отвязать от БД или хотя бы замокать save() или delete() в ActiveRecord для тестов - весьма нетривиальная задача.
Последний раз редактировалось ElisDN 2016.01.20, 22:24, всего редактировалось 3 раза.

Ответить