Pull to refresh

Comments 34

> RFC: Parameter Type Widening

Какой кошмар. А как же принцил Лисков?
А что плохого с принципом случится, если foo все еще будет работать с (в том числе и) массивом?
А как же принцил Лисков?

Вы либо плохо читали RFC, либо подзабыли LSP. LSP разрешает расширение входных параметров и данный RFC как раз таки направлен на то, чтобы соответствовать LSP.

Т.е. если у вас есть класс-наследник с методом, расширяющим сигнатуру базового, то это не противоречит LSP, но в то же время позволяет оперировать терминами будущей совместимости (например вы можете расширить класс, чтобы они принимал не только array, но и \Traversable, и задеприкейтить старый для следующего мажороно релиза). В нынешней реализации это вызовет ошибку
Оно как раз не нарушает LSP. Поскольку, применительно к ООП LSP говорит о том, что субтип (дочерний класс) должен расширять родительский класс (интерфейс).
Представим, что есть базовый класс:
class Parent
{
    /** @param string|int $bar */
    public function foo($bar)
    {
        ...
    }
}

Согласно «контракту» метод foo должен принимать на вход аргумент строкового или целочисленного типа.

И есть использование:
/**
 * @param Parent     $qux
 * @param string|int $arg
 */
function baz(Parent $qux, $arg)
{
    $qux->foo($arg);
}


Тут мы говорим о том, что на вход должен поступить объект, являющийся инстансом класса Parent, и в метод foo этого объект мы передаём аргумент. Так как контракт метода foo говорит о том, что на вход должен поступить либо строковой либо целочисленный аргумент, то и переменная $arg тоже должна быть либо строкового, либо целочисленного типа.
Тут всё логично.

Теперь представим, что есть класс
class Child extends Parent
{
    public function foo(int $bar)
    {
        ...
    }
}
, перегружающий метод foo и сужающий его ОДЗ.
В этой ситуации мы инстанс класса Child уже не можем передать в функцию baz поскольку метод в дочернем классе сужает ОДЗ, а значит мы не можем передать в метод foo аргумент строкового типа. Таким образом мы имеем нарушение LSP.

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

Таким образом, LSP говорит о том, что субтипы не должны сужать супертип. В том числе, переопределение метода в субтипе может расширять ОДЗ метода, но не должно сужать его.

Извините, если ответ получился запутанным или сумбурным.
PHP код, который выводит буквенно-цифровые символы не используя ни один из них
$__='`@`@'^'*/).'; // $__ = 'JoIn'
$_='->.<:'^'__@[_'; // $_ = 'range'

$__($_('>'^@_,'%'^@_)) // join(range('a', 'z'))
Подскажите, как это преобразовывается?
Ужасно, но php позволяет применять бинарные операторы к строкам и результат будет строкой.
If both operands for the &, | and ^ operators are strings, then the operation will be performed on the ASCII values of the characters that make up the strings and the result will be a string.
http://php.net/manual/en/language.operators.bitwise.php
С юбилеем, однако! Спасибо вам за отличный источник информации, желаю не терять энтузиазма и продолжать в том же духе!
Мое лицо, когда Тейлор говорит о справедливых бенчмарках.

Справедливости говоря, на крупных проектах Ларка действительно даст фору Симфони (могу лишь сравнивать с ней, опыта с Zend маловато, чтобы утверждать что-то наверняка). Вся фишка в схеме работы и архитектуре:


Внутри Симфони
1) Сбилженный контейнер со всеми сервисами (frozen)
2) Каждый запрос обрабатывается тучей эвентов из разных мест
3) Внутрь контроллеров всовываются в конструктор сервисы через DI
4) Каждый запрос обрабатывают тучи гвардов


Внутри Ларки
1) Контейнер создаётся лениво, в зависимости от поведения приложения
2) Вместо кернел эвентов миддлвари, которые вешаются на определённый запрос, а не на все
3) Сервисы в контроллеры попадают через двойную диспатчеризацию (т.е. как объект Request в контроллеры в симфони)
4) Гвардов в ларке вообще нет, их ответсвенность берут на себя миддлвари, которые см. п.2 — на каждый запрос (или группу запросов) свои.


