Pull to refresh

Comments 75

UFO just landed and posted this here
Профессионализм в пропаганде анти-паттерна Singleton? Идут лесом такие «профессионалы».

Чем плох Singleton конкретно в PHP? И все компоненты в Laravel работают через фасады: App, View, Queue, Mail, Route, Filter, Session, Cookie.

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

Можно передавать, например, UserRepositoryInterface, но фреймворку (а конкретно IoC контейнеру) нужно указать, какой класс использовать для этого интерфейса, например:

App::bind('UserRepositoryInterface', 'DbUserRepository');
Скрывать работу с экземпляром за вызовом статических методов очень плохо, по той причине, что это ломает полностью ООП подход. Соответственно мы не можем уже полагаться на SOLID в проекте.

К тому, если попытаться подключить класс, который использует подобную зависимость где-либо вне Фреймворка, он рухнет и что бы восстановить его функциональность, придётся писать статические Mock-объекты.
На самом деле, я не понял, зачем прятаться за такими «фасадами»

class Cache extends Facade {
    protected static function getFacadeAccessor() { return 'cache'; }

}


, если всё сводится к
App::container->get("cache")


p.s.
С Фреймворком не работал, знания лишь почерпнул из статьи и комментариев к ней.
Фасад является унифицированным интерфейсом, который скрывает всю логику и сложность имплементации функционала. Он является единственной точкой входа. Так, например если внутри фасада что-то изменится — использование его не изменится. То есть он служит упрощенным интерфейсом сложной реализации.
Для того чтобы использовать Mock фасады в Laravel достаточно написать

Cache::shouldReceive('get');

или

Event::shouldReceive('fire');


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

P.S. Спасибо за интересный комментарий
Он является единственной точкой входа. Так, например если внутри фасада что-то изменится — использование его не изменится.

Как бы, когда вы публикуете интерфейс, то он так и работает. Вы можете подменять реализацию данного интерфейса, но если он реализован, то работать будет так как надо.

К тому же, я пока не понимаю, как набор статических методов воспринимать как интерфейс?
Там нет Singleton, там используются фасады, о них вы можете прочитать на wiki или тут.
Тот магический вариант DI, которым хвастается статья, работает только, когда указан класс в качестве типа, а не интерфейс, что само по себе редкость.

Откуда такая информация? В документации есть примеры использования интерфейсов.
Опа, про анти-паттерн Singleton по подробнее, пожалуйста, а то, как говорится «А ребята не в курсе».
В особо запущенных случаях проект невозможно покрыть тестами.
Если статья носит пропагандистский характер, то не получилось. Перейти с yii на laravel не захотелось.
у вас не найдется минутки поговорить о symfony?
Минутка найдется, но о Symfony ничего вразумительного не скажу т.к не работал с ним серьезно.
Простите, я аметист не верю в symfony.
Попробуйте тогда Symfony2… ;)
Жаль, что цель статьи или итог получился пропагандистским. Из заголовка, я ожидал сравнение возможностей нескольких популярных (или не очень), полноценных или микрофреймворков. В котором автор позволяет сделать вывод, на что стоит посмотреть или обратить внимание.
Возможности всех фреймворков почти одинаковы. Отличия, в основном, лишь в том, что у всех разный подход.
Обычно эти самые подходы и сравнивают. Сильная связанность в Yii и слабая связанность в Zend или Symfony. Хорошо по одной причине, плохо по другой. И так далее. Вот именно это я и ожидал увидеть из заголовка. Пара кейсов и рассуждения, вот что было бы круто.
Еще один простой фреймворк с низким порогом вхождения, но понравился стиль изложения статьи :) Остался с чуством, что этот фреймворк для ленивых топтать клаву и он такой умный что версия 5 будет писать сайты сама, если версия 4 уже умееть решать за разработчика какие данные и какими запросами ей выбирать и какой структуры будет JSON после сериализации.
Меня в Laravel убивает то, что вообще все, что только можно, сделано статическими методами классов.
Где-то статика используется как пространство имен для обычных функций (не понимаю этого стремления избегать функций, они тоже пространства имен поддерживают вообще), где-то фасады со статическими вызовами. В итоге, все делается через статические методы, если не лезть внутрь.
Полгода назад поддался пиару на Хабре и попробовал Laravel. Не понравилось. С Yii почему-то начало было приятнее.
Господа вы может мне объясните зачем в динамическом языке DI?
Что бы параметры подтягивать из настроек и переопределять / расширять поведение модулей 3ей стороны
Других usecase'ов не вижу. А пример DI в статье, так вообще смешной, не понятно зачем он там %)
А пример DI в статье, так вообще смешной, не понятно зачем он там %)

Там же все зависимости «автомагически» создаются, ничего руками делать не надо. Или Вы не об этом?
Зачем создавать авоматически и придумывать магию, в том месте где она не нужна. Из комментариев людей, которые с Фреймворком работали, я понял что создаётся зависимость, если в хинте указан не интерфейс, а конкретный класс.

Что это за внедрение зависимости такое?
class A {
   function __construct(B b=null) [
       if (b === null) {
           b = new B();
       }
   }
}

Явное лучше чем неявное(магия).
Ваш пример кода мне не понятен. Это так в Laravel сделано? Тут совсем другое написано: laravel.com/docs/ioc
Это я написал, как пример явного, которое лучше чем не явное создание экземпляра(как сделано в laravel)
Вообще говоря такой «хак» довольно удобен (если немного изменить). Особенно если DI на 92,486% вводится только для кошерности целей тестирования. В тестовом коде можем подсунуть любой стаб или мок без труда, а в боевом писать без этого.
Это я криво написал :)
Я имел в виду, что мой вариант(который я написал) лучше чем неявное создание через сигнатуру typehint'a.
Это делается без IoC, но во фрейморке он есть и просовывается класс или интерфейс, в случае интерфейса контейнеру нужно указать какой класс в этом случае просунуть
Можете пояснить свою мысль? Почему он не нужен?
Язык у нас динамический. Как бе особых проблем создать на лету все что надо проблемы нет. В случае статически компилируемого языка заката солнца в ручную будет существенно больше.
То есть по вашему главной фишкой di является автоматическое создание объектов и в этом вся суть? А то что мы можем быстро заменить имлементацию какого-то сервиса, от которого зависят наши, это так, бонус? В этих ваших «статически компилируемых» тоже никаких проблем нету сделать new MySuperClass или что-то в этом духе. java вот тоже динамический (управляемый код, jit-компилятор, байткод и все такое прочее), и c#.
То есть по вашему главной фишкой di является автоматическое создание объектов и в этом вся суть? А то что мы можем быстро заменить имлементацию какого-то сервиса, от которого зависят наши, это так, бонус?

В случае динамического языка быстро заменить имплементацию вы можете и так. И DI для этого вам особо будет не нужен. И да как-то я всегда думал, что основная цель DI как раз упросить создание окружения.

new MySuperClass или что-то в этом духе.

В java я не могу на лету добавить еще одно значение или функцию. В динамических запросто.
Что-то не улавливаю. Приведите, пожалуйста, пример код с DI и без.
С DI у вас строгая типизация фактически.
Объявляем интерфейс через DI потом цепляем нужную реализацию. В случае динамики можно просто подсунуть другой класс без интерфейса. К примеру в том же Python тупо нет интерфейсов, только абстрактные классы.
вы не путаете php и js? да, в php есть определенный уровень интроспекции, но даже там заменить имплементацию метода в рантайме довольно проблематично. Даже более того, в java есть возможность подобного изменения кода в рантайме. но… Зачем?

По сути разницы нету, динамический язык или нет. Суть та же что и для java/c++. Имея интерфейс A и сервис B зависящий от имплементации интерфейса A, мы сможем когда угодно подменить реализацию этого интерфейса A глобально для всего проекта. И к слову, подмена имплементации не происходит в рантайме, только во время разработки.