Подытоживая — из подобной схемы работы ларка более тяжеловесна в сыром виде, симфони же после билда контейнера (в продакшн режиме, например) — летает, но при увеличении количества бандлов в проекте на симфони — жручесть его увеличивается по экспоненте, а ребилд всего в дев режиме может достигать десятков секунд. Доходило до такого, что обычная страничка в сонате (правда с xdebug) отжирала до 100+ мегабайт. В случае же ларки потребление оперативы почти не растёт, т.к. добавление провайдера лишь добавляет дефинишн (зачастую интерфейс, а не алиас сервис-локации, как в симфони) для DI, а он уже загружается когда потребуется в том месте, где он используется и только в нём. Т.е. чистый старт ларки содержит почти что пустой контейнер, в тоже время в симфони он полностью заполненный из всех бандлов.


P.S. Я люблю оба фреймворка, они оба замечательные по-своему =)

1) Симфони создает сервисы лениво. Более того, с ocramius/proxy-manager оно может даже создавать супер-ленивые сервисы (не инициализируются до использования, а не до инстанциирования зависимых). Приведенный бенч (опустим вопрос его предвзятости) показывает, что сбилженный конейтнер имеет свои бонусы по производительности.

2) Можете объяснить принципиальную разницу между мидлварью и листнером на пару событий kernel.request + kernel.response? Я думаю можно однозначно отобразить одно на другое, только вместо замыканий мидлвари отрабатывает EventDispatcher

3) Вот это выглядит странно в Ларе, если честно. Трендовая парадигма Симфони «Controller as a Service» выглядить более логично тут. У Лары это больше похоже на ФП.

4) Можно про тучу гвадров подробней? Если вы про секьюрити гварды, то их не туча, а в зависимости от сматченного фаервола. Если про другие гварды — то нужна конкретика. И, отдельно, чем «туча гвадров» лучше тучи миддлварей?

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


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


По остальным пунктам никаких возражений, каюсь =)

В симфони тоже можно реализовать подобное. Технически, роутинг в симфони — это просто некоторая дыра, которая возвращает вам \callable по данным запроса (по pathinfo в самом простом случае). В пайплайне обработки запроса есть момент, когда этот \callable уже известен, и есть специальное событие, которое позволяет его обработать или подменить. Например, навешать те же миддлвари.

Технически, завязыватсья на роутинг в этот момент сложновато, т.к. в симфони нет «этих ваших» Route::get, роута как такового вообще может не существовать, как сущности (вспоминаем про дыру).

Но если сделать некоторые стандартные допущения (что мы работаем исключительно со стандартным роутингом), то можно получить текущий роут и в зависимости от него (индивидуально, по группе — это уже детали реализации) навешивать миддлвари.

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

А теперь встречный вопрос — насколько просто в Ларе будет установить те же мидлвари, скажем, считав аннотации у контроллера?

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


P.S. Я против роутинга на аннотациях, т.к. в этом случае невозможно указать приоритет, но если надо, то проще воспользоваться готовым: https://laravelcollective.com/docs/5.3/annotations#routes

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

То, что не все реализуют — это 100%, именно потому, что как я уже сказал, такая реализация (в ядре) привнесет очень много привязок к конкретным решениям. А симфони — оно про заменяемость компонент.

Про аннтоации — вопрос был не конфигурации роутов, а о расширяемости текущией схемы добавления мидлварей в Ларе. Переформулирую. У меня есть группа контроллеров, произвольным образом промаркированных (аннотации, тэги у сервисов, что угодно). Как будет выглядеть конфигурация мидл-варей для них?