опять же как бы ни было тяжело или легко создавать объекты и передавать им нужные зависимости, с DiC это проще.
вы не путаете php и js? да, в php есть определенный уровень интроспекции, но даже там заменить имплементацию метода в рантайме довольно проблематично.

Вообще нет. eval всегда есть в наличии.

Даже более того, в java есть возможность подобного изменения кода в рантайме. но… Зачем?

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

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

В динамическом языке вы сами понимаете это можно и без DI сделать. По этой причине в случае динамического языка можно и не использовать DI там где в статическом придется.

опять же как бы ни было тяжело или легко создавать объекты и передавать им нужные зависимости, с DiC это проще.

Ну вот разве что. А так я не могу сказать что DI популярен в динамических языках.
ну inversion of control все же лучше применять. будь то динамический язык или статически компилируемый.
Ну и за eval нужно руки отрубать.
В динамическом языке вы сами понимаете это можно и без DI сделать

Можно, а нужно ли? собирать имплементацию интерфейса в строке и запускать её через eval чем лучше обычного DI контейнера?
Это уже другой вопрос. Но обычно в чистом виде вот у нас тут DI в фреймворках динамических языков я не встречал.
Чистый вид это какой?
Четкое выделение DI внутри фреймворка. Это я видел только в статически компилируемых языках.
Пример кода можно?
С выделенным DI — я как-то не могу сообразить чем он отличается от невыделенного. Как по мне DI либо есть, либо нет.
Ну вот того что вы ниже привели я обычно в фреймворках для динамических языков не наблюдаю.
что вы подразумеваете под «четко выделенным di»? отдельный dependency injection фреймворк? Так есть, и даже не один. symfony использует symfony/dependency injection, который можно юзать отдельно в любом другом проекте. Если брать какой silex то можно опять же подключить отдельно dic и работать через него.

По поводу классического di, где в конфигах прописываются только соответствия интерфейса реализации, а выбор зависимостей идет исключительно по тайп хинтам в создаваемых сервисах я пока не встречал, ибо почти все реализации DiC рассчитаны еще и на передачу параметров, и подмена реализации обычно осуществляется переопределением параметров (пусть к классам, названия сервисов выносятся как параметры).
что вы подразумеваете под «четко выделенным di»? отдельный dependency injection фреймворк? Так есть, и даже не один.

Именно его. То что он есть я вполне верю. Но вот в динамических языках я как-то явной работы с ним почти не где не видел. Возможно связано с тем что мне просто они не попадались.

По поводу классического di, где в конфигах прописываются только соответствия интерфейса реализации, а выбор зависимостей идет исключительно по тайп хинтам в создаваемых сервисах я пока не встречал,

В java именно так. Когда идет использование DI идет указание интерфейса и далее аннотация на связывание.
Когда DI осуществляется конфигом типа
<parameters>
    <!-- ... -->
    <parameter key="mailer.transport">sendmail</parameter>
</parameters>

<services>
    <service id="mailer" class="Mailer">
        <argument>%mailer.transport%</argument>
    </service>

    <service id="newsletter_manager" class="NewsletterManager">
        <call method="setMailer">
             <argument type="service" id="mailer" />
        </call>
    </service>
</services>

А работа с ним происходит по типу:
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;

$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.xml');

// ...

$newsletterManager = $container->get('newsletter_manager');

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

Вроде и аннотациями можно, но я их не очень люблю.
Как правило динамичность сильно уменьшает понятность, сложно удержаться от перевода его в магию.

Ну DI с аннотациями тоже магией попахивает. При этом довольно сильно. Тут больше вопрос в том чтобы динамичность была единообразна.

Вроде и аннотациями можно, но я их не очень люблю.

Вот когда когда много все же лучше делать аннотациями. Проще объяснять и проще читать. Достаточно воспринимать все объявления как декларативное программирование. Чем оно по сути и является.
Статика должна умереть. То что на самом деле это не статика а фасад никак не влияет на тот факт что программист использует как раз статику и поэтому учится плохим подходам.