Так проблем в дефолтной поставке симфони никаких нет, всё летает. Но когда последний раз вы видели require у composer.json в симфони из пары-тройки строк? Я не хочу доказать, что N медленнее M де-факто, цель донести мысль о том, что архитектурные решения ларки чуть более эффективны для расширения благодаря ленивости загрузки и "точечности". Даже конфиги в пакетах под ларку почти всегда инициализируются и читаются только после того, как создаётся сервис, который их требует.


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

Я примерно о том же. Ларка позволяет делать «более просто» за счет того, что некоторые решения фиксированы (ну по крайней мере, оно так выглядит, я могу ошибаться).

Я правильно понимаю, судя по вашему ответу, что запуск мидлвари, конфигурирующей другие милдвари (т.е читающая аннотации, каждый раз или проверяющая весь контейнер на предмет тегов), будет производиться на каждый запрос? В чем отличие от листнера, который будет проверять, нужно ли запускать другие миддлвари|листнеры|%tool% на каждый запрос?

Как эта мидлваря получит аннотацию конечного контроллера, если поверх него могут быть натянуты еще мидлвари?

Дефолты, если что, тоже можно повесить на группу роутов, если быть точным, то на импортируемый ресурс (yml, аннотации, что угодно). Еще есть options, которые можно устанавливаются и на импорты

1) Не совсем, скорее за счёт того, что никакой фиксации и нет, а провайдер регистрирует лишь сервис в контенере, в отличие от бандлов, требующих довольно жёсткое соблюдение правил именования в т.ч. Указние конфигов и ресурсов в ларке лишь декларация, мол тут у нас A, а тут Б, никаких разборов дерева конфигов.


2) Нет, в ларке нет аннотаций. Как следствие — всё зависит от реализации и в частности о того ридера, что используется. Отличие листенера в от миддлварей в том, что миддлваря жёстко фиксирует вход и выход и инициализируется только в момент аппрува роута. Листенер же подписывается (и создаётся объект с резолвом DI в том числе) на каждый запрос.


3) Как реализуете — так и будет. Но по логике это не задача миддлварь читать аннотации роутинга. Это скорее задача некого сервиса, который добавит их в список готовых при старте приложения. Например внутри Http Kernel, обязанность которого как раз и является загрузка роутов.


4) Я и написал (в самом конце), что дефолтсы можно вешать на группу лишь ссылаясь на внешний ресурс, что вполне годно, но не всегда удобно.

1) Нет никаких жестких правил ). Жесткие правила нужны только в том случае, если вы хотите делать все автомагически (ну, типа quick-start). В остальных случаях вам достаточно имплементировать BundleInterface и зарегистрировать его в ядре. Аналогично с конфигами, расширениями и прочими конфигурациями.

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

2) В симфони тоже нет аннотаций, это формально внешняя библиотека (doctrine/annotations), хоть и рекомендуемая к использованию.

Ну в целом все ваши ответы сводятся к «как реализуете» (и это нормальная позиция), поэтому я считаю дальнейшие распросы бессмысленым, спасибо за информацию. Обычно действительно большая часть зависит от рук разработчика и авторов библиотек.

в Symfony 3.3 сделали ленивые листнеры, в том числе, посмотрите. Я думаю это решит многие из озвученных вами проблем (по флуду листнерами)

Еще раз спасибо за разъяснения

1) Ну это конечно верно, с другой стороны фреймворк всё же ограничивает. Например невозможно указать несколько директорий для сущностей одновременно, работая по DDD модели. Или подключить 10 бандлов внутрь другого, аггрегирующего. и т.д.


2) О, с за ссыль огромное спасибо. Жаль только что у меня в работе пока текущий 2.8 LTS, не скоро насложусь новыми фичами =(

1а) Можно. Потому что, внезапно, доктрина — это не симфони. Поэтому можно долить конфигурацию так, чтобы грузило несколько папок
1б) https://github.com/symfony/symfony/pull/15011#issuecomment-183884227 этого периодически действительно не хватает, но решение вроде как принципиальное на данный момент. Но есть сторонние имплементации https://github.com/mmoreram/symfony-bundle-dependencies