Фреймворк в первую очередь должен быть написан так чтобы учить новичка как надо делать лучше
Страное требование
Вы забыли о главном плюсе — огромном количестве хороших пакетов с расширениями. Печально только что главное расширение — это IDE helper, без которого писать код придётся без автокомплита.
Мне уже довелось видеть примеры плохого кода на laravel — без пакетов, без миграций, со своими велосипедами, которые ломают возможность пользоваться пакетами и вовсе консоль. Так что laravel — это не для всех.
Плюсов, на самом деле, очень много, описывать их очень долго. Проще и полезней заглянуть в документацию и своими глазами все увидеть. В стиле Laravel еще нужно научиться писать т.к. другие фреймворки не имели такой философии. Некоторые не считают Laravel чем-то таким особенным, однако если приглядеться и попробовать что либо написать на нем используя все возможности — влюбляешься в процесс.
То же самое могу написать про symfony2 или zend. Только для того что бы оценить возможности фреймворка — его нужно знать. С большими фреймворками, порог вхождения в которые по выше, это проще, так как человек походу дела разбирается с возможностями фреймворка и использует их по максимуму, либо сдается и выбирает что-то по проще (yii/laravel тому пример). Если же брать yii с их философией «мы не навязываем все эти fancy-паттерны» можно наблюдать кучу говнокода в проекте. Во всяком случае сколько проектов видел, везде какие-то кастыли да встретишь, причем такие что аж дух захватывает. Laravel мне в этом плане напоминает yii на компонентах симфони.
В говнокоде на yii и тем более laravel — можно легко разобраться, а вот эпик на zf или sf лучше переписывать с нуля. Кстати, показательно, что на недавно fw дне все рассказывали о фичах фреймворков, а symfony-сты о косяках и типичных ошибках.
Фреймворк ни от чего не спасает, другое дело что он может сделать ошибки фатальными.
Эпиков на yii и на laravel хватает. Так же как и на symfony и в zend и в ror и в django. Это есть у каждого фреймворка в любом языке программирования.
А может кто-то объяснить мне про фасады?

Насколько я понимаю, они решают задачу тестирования (т.е. подмены реализации), на которую ссылаются все противники статических вызовов.
Но как быть с тем, что использование статических членов очень неявно? Пример:

Экземпляры:

$foo = new Foo;
$foo->prop = 10;
do_something($foo); // явная передача экземпляра
var_dump($foo->prop); // очевидно, что здесь может быть не 10

Статика:

Foo::prop = 10;
do_something(); // внутри делает Foo::prop = 20
var_dump(Foo:prop); // я ожидаю увидеть 10

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

Или я ошибаюсь?
Нет, фасады существует не для этого.
Прочитайте про них в wiki.
Я говорю о статических фасадах в контексте Laravel.

Обычно разговор об этом фреймворке происходит так:

Ларавельцы: зацените клевый фреймворк!
Толпа: там статика, фуууууу!
Ларавельцы: а что с ней не так?
Толпа: ее нельзя тестировать (куча ссылок на статьи)
Ларавельцы: так у нас же фасады! смотрите, как они клево тестируются! (пример)
Толпа: о, класс! и вправду круто!

Тестируется? Ок. Но мне не ясно как быть с этим:

Cache::put('foo', 'bar', $minutes);
do_something1();
do_something2();
do_something3();
do_something4();
var_dump(Cache::get('foo')); // baz

Кеш возвращает не то, что я ожидаю. Мне надо перерыть все вызовы от Cache::put до Cache::get.
Мне одному кажется, что это проблема?
Проблема статиков в том, что они полностью игнорируют ООП при проектировании. Статики, по сути — это функции объеденные в namespace. Соответственно используя везде статики, мы не можем полагаться на SOLID.
Если при использовании ООП, у многих хоть как то сложилось в голове своя методика, как писать структурированный код, то в данном случае, очень большая вероятность понаписать кода, который очень тяжело будет поддерживать, т.к. ООП — это в первую очередь рамки, которые позволяют другим программистам понимать ваш код.
Лайфхак: если встречается код, где много статиков, то можно думать о таких классах как об экземплярах метакласса и считать код типа:
User::init();
$user = User::findById($id);