1а) И всё же doctrine bundle, в отличии от doctrine orm — это часть симфони в некотором роде. И именно она не позволяет указывать в конфигах массивчик папочек для сущностей. Но, верно подмечено, можно переписать этот бандл, чтобы работало. Другой вопрос: а не проще ли забить и "не выпендриваться"? =)
1б) Ещё чего не хватает в симфони ещё двойной диспатчеризации как в ларке. И регистрации сервисов под интерфейсами, а не под алиасами.

1а. Ну можно. Вы можете докинуть в метадата-фактори любой альяс + любую папку. Просто это не происходит автоматически.

http://symfony.com/doc/master/bundles/DoctrineBundle/configuration.html#configuration-overview вот простыня конфига, там строчка 309. В ней вы можете указать дополнительные мэппинги сверх того, что регается само. В том числе другие папки.

Переписывать вообще ничего не требуется. Требуется конфиг написать

И нет, доктрина не является частью симфони, это независимый проект, как и твиг, например.

1б. Если вы про передачу в контроллер через аргументы, то это есть, в виде парам конвертеров, аргумент-резолверов. Да и вообще вы можете пробросить любой аргумент руками в action через аттрибуты запроса.

Посмотрите, например, как реализован проброс UserInterface с версии 3.2

https://github.com/symfony/symfony/pull/18510

Регистрация сервисов под интерфейсами есть как автовайринг (вы можете просто объявлять зависимость от интерфейса, в него подтянется сервис, если он единственный имплементирует его, если нет, можно уточнить один раз)

А с версии 3.3 идентификатор сервиса будет опциональным
http://symfony.com/blog/new-in-symfony-3-3-optional-class-for-named-services

Идея регистрировать сервис сразу под всеми имплементируемыми интерфейами интересна, но 100% будут конфликты у всяких логгеров и других сервисов, которые генерятся фабриками

Ладно, убедили в том, что я отстал от этой жизни с этим симфони 2.8… =( Буду ждать конца года, там новый LTS должен подъехать, вот тогда и заживём!


Огромное спасибо за эти подробности, невероятно полезная информация (ну лично для меня).

Я не к Laravel в частности, а касательно Lumen-а и бенчмарков, которые Тейлор приложил к нему.
А так, сейчас специально замерил два своих приложения — одно классика веб, второе апи, оба локально, dev режим, xdebug, sf 3.2, в каждом по 2-3 десятка бандлов, БД тоже локально в виртуалке (обычный рабочий комп)

Веб:


Апи:


Не вижу ваших сотен метров. То, что простая страничка сонаты отжирает сотню — это вопрос к самой сонате, которую лично я не долюбливаю. Симфони тут не причем.
Это blackfire на скринах?
Нет, родной встроенный профилировщик symfony

Блэкфайр в прод режиме выдает по веб приложению вот такую картинку
blackfire.io


Избавляемся от статических вызовов

Это очень сомнительно, как по мне…

З.Ы. С юбилеем!
Есть еще новость: Yii2 Application Development Cookbook, Дмитрий Елисеев, вчера опубликовал в своем блоге. Он работал над завершением этой книги.

Статья про уязвимость майлера для тех кто не проверяет отправителя. Но вообще странно, почему эта уязвимость есть, майлеру -то сколько лет…
Есть еще новость: Yii2 Application Development Cookbook, Дмитрий Елисеев, вчера опубликовал в своем блоге


в декабре еще вышла, новость есть в прошлом, 99-м, выпуске
ваша правда. Дмитрий так рассказывал про неё, создалось впечатление, что это первая книга с печатного станка. Но теперь мы можем прикинуть, сколько по времени занимает доставка ))
RFC: Parameter Type Widening
Implementing this RFC would allow libraries to be upgraded to use type declarations in their method signatures. Currently adding types to a method of a class in a library would break any code that extends that class.

Если это делается исключительно для того, чтобы обеспечить совместимость старых пакетов с PHP7 + strict types, то я категорически против.
Sign up to leave a comment.