аналогом кода:
$userMeta = new UserMetа();
$userMeta->init();
$user = $userMeta->findById($id);

что позволяет оперировать принципами SOLID при проектировании, реализации и рефакторинге.
Эм, простите, но не соглашусь абсолютно.
Я хочу показать всем, какую показать в каком месте мой класс может быть расширен (за счет protected) — тут я этого сделать не могу.
class UserMeta {
    protected function getFields() {
       /* какой то код */
       return $fields;
    }
}


Что-то сделать с интерфейсами со статиками вообще невозможно. Аналога со статиками нет, тут просто спрячется зависимость внутри метода, что не хорошо.
public function setUserMeta(UserMetaInterface $userMeta) {}


Я не могу не думать о целостности данных.
User::init();
/* тут может быть вызов любой функции или любого статика */
$user = User::findById($id);

Всё, тут я уже не могу гарантировать, что findById будет работать так же, как если бы не было кода в месте, указанном комментарием. Если же использовать экземпляры, то я явно увижу, куда экземпляр передается.

На самом деле — статики, это не только невозможность тестирования — это глобальный факап на уровне архитектуры системы. Статик — это упрощение, когда не охота думать о идеологии построения приложения.
В Yii это «упрощение» сделано лишь для того, что бы можно было просто оперировать с теми сущностями, которые в веб-разработке точно являются singleton. Например — приложение. Когда вы в любом месте программы, у вас точно есть одно и только одно приложение. За счёт этого Yii очень сильно выигрывает в простоте при проектировке over 95% приложений для web, где они за счет «упрощения», ввели аспект единого приложения для всего кода.

Ковырял Laravel всю последнюю неделю.
Не понравилось отсутствие Identity Map, т.е. следующее:

class User extends Eloquent {}

$user1 = User::findOrFail(1);
$user2 = User::findOrFail(1);

print_r($user1 === $user2); // false

Как вы считаете, важно ли наличие этого паттерна в используемом фреймворке?
Имхо, это удобно, но не критично, если об этом предупрежден. Куда больше напрягает отсутствие UnitOfWork, когда за каждой сущностью нужно следить «руками» и не забывать её сохранять.
Ошибся веткой, продублирую здесь.

А где это реализовано? Symfony?
Правильно понимаю, что в этом случае не нужно явно вызывать метод save() у модели т.к. фреймворк не забудет сделать все сам?
В Doctrine 2 ORM — в Symfony 2 она входит по умолчанию, но вообще друг от друга независимы, часто прикручивают её и к другим фреймворкам.

Не нужно вызывать save() у каждой сущности, но один раз вызвать метод типа flush() всё же придется, но там уже uow пройдется по всем сущностям и сохранит всё что нужно (если надо и в одну транзакцию обернет). Может даже можно сделать автоматический вызов flush(), но лично я предпочитаю такие вещи на автоматику не перекладывать.
не совсем все так. persist сущностей вы всеравно делаете. Просто записываются они в базу только по flush. Для связанных сущьностей можно настроить каскадный персист, и тогда uow сам все сделает за вас. Но опять же этот подход не особо рекомендован.

С другой стороны удобнее штуки чем doctrine (во всяком случае в контексте реляционных баз) я не встречал.
Да, но persist это не сохранение как таковое. Это сообщение менеджеру, что с данного момента он отвечает за новую POPO сущность. Касается это только новых сущностей, создаваемых обычно через new Entity(); в Create действиях, об уже существующих (читай — полученных из базы) менеджер и так знает, собственно он их оттуда и достал и передал приложению.

Пишу не столько для вас, сколько для незнакомых с Doctrine 2 людей вообще, и aazon в частности.

P.S. В Python есть нечто подобное Doctrine 2, а в Ruby вроде как нет :)
А где это реализовано?  Symfony?
Правильно понимаю, что в этом случае не нужно явно вызывать метод save() у модели т.к. фреймворк не забудет сделать все сам?
, Это реализовано в doctrine (если вы о unitOfWork). К хорошему быстро привыкаешь.
Sign up to leave a comment.

Articles