Как стать автором
Обновить

Комментарии 571

НЛО прилетело и опубликовало эту надпись здесь
Согласен, получилось скорее hello, world SPA. Но сейчас SPA со страшной силой шагает по планете. Зайдите в любую современную админку банка и т.д. — там будет реакт. Мне кажется, надо уметь хотя бы хелловорлд всего этого дела.
SPA должен умереть…

отчего же? Это же волшебно когда UI на клиенте это не статическая страничка а луп отрисовывающий стэйт! А если еще и выстроен как композиция реюзабельных компонентов, ух… А бэкэнд stateless и в целом не зависит от UI особо, крути верти как хочешь. Дикий простор для творчества. Для определенного процента проектов можно вообще без бэкэнда обходиться благодаря всяким там firebase-ам и похожим сервисам. Идеально для CRUD-like приложений, да и с кодогенерацией для UI и реюзом оного как-то получше дела обстоят.


Конечно же это не для всех проектов/команд подходит… И надо фронтэнды уметь, с чем сейчас тугова-то у людей. То есть свои жирные минусы несомненно есть...

Мне не нравится JSX из-за смешивания логики (кода) и шаблона. В Knockout.js, который я использую, логика отдельно, шаблон data-bind отдельно. Это позволяет использовать общий шаблон с разными JS классами, что очень удобно. Никаких транспилеров и кучи node.js приложений не нужно, все работает на ES5 без какой-либо прекомпиляции. Сам Knockout.js очень небольшой и содержит все что нужно.

SPA плохи двумя вещами — слабой индексацией в поисковиках и глюками старых браузеров. Я больше использую AJAX компоненты чем чистое SPA. Хотя для админки SPA действительно хорош — там не нужна индексация и вряд ли кто зайдет старым браузером.
Я раньше также думал. Пока не попробовал.

В JSX есть логика, но это только view логика.

Когда вы в handlebars пишете
  {{#if author}}
    <h1>{{firstName}} {{lastName}}</h1>
  {{/if}}


ведь это тоже смешение тегов с логикой отображения. Просто в jsx наоборот, смешивается логика с тегами. Что дает определенные преимущества: не надо сочинять свой шаблоноязык, а использовать чистый js.

Разумеется, бизнес-логику нельзя пихать в react-компонент, ни в коем случае. Для бизнес-логики надо юзать redux (или еще какой-нибудь flux), обмениваясь с компонентами событиями и т.д.
Разумеется, бизнес-логику нельзя пихать в react-компонент, ни в коем случае.

Нельзя смешивать в одном компоненте бизнес-логику и логику отображения. Разделять вполне можно.

Когда вы в handlebars пишете

Так не пишите, оставьте шаблоны простыми.

Ну вот кстати именно поэтому на первый взгляд менее удобные mustache в итоге оказываются более поддерживаемым решением. Ибо вся логика оттуда искусственно вытеснена в контроллеры.

из-за смешивания логики (кода) и шаблона.

Twig/Nunjucks/Jinja2


<ul>
{% for item in list %}
   <li>{{ item}}</li>
{% else %}
   <li class="empty">List is empty :(</li>
{% endfor %}
</ul>

Angular


<ul>
  <li *ngFor="let item of list">{{item}}</li>
  <li class="empty" *ngIf="!list.length">List is empty :(</li>
</ul>

React JSX


render: function() {
    return (
        <ul>
          {this.props.list.map(item => <li>{item}</li>)}
          {this.props.list.length == 0 &&
             <li class="empty">List is empty :(</li>
          }
        </ul>
      )
    }

Knockout.js


<li data-bind="foreach: list">
   <span data-bind="text: $data"></span>
</li>
<li class="empty" data-bind="if: pets().length == 0">List is empty :(</li>

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


все работает на ES5 без какой-либо прекомпиляции.

Я уже не могу писать просто на es5 без всяких бабелев… к хорошему быстро привыкаешь.


SPA плохи двумя вещами — слабой индексацией в поисковиках и глюками старых браузеров.

На счет браузеров — согласен, хотя с каждым годом ситуация меняется в лучшую сторону. Ну а по поводу индексации — приведу пример. angular-universal. Server-side рендринг в пол пинка (ну как. относительно конечно).

Тем не менее в случае knockout / angular мы имеем самый минимум логики в шаблонах (конечно можно запихнуть в data-bind JS-код на несколько строк, только это даже человеку не любящему красоту кода будет очевидно выглядеть плохо), а react как-бы поощряет запихивать побольше кода в шаблон. Понятно что хороший программист будет сдерживать себя.

И еще момент с наследованием этого дела. В ES5 и тем более в ES6 можно прототип метода у потомка заменить и пожалуйста — тот же шаблон knockout.js с другим кодом. В то же время в популярном Vue.js с наследованием оказались огромные проблемы вообще не рекомендуют использовать. Хотя на мой взгляд UI без наследования это нон-сенс.

es5 хорош тем что быстрее проект разворачивать (node.js и прочее не нужны) а также сторонний код можно на лету править.

На react может конечно и надо переходить так как спрос на него огромен, хотя идеологически коробит меня от него, привыкнуть ко всему можно. Куча уже существующего кода на Knockout.js работает и вроде как непонятно зачем. Разве что из-за денег.
Тем не менее в случае knockout / angular мы имеем самый минимум логики в шаблонах

присмотритесь. Количество логики ровно одинаковое. Разница лишь в синтаксисе. Причем в этом плане у jsx есть преимущество перед knockout — не надо изучать новый синтаксис — все работает на обычном js. Как и в underscore templates например и куче других шаблонизаторов.


es5 хорош тем что быстрее проект разворачивать

yoman, кучи скафолдингов, шаблоны проектов и т.д. решают эту проблему. А за счет es2015+ скорость разработки и удешевление поддержки быстро окупает эти вещи.


На react может конечно и надо переходить

В целом я фанат ангуляра так что… я не перейду)

React версию можно отрефакторить до такого:

render() {
    let {list} = this.props;
    return (
        <ul>
            {list.map(item => <li>{item}</li>)}
            {!list.length && <li className="empty">List is empty :(</li>}
        </ul>
      )
}

да, так поприятнее. Тогда разница вообще ничтожна.

$mol:


$my_pets $html_ul
    Pet_row!index $html_li
        sub /
            <= pet_name!index \
    No_pet $html_li
        sub /
            <= no_pet_messsage @ \Not pets :(

sub() {
    if( this.items().length === 0 ) return [ this.Empty() ]
    return this.pets().map( ( name , index )=> this.Pet_row( index ) )
}

pet_name( index : number ) {
    return this.pets()[ index ]
}

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

Не this.Empty(), а this.No_pet(), конечно же. И с наследованием тут всё хорошо (прям отлично). И нет, это не "самопиар", а объяснение, как та же проблематика может быть решена лучше.

Ваш пример не особо лучше. Вы все равно изувечили html, напихав туда свой язык. Как и в других шаблонизаторах

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

На таком простом примере преимуществ не видно

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

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


При разделении композиции и логики вы просто пишете, например:


$my_app $mol_view
    as_card true
    sub / <= Card $mol_card
        sub / <= Info $mol_labeler
            title <=  info_title @ \Your salary
            content / <= Salary $my_currency
                value <= salary null

А потом добавляете отдельно любой сложности логику:


sub() {
    return [ this.as_card() ? this.Card() : this.Info() ]
}
На любом более-менее реальном.

То есть вывести список — не реальная задача?


Ну, банальная задача: в зависимости от флага заворачивать содержимое в дополнительную обёртку.

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

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


Так тут родитель (блок $my_app) и управляет всеми своими элементами (Card, Info, Salary).

Вывести список строк в одиночном теге — не реальная

Очень реальная. Пример — вывести табличку с заголовком строки в виде ФИО клиента и одним из столбцов через запятую номера заказов.

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

Сложно по бумаге кликать.

Версия для печати — очень частный случай.

Большинство отчётов не нуждаются в интерактивности, даже если формально не печатаются.

У нас ровно противоположная статистика.

У нас отчёты — это отчёты. Если отчёт подробный, то там всё и так есть. Если агрегированный, значит детали никого не интересуют. Были попытки получить интерактивность у отчётов, но и показали другой инструмент и сказал, что это то, что нужно.

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


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

Значит ваша выборка "реальных случаев" не может быть репрезентативной.

А какая может? Будет чудесно, если вы приведёте ссылку на репрезентативную статистику.


Да и вывести ссылку — не сильно сложная проблема

Я говорил про не только ссылку.


"Мутация" контейнера детьми я расцениваю больше как какой-то костыль

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


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

Реализуйте мой пример по проще :-)

Реализуйте мой пример по проще :-)

<my-card *ngIf="asCard()"></my-card>
<my-info *ngIf="asInfo()"></my-info>

Если я правильно понимаю о чем ваш пример.

Нет, типичная реализация того же на Ангуляре будет выглядеть так:


<my-card *ngIf="asCard()">
    <my-info>много тегов</my-info>
</my-card>
<my-info *ngElse>копипаста</my-info>
типичная реализация того же на Ангуляре будет выглядеть так:

Эм...


  1. в Angular нет ngElse. Это атрибут элемента, он ничего не знает о соседях.
  2. Вся "копипаста" выносится в отдельный компонент. И компонент-родитель будет рулить какой выводить и с какими параметрами.

Ну то есть мэйнстрим подход, UI как композиция UI компонентов.

  1. Тем более, значит ещё и условие повторить надо.
  2. Ну то есть вводится специальный компонент со своим конфигом, с пробрасыванием всех параметров. И это не потому, что требуется декомпозиция в этом месте, а потому, что шаблонизатор настолько дубовый, что не способен переставить блок в другое место.
  1. не то что бы повторить, у вас должен быть красивый и удобный метод во вью модел который проверяет это условие, и там где надо просто надо добавить отрицание. Логика провери же будет инкапсулирована.


  2. Шаблонизатор не должен быть умным. Вся логика во вью модели, а шаблоны просто шаблоны. Ну и да, "умный" компонент-контейнер выгоднее "умного" универсального компонента. Это как раз более качественный уровень декомпозиции.

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

  1. Суть от этого не меняется — нужно повторить вызов метода. А если надо будет привязаться к другому методу, то оба вызова надо будет поправить.


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

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

А если надо будет привязаться к другому методу, то оба вызова надо будет поправить.

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


Именно, поэтому в "шаблонах" вообще не должно быть логики

так ее и нет, вся логика в презентерах.


От того, что вы разобьёте один компонент на два сильносвязанных

компоненты не могут быть "сильносвязанными". Они либо имею связи/зависимости либо нет.

В одно месте вам нужно написать "asCard()", в другом "!asCard()". А если нужно первый блок показать при выполнении условий А и Б, второй — Б и В, а третий — А и С? Да, вы можете создать отдельные методы вида "isFirstBlockShowing", но:


  1. так мало кто сделает.
  2. писанины будет ещё больше.

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


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

Выносить в отдельный компонент имеет смысл лишь переиспользуемую логику.

Очень спорное утверждение, противоречащее многим общепринятым принципам проектирования типа того же принципа единственной ответственности.

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

не несёт какой-то уникальной ответственности.

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


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


Ангуляр не фосирует вынос логики, а наоборот поощряет её написание прямо в шаблоне.

в целом — да, но можно во вью модели логику подублировать всегда. У разработчиков всегда будет возможность сломать инкапсуляцию и продублировать логику. С этим надо бороться другими способами.


Могут. Выделяя как попало компоненты вы получаете такие вложенные

Есть такая проблема. Нужна нормальная декомпозиция, а многие разработчики почему-то этого не умеют.

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

Решение в лоб


ProductCard = (product) => 
  <div>
    <ProductImage value={product.image}/>
    <ProductName value={product.name}/>
    <ProductDescription value={product.description}/>
    <ProductPrice value={product.price}/>
    <ProductTags value={product.tags}/>
  </div>;

LinkableProductCard = ({product}) => <a href={product.link}><ProductCard product={product}></a>;

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

Всё строго по ТЗ :) Единственное замечание которое принимаю сходу — title в ссылку, но только как фичереквест. О всём остальном вы не можете судить по этому фрагменту, не зная схемы объекта product. А div не лишний, а несёт семантическую нагрузку — отделяет карточку продукта от остального документа, делает её самостоятельным элементов, на который, в числе прочего, можно ссылаться в файлах стилей. И компоненты лишними не считаю, даже если они не будут переиспользоваться — они улучшают поддерживаемость кода. Вот захотели вы валюту указывать — я джуну скажу "там есть элемент ProductPrice — добавь наименование валюты", а без компонентов придется "там есть семиэтажный Product, поройся в нём, найди часть, где цена выводится и добавь наименование валюты"

ТЗ изменилось. Сколько вам времени потребуется на рефакторинг всего проекта под новые требования, описанные в предыдущем сообщении?


Вы на имя тега будете ссылаться в файлах стилей?


Для отображения валют в разных местах имеет смыл создать компонент CurrencyValue. Отображать ProductPrice вне ProductCard было бы странно.

title для ссылки откуда брать? product.name или product.link.title? Если первое, то до десяти минут, включая деплой на стейдж- сервер. Если второе — ждём бэкенедеров и плюс 10 минут.


Пока в проекте только одно место, где нужно отображать. Появится второе — будем смотреть что в них общего.

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

карточка должна быть ссылкой

Согласно ТЗ и реализовано. Никаких "внутри" в ТЗ нет. Ели опять ТЗ меняется, то уточните, что значит "внутри". И почему не могу завернуть ссылку в ссылку?

image

Работает?

Нет, работать оно будет либо в режиме XHTML, либо если вы создаёте DOM скриптом. О каком-либо изоморфном рендеринге HTML в таком случае придётся забыть.

В ТЗ что-то было про изоморфный рендеринг? По умолчанию у нас обеспечивается только функциональность в актуальной стабильной версии Chrome, что фиксируется в коммерческом предложении на внедрение и SLA, ну в Firefox потестим чтобы вёрстка не плыла глобально, а практически весь body генерируется скриптами, клиенту отдаётся минимальная статическая страница.

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


Вы когда к стоматологу приходите — тоже выкатываете ему подробное тз или всё же рассчитываете на его профессионализм и понимание что и для чего он делает и к каким последствиям это приведёт?

Не за нами написанным, за вами.

У вас видимо очень продвинутые заказчики, раз сами пишут вам ТЗ :-)

Мне не важно кто пишет, наши заказчики или наши аналитики, главное, что я не пишу.

В ТЗ что-то было про изоморфный рендеринг?

Справедливости ради, вот эти "в тз небыло" немного напрягают. А как же protected variations?

Небольшие (с точки зрения заказчика) могут полностью перевернуть подход к разработке. Навскидку, для изоморфного рендеринга React-"шаблонов" я вообще никаких оценок дать не могу — знаю, что некоторые люди это как-то делают, вероятно с помощью http-сервера на NodeJS. Всё. Есть ли у React различия между рендерингом в DOM и в http-ответ я не знаю, в этом направлении в компании вообще ничего не делалось никогда. И вообще, может из-за требования изморфного рендеринга нужно будет полностью пересмотреть бэкенд часть. Ну или оставить PostgreSQL, но выпилить из проекта PHP и меня вместе с ним.


Обеспечивать во фронтенд коде совместимость с каким-то сервером, которого разработчик в глаза не видел — это не мелочь, которую можно опустить в ТЗ по принципу "это же очевидно, вы же профессионалы"

Представьте, что у заказчика уже есть изоморфное приложение, вам нужно лишь добавить раздел с товарами. Если вы не будете учитывать спецификации, то обретёте проблемы с тем, как браузеры и роботы понимают генерируемый вашим кодом HTML.

Для такой задачи я увеличу сроки на месяц, даже если оценю её чисто для фронта на час. И то с большими рисками не уложиться.

Вы нам лучше объясните какой плюс заказчику от изоморфного рендеринга :)

Ну, там разные тезисы: генерация html-писем, индексирование поисковиками и прочими роботами, поддержка отключённых скриптов и браузеров не поддерживающих скрипты, экспорт в хтмл, быстрый показ страницы, экономия батарейки клиентского девайса.

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


Продать изоморфность весьма просто. И в целом это не требует каких-то больших эфортов если делать нормально.


@vintage я вот только ваш кейс с "а тепер весь блок продукта стал ссылкой" немного не понял. Мне всегда казалось что делать блоки ссылками — не хорошо, и как правило была бы ссылка где-то внутри блока а какой-то декоратор над контейнером уже бы превращал весь блок в ссылку. Это как бы и кейс изоморфности покрывает, и делать легко и просто, и нет конфликтов со спецификациями.

Ну например «хотите чтобы ваш круто интернет-магазин вообще поисковиками индексировался»


А он индексируется только при изоморфном рендеринге? :)
Или других способов отдавать выдачу не за 5 секунд нет?

Продать-то можно что угодно — вон участки на Луне вполне себе продают…
Но реальный плюс какой?
А он индексируется только при изоморфном рендеринге? :)

Быстрее и надёжнее индексируется, если рендерится на сервере. Но для этого не обязательно делать изоморфный рендеринг, есть способы попроще, типа prerender.io.


Или других способов отдавать выдачу не за 5 секунд нет?

Тут проблема не в отдаче, а в рендеринге. Сложная страница — долгий рендеринг. Мобильный девайс медленный, батарейка хилая, а серваков мы можем понаставить реактивных пачку. К сожалению редко какой современный фреймворк умеет в ленивый рендеринг, чтобы рендерилось не всё, что есть на странице, а только лишь то, что попало в видимую область.

Мы же о веб-приложениях и мобильных приложениях говорим?

В первом случае там голый html, который давно собирался на сервере.
Во втором случае могут быть варианты, вплоть до VNC.

Я просто, читая вашу дискуссию, до сих пор не осознал всех радостей.
Судя из всего — изобрели трудности и собираем страницу нетрадиционными средствами. Из-за чего проблемы с индексацией и т.п.

То есть ровно те проблемы, которых не имеют традиционные server-side языки — java, php, python, ruby, etc…

А потом борьбу с этими проблемами выдаем за преимущество каких-то решений.

Я правильно понял общую мысль?

Веб приложения можно открывать и с мобильника.


На старом голом хтмл вы не сделаете отзывчивого приложения с удобным интерфейсом. с внс — тем более.


Проблема с индексацией не сложно решается.


Традиционные языки имеют кучу других проблем. Основная из которых — они не работают в оффлайне.

Я не очень понял, чем то, что вы описали отличается от реализации VolCh.

ListItemsOrNoItems = ({items}) => items.length !== 0  ? <ListItems items={items}/> : <NoItems/>;

ListItems ({items}) => <ul>{items.map((item) => <ListItem item={item}/>)}</ul>;

ListItem = ({item}) =>  <li>{item}</li>;

EmptyList = () => <p>No items</p>;

У каждого компонента ровно одна собственная уникальная ответственность. Будет она переиспользоваться где-то, может будет дублироваться — дело десятое. Главное — декомпозиция ответственностей и обязанностей.

Замечательно. А если мне нужен ListItemsOrNoItems, но с другими ListItems и NoItems?

Что-то вроде
ListItemsOrNoItems = (items, AbstractListItems, AbstractNoItems) => items.length !== 0 ? <AbstarctListItems items={items}/> : <AbstarctNoItems/>;


и вызов codehtml


  <ListItems items={items}  AbstractListItems={ListItems} AbstractNoItems={NoItems/} />

P.S. что-то разметка глючит

Очень наглядно, спасибо.

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

Чудесно, особенно когда таких опциональных компонентов больше 2.

Это же XML по сути.

ListItemsOrNoItems = (items, AbstractListItems, AbstractNoItems) => items.length !== 0 ? <AbstarctListItems items={items}/> : <AbstarctNoItems/>;

Это лапша. И в жизни это так не работает, так как вы не прокидываете параметры в в создаваемые тут компоненты.

Присмотритесь — прокидываю. И в чем лапша?

Вы прокидываете один единственный параметр — items.

От количества параметров что-то зависит? Тем более их можно группировать в объекты типа options

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

Чем оно не переиспользуемо? Вывод списка элементов или элемента, замещающего пустой список — типовая задача и данный сниппет её решает. Передачу дополнительных параметров (что обычно для таких задач не требуется, стили или инлайнятся/импортятся в конкретных элементах, или через контекст передается тема) для дочерних элементов можно реализовать разными способами, например через…

Договаривайте уж. Через контекст? Глобальную переменную? Урл?

Это не многоточие русского языка, а рест оператор джаваскрипта/джейэсикс :)

что ж вы сразу-то этого не сделли? Давайте посмотрим какой код у вас получится.

Для сравнения, у меня получился такой.


Рендерит списки любых компонент, а если список пустой — рендерит опять же произвольный компонент переданный владельцем. При этом не трубется никаких прокидываний параметров через...

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

Разница в стоимости этого решения и стоимости его поддержки. Тинькоф — вообще чудесный пример — орава разработчиков сделала тормозной сайт, где некоторые страницы рендерятся по 4 секунды :-)

Не видел необходимости. KISS

Теперь она появилась, не заставляйте иеня упрашивать вас написать полноценное переиспользуемое решение, которое можно применять в реальных ситуациях, а не только в комментариях на Хабре :-)

Не заставляйте меня делать продакшен решение в комментариях на хабре по неясным для меня критериями переиспользуемости.

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

Сформулировать требования к переиспользуемости много времени займёт. Навскидку, рассматривать ли возможность замены тегов ul, li и т. п.

Разумеется :-)


  • замена тегов, аттрибутов, стилей, полей как корневого элемента так и всех вложенных
  • рендеринг разных типов строк вперемешку
  • произвольный блок вместо пустого списка
SPA
слабой индексацией в поисковиках

Как правило, приложение, в отличии от сайтов и порталов, не нуждается в индексации.

А если я не знаю PHP, хочу его изучить, мне сразу начинать с фрейморвка? Мне кажется более логично изучить голый PHP.
Согласен. Но при этом не надо обманываться насчет того, что голого php хватит для реального приложения. Поэтому почти сразу же надо начинать юзать фреймворк
сейчас да пожалуй, но как же раньше писались веб-приложения, когда фреймворков не было
Сперва приходилось писать свой фреймворк. А потом на нём писать веб-приложение.

тысячами разработчиками писались одни и те же вещи, часто с одними и теми же ошибками. Попробуйте написать такое же (желательно так же с объектом Greeting, получаемым из базы, а не массивом или stdClass) приложение на голом PHP и количество кода вас неприятно удивит. Или в коде будут грубые ошибки.

Строчек 5-6 займет? :)
Ага, но только реализация класса Greeting (без учета взаимеодействия с БД)
Это только при условии, что стоит цель усложнять код. В ином случае готовый json получается 1 запросом из базы и делать из него внутренний обьект php вообще надобности нету ;)
При условии не усложнять объект и база лишнии, пишем константу в коде, но это выше выяснили.

Не только. Условием может быть моделирование предметной области, в которой есть сущность "Приветствие".

скорее всего это будет VO

Надо побеседовать с экспертами :)

Это смотря какое приложение. Да и фреймворки разные есть. Тут больше важно что нужно знать современные концепции и стандарты современного веб приложения. Вот composer — да; PSR'ы; Неймспейсы; Автолоадинг; Система контроля версий; Единая точка входа; Паблик директория; Роутинг; Шаблонизация; Query builder. Остальное имхо опционально.


И если уж говорить о самой статье, то замените symfony на laravel и react на vue. И статья выйдет короче)

НЛО прилетело и опубликовало эту надпись здесь
голого php хватит для реального приложения

Его то хватит, вопрос в стоимости решения и покрываемых кейсах.

Пожалуй, стоит подбирать инструмент подходящий под задачу, а не бездумно хватать фреймворк, если задача запросто решается без него.
Брать фреймворк для вывода hello world — извращение.

Как минимум, нужно ещё подумать как задача будет изменяться в обозримом будущем.

Вероятность того, что задача «Hello World» менять не будет несколько выше, чем вероятность её изменения, на мой взгляд. Если задача «Hello World» должна будет вырасти дальше за пределы, собственно вывода на экран надписи «Hello World», то проще код вывода «Hello World» выкинуть и решить задачу с нуля отдельным проектом за отдельную сумму.

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

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

Да ладно не желают. Желают, но узнав сколько оно стоит (или в случае внутреннего заказчика — сроки), то свои аппетиты умеряют.

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

Разработчики не желают? Да нам дай волю, мы ИИ в хелловорлд запихаем, не то что СУБД с фреймворком :), да менеджеры нудят "строки-сроки".

Видимо я в душе менеджер, а не кодер :)
Ну или просто сферический лентяй
НЛО прилетело и опубликовало эту надпись здесь
потом попробовать написать свой фреймворк

Тут можно напороться на проблему "а что писать то". Ну то есть мы хотим на практике поучиться — это похвально. Но как новичку определить что он делает что-то дельное? Мы же таким образом можем вредные привычки сформировать у человека.

Может выложить свое творчество на Хабр — тут очень доброжелательно ему объяснят, что он делает не так.

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

В целом есть еще чатики всякие, тостеры и подобное. Ну то есть получать фидбэк сейчас есть где. Другой вопрос что… то о чем я писал немного о другом.


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


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


Ну и под "прикладными задачами" я подразумеваю не бложики, а что-то интереснее. Например — клоны инстаграммов, твиттеров. Какие-то утилитки. Хороший пример в чатиках проскакивал — человек отрабатывал навыки на примере тулы для генерации чейджлогов из git log.

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

Паттерны почти бесполезно изучать, если на практике не сталкивался с проблемами, которые они решают.

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

еще нюанс — следует попробовать поработать хотя бы с двумя фреймворками. Чтобы посмотреть разные подходы.

И с двумя языками, а лучше хотя бы с тремя — родственным потенциально основному и совсем другому, может ассемблер, а может лисп или пролог :)

точно не ассемблер… эрланг например.

Мне кажется более логично изучить голый PHP.

А еще было бы неплохо поучиться тесты писать до момента когда надо фреймворк брать. Ну и не стоит на голом пыхе заниматься коммерческой разработкой (если у вас нет достаточно опыта).


А так да, знание фреймворка не убережет вас от незнания языка. Логику мы все ж на php пишем.

Советую изучить Python вместо PHP. Не смотря на то что PHP в последних версиях взял очень много из Питона, по легкочитаемости кода и красоте синтаксиса уступает последнему. Python по сути это тот же PHP, но лучше. Особенно если 3.x и с использованием Jinja2.
НЛО прилетело и опубликовало эту надпись здесь

Дело не JavaScript, а разнообразии платформ и браузеров.

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

И всё же, hello world на PHP выглядит именно так:
<?php
echo 'Hello, world!';
он выглядит вот так:
Hello, world!

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

Доставание объекта из репозитория не бизнес-логика. Здесь бизнес-логики по сути нет вообще, только бизнес-данные.

В 2017 прямое обращение к репозиторию и сущности из контроллера — это oldschool.
Надо было создать сервис greetings, который орудует приветствиями, а в контроллере извлечь его из контейнера и запросить у него нужное приветствие.
    /**
     * @Route("/greetings/{id}")
     */
    public function greetingAction($id)
    {
        $greetingsManager = $this->get('greetings'); 
        $greeting = $greetingsManager=>get($id);
        return new JsonResponse(['greeting' => $greeting]);
    }
В 2017 методы контроллера не должны иметь доступа к контейнеру, все зависимости контроллера должны инжектится в него через конструктор.
class GreetingController{
  private $greetingService;

  public function __construct(GreetingService $greetingService){
    $this->greetingService = $greetingService;
  }

  ...
}

для контроллеров удобнее через дабл диспатч… ух жду symfony 3.3… смогу убрать свои кастыли для этого. В целом нет ничего зазорного не делать контроллеры сервисами.

для контроллеров удобнее через дабл диспатч

Не знаю как это.
В целом нет ничего зазорного не делать контроллеры сервисами

Откуда же им взять состояние, чтобы не быть сервисами?
Не знаю как это.

Это когда зависимости для метода прокидываются как аргументы этого метода:


// вместо такого
public function doSomething()
{
    $this->someData = $this->dependency->someCalculations($this->someData);
}

// так
public function doSomethingCooler(MyDependency $dependency)
{
    $this->someData = $dependency->someCalculations($this->someData);
}

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


Откуда же им взять состояние, чтобы не быть сервисами?

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


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

А кто мешает сейчас писать в режиме 1 action = 1 controller?

Получается именно то самое поведение, которое вам хочется.

PSR-7 middleware фреймворки реализуют именно то, что вы хотите.
Например Zend Expressive
А кто мешает сейчас писать в режиме 1 action = 1 controller?
Получается именно то самое поведение, которое вам хочется.

далеко не то же. Зачем мне ажно целый класс который выглядит так:


class RegisterUserAction
{
    private $handler;
    private $loginManager;

    public function __construct(RegisterUserHandler $handler, LoginManager $loginManager, Flusher $flusher)
    {
          $this->handler = $handler;
          $this->loginManager = $loginManager;
          $this->flusher = $flusher;
    }

    /**
     * @Route("/users", methods={"POST"})
     */
    public function __invoke(Request $request)
    {
         $user = $this->handler->__invoke($this->mapRequestData($request));
         $this->flusher->flushChanges();

         return $this->loginManager->login($user);
    }
}

если я могу сделать так:


public function registerUserAction(Request $request, RegisterUserHandler $handler, LoginManager $loginManager)
{
    $user = $handler($this->mapRequestData($request);
    $this->flushChanges();

    return $loginManager->login($user);
}

Ну то есть кода меньше, делает одно и то же. Это же контроллеры, там нет логики, даже логики уровня приложения. Оно просто связывает HTTP и приложение. Вот если бы можно было полностью от HTTP отвязаться на уровне фронтконтроллера, у меня остались бы только хэнделеры и контроллеры бы юзались как адаптеры например для совместимости со старыми версиями API. Но увы я пока не придумал эффективного способа.


PSR-7 middleware фреймворки реализуют именно то, что вы хотите.

Не то же самое. Повторюсь — я пробовал, на 100% не выходит. Проблема обычно с http реквестом или с flush-ем доктрины. Все равно нужна какая-то одна штука на запрос которая будет связывать все вместе. Проблему частично решает graphql но это если он нам подходит.

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

Может это вдохновит вас на идею https://habrahabr.ru/post/280512/

Это хорошо подходит когда вы не любите фронтэндщиков.


CommandBus это хорошо, это прикольно, это весело. Но это не значит что HTTP запрос который дергает шину команд ничего не должен возвращать. А если вам надо что-то вернуть — надо знать что возвращать.


p.s. Практикую CQRS на проектах, так что вдохновиться увы не могу и думал над этим не один месяц. Отказаться от контроллеров является непрактичной затеей. Делать экшены контроллеров как классы практично только если у вас логика в контроллерах.

Зачем мне ажно целый класс который выглядит так

А почему бы не реализовать делегирующий контроллеру middleware?

и будет по мидлвэру на действие. То есть адаптеры такие небольшие. То есть… контроллеры.

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

Вы контроллер можете инстанцировать через фабрики. Зачем там middleware?
Дело не в инстанциировании, а в подготовке запроса и ответа. Тут все зависит от задачи. К примеру у вас есть требование: «при запросе с расширением .json, должен возвращаться ответ в формате JSON, а без расширения, в формате HTML» — можно конечно реализовать два контроллера, которые будут одинаково обрабатывать Request, одинаково запрашивать у слоя домена, но по разному формировать Reponse, а можно просто добавить в Pipeline Middleware, который на основании Request будет оборачивать полученные от одного контроллера данные в разные форматы (JSON или HTML).
В моем понимании разные форматы это рендеринг в разные View

Соответственно постпроцессинг запросов в middleware у вас потом ответ куда отдаёт?
В моем понимании разные форматы это рендеринг в разные View

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

Про JSON/HTML это был общий пример использования Middleware Pipeline, по тому же принципу, к примеру, можно организовать ACL расположив слой проверки перед контроллером в виде Middleware.
Зачем?

Если у меня приложение поддерживает несколько форматов рендеринга, то я это свяжу на этапе роутинга например.

Или в самом контроллере анализируя пришедший из роутинга параметр формата буду подключать необходимые View.
Эдакий Context switch.

Суть в том, что никому другому знать о том поддерживает ли контроллер разные форматы не нужно.

Вот ACL это неплохой пример post-routing события.
Информации для принятия решения к этому времени уже достаточно
Или в самом контроллере анализируя пришедший из роутинга параметр формата буду подключать необходимые View.
Эдакий Context switch.

В том то и суть, что Middleware Pipeline позволяет вынести из контроллера часть логики, инкапсулировав ее в других классах. Разделение ответственности и все такое. Я не настаиваю, просто обсуждаю один из вариантов решения.

Как правило, для HTML нужно возвращать больше данных.

В том то и прелесть, для HTML можно добавить дополнительный Middleware.

То есть мы можем воспринимать контроллеры как еще один адаптер, еще один мидлварь. И у нас будет адаптер (мидлварь) которая на основе правил маршрутизации будет выбирать нужный. Пока не вижу ни разницы в подходах ни профита. Я лишь выбираю способ с которым удобнее. В PHP удобнее класс-контроллер с несколькими экшенами и дабл диспатчем. В JS удобнее функции-адаптеры которые выступают как мидлвари.

И у нас будет адаптер (мидлварь) которая на основе правил маршрутизации будет выбирать нужный

Выбирать нужный что?

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

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

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

Резюмировать мой ответ можно так: не обязательно дробить Middleware по штуке на действие.
не обязательно дробить Middleware по штуке на действие.

Не обязательно. По факту мы можем реализовать:


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

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

потому что тут можно без контроллера, а тут без контроллера будет сложно

Fesor, не понял причину разброса роутов по системе. Чем плох вариант вроде:
[
  'route' => '/article/update/:id',
  'middleware' => [
    SessionMiddleware::class,
    AclMiddleware::class,
    ControllerDelegatorMiddleware::class,
  ],
  'controller' => ArticleController::class,
  'action' => 'updateAction',
]

Возможно проблема в том, что вы конфигурируете роуты только на уровне контроллеров?
Возможно проблема в том, что вы конфигурируете роуты только на уровне контроллеров?

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

Что менее удобно

Ну это уже слишком субъективно, чтобы обсуждать )

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

проверять права, даже если они фактически не понадобятся

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

В крайнем случае, что вам мешает исключить конкретные Middleware из очереди для конкретного контроллера?

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

Не очень удобно. Удобнее это делать между вью и моделью, то есть в контроллере. Правил доступа может быть множество, копить их всех в модели — она разрастется без всякого изменения бизнес-ценности.

Удобнее вообще не разделять серверную логику на M и V и C, ибо этот паттерн предназначен для интерактива с пользователем, а схема "запрос-ответ" не интерактивна, а строго последовательна.


Более продуктивный подход — организовывать все сущности в виде "ресурсов", к которым можно обратиться через любой протокол (http, udp, ws, ssh) через соответствующие адаптеры.


Зачем вам множество правил доступа?


Ограничения прав имеет чуть ли не наибольшую бизнес ценность.


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

На многих серверах система "запрос-ответ" уже не используется в чистом виде. Как минимум есть всякие пуш-технологии. Да и сам запрос можно рассматривать как событие UI, причём не важно пользователь его инициировал, кликнув ссылку или другой сервер.


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


Не мне, бизнесу. Постоянно новые требования. Но эти требования обычно к основной бизнес-логике не относятся. И бизнес-ценности особой не имеет, в плане что не приносит прибыли, защищают от потерь, нужны как неизбежное зло, но прибыли не приносят. Мировая практика показывает, что неразрывное связывание объектов прав доступа (это не только сущности) и описания самих прав (особенно императивное) крайне редко приносит пользу. Различные ACL, хранимые и обрабатываемые отдельно более эффективны как в плане удобства их манипулированием, так и в плане улучшения основной логики — мухи отдельно, котлеты отдельно.


Что является атрибутом, а что нет, определяется не разработчиком приложения, а экспертами предметной области. И даже если эксперты настаивают на том, что право доступа неотъемлемый атрибут сущности или экземпляра процесса, то разработчик может их разбить и связать 1:1, чтобы облегчить свои задачи, например не хранить в базе данных код определения прав с последующим его интерпретацией.

На многих серверах система "запрос-ответ" уже не используется в чистом виде.

В рамках REST это в любом случае остаётся — запрос ответ. Даже в схеме "запрос от одного сервера, ответ другому". Нет сохраняющегося состояния вьюшки — нет MVC. Комбинацию клиент+сервер уже можно рассматривать как MVC, точнее на клиенте MVC, но модель синхронизируется с мастер-моделью на сервере.


И, главное, бизнес обычно оперирует процессами, а не сущностями.

Чем бизнес оперирует — то и есть сущность.


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

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


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

В рамках REST это в любом случае остаётся — запрос ответ. Даже в схеме "запрос от одного сервера, ответ другому". Нет сохраняющегося состояния вьюшки — нет MVC. Комбинацию клиент+сервер уже можно рассматривать как MVC, точнее на клиенте MVC, но модель синхронизируется с мастер-моделью на сервере.

Я рассматриваю клиент-сервер как MVC, в котором V и C размазаны между клиентом и сервером. Что между клиентом и сервером есть односторонний синхронный канал "запрос-ответ" — деталь реализации, обусловленная протоколом обмена. Что на клиенте может быть дублирование модели и свое MVC — деталь реализации.


Чем бизнес оперирует — то и есть сущность.

Очень отличается от классического определения сущности в моделировании предметных областей. Как минимум, бизнес оперирует сущностями и процессами, в которые сущности вовлечены. Например, сущности "клиент" и "договор" вовлечены в процесс "заключение договора".


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

ACL просто как пример. Проверять авторство — да, но в слое проверке прав доступа писать


 $userCanEdit = ($article->author === $user) || $user->hasGroup('admin', 'moderator') || $user->hasPermission('edit_any_article') || ($user->birthDate ==  new \DateTime('1975-05-03')); 

а не в методе Article::isEditableBy(User $user) то же самое — слишко сильная связанность с User получается.


И способ хранения и сущности, и прав особого значения не имеет для бизнеса, как правило.

Как минимум, бизнес оперирует сущностями и процессами, в которые сущности вовлечены. Например, сущности "клиент" и "договор" вовлечены в процесс "заключение договора".

От того, что некоторые сущности назвали процессами, они сущностями быть не перестали. Процесс "заключение договора" сам по себе вовлечён в процесс "продажа товара", который агрегирован в сущность "продажи".


не в методе Article::isEditableBy(User $user) то же самое — слишком сильная связанность с User получается.

И что в этом плохого?


И способ хранения и сущности, и прав особого значения не имеет для бизнеса, как правило.

Формат хранения определяет мышление :-)

От того, что некоторые сущности назвали процессами, они сущностями быть не перестали. Процесс "заключение договора" сам по себе вовлечён в процесс "продажа товара", который агрегирован в сущность "продажи".

Сущность — вещь, процесс — действие.


И что в этом плохого?

На любой чих службы безопасности и т. п. нужно менять бизнес-логику. На любое изменение сущности пользователя нужно менять бизнес-логику.


Формат хранения определяет мышление

Я вообще стараюсь не думать о хранении пока не определюсь с объектной моделью. Выбор из SQL, noSQL или вообще в какого-то веб-хранилища последний этап.

Сущность — вещь, процесс — действие.

А что толку от этого переименовывания? Суть остаётся та же. Действие, как и любая вещь, обладает состоянием, связями с другими вещами. Как и любые вещи, действия складываются в различные коллекции. Даже процессорные потоки — это не более чем особые структуры. Что уж говорить про собственно процессы.


На любой чих службы безопасности и т. п. нужно менять бизнес-логику. На любое изменение сущности пользователя нужно менять бизнес-логику.

И? Если вы перенесёте эту функцию из файла А в файл Б, связанный с первым как 1-к-1, что-то сильно изменится?


Выбор из SQL, noSQL или вообще в какого-то веб-хранилища последний этап.

А с какими типами СУБД вы работали? Ну, помимо реляционных и словарных.

Действие не обладает состоянием. Оно либо есть, либо его нет. Можно условно считать, что состоянием действия является супепозицией состояний всех вовлеченных сущностей, но это только условность.


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


dBase к каким относится? ) А так из экзотики xBase и Db4o немного крутил.

Действие не обладает состоянием. Оно либо есть, либо его нет.

Процесс обладает состоянием. Например, текущая стадия.


при изменении сущности пользователя нужно будет менять все сущности модели.

Зачем? Реализуйте тот же интерфейс.


dBase к каким относится? ) А так из экзотики xBase и Db4o немного крутил.

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

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


Речь об изменении интерфейса сущности пользователя.


Я приверженец подхода по которому система хранения должна минимально влиять на объектную модель. Посмотрю, может быть некоторые мои модели удобнее будет хранить в ней, чем в реляционных или документных.

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

Процесс "списание товара". Подчинённый подготавливает список товаров, руководитель подтверждает или нет, бухгалтер оформляет списание официально, только если списание подтверждено.


Речь об изменении интерфейса сущности пользователя.

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

  1. (подчинённый создал список в системе и начал заполнять) у сущности "список товаров на списание" статус "новый"
  2. (подчиненный отправил на рассмотрение руководителю) у сущности "список товаров на списание" статус "на рассмотрении"
  3. (руководитель рассмотрел) у сущности "список товаров на списание" статус "утвержден" или "отклонен"
  4. (бухгатер оформил) у сущности "список товаров на списание" статус "утвержден" и с ней связана бухгалтерская проводка

Этап процесса однозначно определяется состоянием сущности "список товаров на списание" и наличием связанной с ней проводки. Причём связь может быть односторонней — только проводка ссылается на список. А собственного состояния у процесса и нет. Каждый этап должен иметь результат в виде создания, изменения или удаления каких-то сущностей.

Причём связь может быть односторонней — только проводка ссылается на список.

Ссылка — это тоже состояние. Вам не кажется странным, что у вас тут две сущности, связанные 1-к-1 и которые всегда существуют в паре? Что вы выиграли от такого условного разделения "процесса списания", на "проводку" и "список товаров" (который на самом деле не список, а полноценный документ с подписантами, количеством, ценами и прочей требухой — почему бы не назвать этот документ "проводкой"?)?

Не всегда. Проводка формируется только если список утвержден руководителем (представим, что бухгалтерия права голоса не имеет). Более того, руководитель может утвердить список лишь частично (для простоты в комментарии) и проводка не будет соответствовать списку.


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

Представим что мы реализуем полный жизненный цикл. Какой смысл разделять эти сущности? Предлагаете дублировать сущность и "закрывать" предыдущую после каждого пройденного шага процесса? А если надо будет вернуться на шаг назад не потеряв при этом привязанные к сущности обсуждения в почте?

Простой — это разные выделенные контексты. Оперативный документооборот и бухгалтерский учёт. Нет смысла создавать бухгалтерские проводки "в планах" на каждый чих, особенно если 90% из них руководством не утверждается. Ну а если в требованиях априори есть, что в процессе утверждения список товаров может измениться и необходимо хранить первоначальную заявку, то мы очень сильно усложним систему, не разделив эти слаюосвязанные и в реальной жизни сущности.

99% утверждаются. 1% уточняется и потом всё же утверждается. Первоначальная никого не интересует, но после утверждения изменяться состав списка не должен.

В рамках REST это в любом случае остаётся — запрос ответ.

Вот только есть мнение что подход в чистом виде устарел немного, требования сейчас чуть-чуть другие.


В целом подход запрос-ответ хорошо ложится на MVA.

Я проспал statefull революцию?

graphql + subscribe пиарят, все чаще вэбсокеты для мобилок юзаются с доставкой ивентов на сервер вместо стэйта… Не уверен что это "революция" но тенденция есть.


Ну и в целом есть нюансы, можно сделать stateless сервер (легко скейлить) с подпиской на изменения данных используя websockets.

Вебсокеты батарейку кушают, поэтому лучше всё же централизованные пуши.


Полноценный subscribe я мало где видел. Но подписки — это очень уж ограниченное состояние. Я бы сказал statefew.

Вебсокеты батарейку кушают, поэтому лучше всё же централизованные пуши.

Батарейку они кушать могут только за счет проверки соединения (ping/pong). В библиотеках типа socket-io вы можете сами выставлять "частоту" проверок. Ну а в бэкграунде на всяких там iOS у вас всеравно нет выбора — надо юзать пуши.


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


Но подписки — это очень уж ограниченное состояние. Я бы сказал statefew.

Ну так это же хорошо :)

Эмм… А вашей модели «Статьи» нужно знать, что есть такие юзеры, как админы?
Ну хз, я стараюсь модели максимально ограничить в знаниях. ACL по админам и авторам точно не реализовываю через уведомление Статьи о том, что в ее окружении существуют некие пользователи, которые и не авторы, но тоже важны. Зачем об этом знать Статье? (вопрос риторический)

Доменная модель естественным образом представляется в виде графа, где все узлы друг с другом перелинкованы. Пользователь логически "знает" о всех своих статьях точно также как и статья "знает" о своём авторе. Да, это не вписывается в реляционную модель, где связь многие-ко-многим эмулируется через поиск по индексу. Если же вы попробуете использовать графовую субд, то перестанете удивляться тому, что многие сущности знают друг о друге. Статья знает об авторе, пользователь о своих группах, группа о ролях, роли… о статьях :-)


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

Статья то не «знает» о таких пользователях, как «Админ». Более того, Статье вообще незачем «знать» о ACL-Ролях. Под «знать» я понимаю не наличие ссылки, а скорее возможность одного объекта использовать другой в своей деятельности.

Почему это незачем, если ей, чтобы рассказать о правах, надо знать о ролях?

А зачем Статье рассказывать о правах? Статья это не ACL, у нее другие обязанности.

Затем, чтобы её правильно визуализировать.
У статьи вполне конкретные уникальные правила проверки прав. Зачем их уносить далеко от реализации собственно этой модели?

Возможно я плохо понимаю вашу задачу, но при чем тут визуализация Статьи? ) Если пользователь пытается открыть статью, действуют правила ACL, если же нужно не показывать ссылку на статью в GUI, то достаточно воспользоваться Спецификацией при выборке.

Задача показать кнопку "редактировать" только автору и админу.

Да все тем же ACL:
<?php if($this->acl($currentUser, $article, 'edit')): ?>
  <a href="#">Редактировать</a>
<?php endif; ?>

У меня SPA.

Не важно (можно вынести полностью или частично ACL во frontend, или сообщить во frontend, что можно делать с ресурсами), цель отделить «котлеты» от «мух».
можно вынести полностью или частично ACL во frontend

то есть… продублировать реализацию?

Все зависит от вашей фантазии и возможностей.

HATEOAS?

Это не у статьи проверки, это у пользователей. :) Посмотрите на реальность — у документов нет систем проверки прав, есть комплекс мер, которые мешают не авторизованному пользователю получить доступ к совсекретному документу, но сам документ лишь чистая информация.

Более того, Статье вообще незачем «знать» о ACL-Ролях.

Сильно зависит от задачи. Частенько именно "статья" сможет сказать кто ее может редактировать. Например только ее автор. Эта информация доступна только ей.


Но не надо пытаться "обобщить" все на счете.

Например только ее автор

Меня смущает не то правило, что про автора, а то, что про Админа. Статье не нужно знать о наличии Ролей в приложении.

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

Судя по первому взгляду там не запись знает, кто её может редактировать, а система проверки прав доступа решает по метаданным, сохраненной в одной записи с основными данными. И я сильно удивлюсь, если в этой СУБД нет суперпользователя, который может отредактировать запись, даже если его нет в метаданных записи. В любом случае не похоже, что там хранится полноценный объект, а не, максимум, его данные и метаданные.

Пользователь там — это просто запись типа OUser в БД. Если входишь под таким пользователем и обращаешься к restricted записям, то срабатывает повешенный на них хук, проверяющий права. Хук наследуется от суперкласса ORestricted и к неунаследованным от него записям не применяется. Так что фактически это объект со своим состояним и поведением.


Ну, и да, всегда можно войти администратором кластера и воротить что угодно, но это к бизнес-модели уже не относится :-)

Так что фактически это объект со своим состояним и поведением.

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

Покажите мне хоть один компилируемый ООП язык, где методы принадлежат объектам :-D Везде методы — это отдельно лежащие функции, которым в качестве контекста передаётся ссылка на структуру. Обычно в самом объекте максимум, что хранится — это ссылка на таблицу виртуальных методов.


В случае ORestricted — у записи есть ссылка на её класс, у класса — на супер класс, на суперклассе навешан хук. Та же таблица виртуальных методов, только в профиль.

Покажите мне хоть один компилируемый ООП язык, где методы принадлежат объектам

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


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


Вот только ООП оно не про объекты. "Все есть объект". Это не значит что у всего должен быть класс, это буквально означает что "любая штука есть объект". Скаляры — объекты, просто в некоторых языках у них нет поведения. Даже в языках вроде Erlang мы можем под объектом со своим стэйтом и поведением понимать тред. И треды между собой будут обмениваться сообщениями. И можно подменять треды в рантайме (late binding).


То есть совершенно не важно что вы называете объектом. Важно то, что статически, на уровне исходников, у вас стэйт и обработка этого стэйта будут максимально рядом. Инкапсуляция и data-abstraction. А как оно там в рантайме — нам не особо интересно с практической точки зрения. Семантический разрыв и все такое.

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


Вы делаете что-то вроде Record::findById(1)->read(): record, когда получаете отлуп от системы управления правами или делаете Record::findById(1): record? Если второе, то проверкой права на чтение данных записи занимается не запись. Если первое, то как ограничить право, чтобы клиент не узнал о существовании записи в одних случаях, и узнал но не мог прочитать — в других.

Логически и в OrientDB хук принадлежит записи :-)


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

Каждая запись знает, что есть суперюзер и его надо пускать всегда?


Кем не будет найдена, если проверка прав осуществляется в записи? Сначала найдётся для движка, а потом сама удалится при попытке движка отдать её? Или, всё таки, движок её найдёт, увидит, что пользователь не в ACL записи и не суперадмин и исключит из результатов? А если надо различать ситуации "не найдена" и "нет прав доступа" при общем запросе типа "вывести список всех сотрудников, а также их оклады если пользователь является начальником сотрудника"?

Хук скорее всего знает, да.


Скорее всего движок попытается прочитать, сработает хук, который проверит права и даст отлуп, а движок пропустит эту запись. Но я не особо знаком со внутренностями. Да это и не важно — для пользователя "объект сам знает кто как может с ним работать".


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

для пользователя "объект сам знает кто как может с ним работать".

Если движок пропускает записи, с которым пользователь не может работать, а не даёт отлуп на всю выборку, то не объект, а движок знает.


В OrientDB минимальная гранулярность прав — объект.

Тогда для очень многих схем управления доступом он не подойдёт. Очень часто права ограничиваются к полям объектов и нередко в зависимости от состояния этой, а то и других сущностей.

Движок спрашивает у объекта.


Сложную логику вам и ACL не заменит. Только код.

НЛО прилетело и опубликовало эту надпись здесь

А чем вас не устраивает класс из нескольких экшенов? У вас лимит на количество методов в классе? :)

НЛО прилетело и опубликовало эту надпись здесь
Ну человек же борется с километровыми конструкторами путем внедрения необходимых каждому методу зависимостей прямо в екшен.

не совсем. Я борюсь с lack of cohesion. У меня есть по классу на экшен приложения, по классу на юзкейс. А контроллеры лишь являются адаптером между HTTP и этим "юзкейсом". Мне проще сделать один класс с 5-ю экшенами связанными по смыслу, а поскольку всем 5-ти экшенам нужны разные зависимости инъектить их через конструктор как-то не очень выгодно.


так как в аргументах смешиваются зависимости и непосредсвтенно аргументы.

А что, аргументы уже не являются зависимостями? Да, это зависимость по данным, но смысл примерно тот же. Грубо говоря у вас будет "запрос" + 1-2 зависимости для обработки этого запроса. Почти всегда. Крайне редко вам понадобится что-то сложнее.

НЛО прилетело и опубликовало эту надпись здесь
Но дабл диспатч привязывает вас к контейнеру который умеет в дабл диспатч.

Нет, дабл диспатч привязывает меня к мидлвари которая достаточно умная что бы сделать $container->get(Foo::class) и заполнить аргументы метода. А делать свои имена сервисов умеют все.


Если что — контейнер симфони этого не умеет из коробки. У меня есть ~80 строк кода которые добавляют эту функциональность + кэширование.

Тем что при слабой связности вы будете иметь раздутый конструктор для всех зависимостей. При том что в самих action все одновременно они не требуются.

Вот для случаев описанных ниже
один класс с 5-ю экшенами связанными по смыслу,

Можно.

Я только не совсем понимаю почему они связаны между собой, а зависимости при этом у всех разные…
Из чего и возникает потребность получать зависимости непосредственно при вызове action
Я только не совсем понимаю почему они связаны между собой, а зависимости при этом у всех разные…

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

Потому что слегка разные операции + у меня есть отдельные классы которые реализуют одно конкретное действие.


есть у меня например ресурс /accounts/{id}. Он содержит в себе операции над этими ресурсами:


  • PUT /accounts/{id} — создание аккаунта
  • GET /accounts/{id} — детали аккаунта
  • POST /accounts/{id}/withdrawn — вывод средств

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


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

image

Вот вариант когда отдельные классы на каждый тип действия
И естественно фабрики для них примерно так выглядят
            App\Action\User\Register::class => function (ContainerInterface $container) {
                return new \App\Action\User\Register(
                    $container->get(RouterInterface::class),
                    $container->get(TemplateInterface::class),
                    // прочие зависимости
                );
            },
            App\Action\User\Login::class => function (ContainerInterface $container) {
                return new \App\Action\User\Login(
                    $container->get(RouterInterface::class),
                    $container->get(TemplateInterface::class),
                    // прочие зависимости
                );
            },

 - Invoices
   - Handler
       - PurchaseProductHandler
       - TransferMoneyHandler
       - PayTheInvoiceHandler
   - Model
   - Infrastructure
   - Http
- Orders
   - Handler
      - AskForRefundHandler
      - ...

У меня примерно так. Но все равно нужны контроллеры, а "хэндлеры" это просто application-level сервисы, реализация отдельных юзкейсов. Директории верхнего уровня описывают ограниченные контексты (Bounded Context).

У вас лимит на количество классов? оО

У меня лимит на количество кода не приносящего пользы. Чем меньше — тем лучше. У меня лимит на количество кода в экшене контроллера.

Увы в реальности так красиво выходит только на очень простых задачах. В остальных случаев хоть какой-то адаптер нужен для каждого конкретного UI. То что мы потом сверху можем через контент негошиейшен разруливать делать json или html — это мелочи.

А флаш где делать? Мне его надо сделать между "сделал дело" и "сделал выборку для респонса".


Более того, я и так это делаю в мидлварях. Просто у меня всеравно есть необходимость юзать контроллер как точку, которая уже разделяет операции чтения и записи.

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

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


Через такие штуки как AOP мы можем это разрулить (декорация или ивенты неудобно).


можно реализовать специальный Middleware для этих целей. Вариантов куча.

В этом случае для каждой бизнес транзакции нам нужна мидлваря а при таком раскладе у вас разница между контроллером и мидлварем невилируется. Я вообще не понимаю о чем спор. Покажите мне код который не требует наличия контроллеров, а я скажу что это либо "тоже самое" либо "вот вам причины почему это неудобно".

а я думаю в том месте, которое регламентирует границы бизнес транзакции

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

Выбирайте любой — http://stackphp.com/middlewares/
Понятно, что это можно добавить в контроллер и на выходе мы получим, как бы контроллер, но эти решения проще компоновать.

повторю еще раз — мидвари это просто цепочка адаптеров. контроллер = адаптер на границе приложения и UI layer. Вы можете делать все так что бы "контроллер" был лишь одним из элементов цепочки мидварей.


p.s. я использую мидлвари, у меня туда вынесены кеширования, аутентификация, jwt авторизация, есть еще кое какие мои мидлвари которые занимаются раграничением прав, но у меня все еще есть контроллеры и за пол года размышлений я не смог от них отказаться. Из вариантов которые пока не пробовал и которые могут подойти — использование graphql или подобных подходов, где вот этот вопрос "что делать после изменения данных" регламентируется на уровне запроса клиентом. В этом случае всем этим может заниматься одна единственная мидлваря.

Таки я вас не призываю отказываться от контроллеров.
Ну то есть кода меньше, делает одно и то же.

Это в том случае если зависимости типичны для группы action.
Ну так и сделать наследование никто не мешает — некий CommonAction

А если нет?
Не совсем понял, кто и когда в вашем примере делает инъекцию зависимостей в методы action?
Router?

Проблема обычно с http реквестом или с flush-ем доктрины. Все равно нужна какая-то одна штука на запрос которая будет связывать все вместе.

Все это что? Запрос и работу с моделями доктрины?
Это когда зависимости для метода прокидываются как аргументы этого метода:

Сейчас работаю в проекте на Zend. Там все так сделано. Пекло пекельное.
Когда у метода одна зависимость, как в вашем примере, еще ничего, а представьте пример с 5-ю зависимостями плюс собственными аргументами метода. Каждый вызов такого метода — простыня из аргументов. Да и объявление выглядит уродливо.
а представьте пример с 5-ю зависимостями

у меня есть правило — у любой штуки не должно быть более 3-х зависимостей. Если у вас их больше — вам нужно подумать больше над декомпозицией.

Зависимости в конструктор.

Если всего много и объекты сложный, то создаётся фабрика.
Или builder для инжекции зависимостей.

А параметры идут уже непосредственно при вызове
Хорошо подходит для экшенов контроллеров

Я предпочитаю контроллеры вида:
public function *Action(Request, Response, [$next]){
  ...
}

Потому такое мне не очень подходит )
Поясню свою мысль

Честно говоря не понял вашу мысль ) По моему мнению контроллеры зависят не от фреймворков, а от семантики запросов/ответов.

в 2017-ом году сервисы менеджеры сущностей являются моветоном. Хотя и в нулевых так было...


    /**
     * @Route("/greetings/{id}", requirements={"id": "\d+"}, methods={"GET"})
     */
    public function greatingAction(int $id, Greater $greater)
    {
        return $this->json([
            'greating' => $greater->greet(),
        ]);
    }
Зачем вам здесь $id?

забыл опрокинуть его в сервис Greater.

int $id в параметрах лишний, он автоматически будет преобразован в Greater $greater
А так да, это самый правильный вариант. Хоть для новичков и многовато магии.

нууу я не сторонник идей что бы сущность попадала в контроллер (хотя так часто делаю ибо ленивый), ну а в моем примере Greater это сервис. И да, у меня симфони так умеет)

Кастомный ParamConverter?

В данном случае лучше всего автоматический конвертер передающий конструктору готовую сущность, ок. А что предлагаете использовать для манипулирования сущностями вместо сервиса менеджера сущностей?
Контроллер-сервис, как выше, не предлагать — плодить десятки, а то и сотни сервисов, которые таковыми не являются, да еще и инициализируют другие сервисы, которые могут им и не понадобиться ничем не лучше толстых контроллеров.

Сервисы домена

Да он просто задолбался уже.
Полагаю, что требования к Hello world звучат примерно так «нужно уметь выводить надпись Hello world». Точка.
Даже помня, что «выводить» можно теоретически не только в консоль, реализация слишком усложнена.
Откуда у вас взялись REST, Postgres и куча фреймворков? Где тогда load balancing, горячий кэш данных и (конечно же!) MongoDB? Усложнять так усложнять!
Полагаю, что требования к Hello world звучат примерно так «нужно уметь выводить надпись Hello world». Точка.

Надо быть готовым, что в последствии нужно будет выводить надпись «Good evening world» или «Good morning world» в зависимости от времени суток, на выбранном языке, а потом дать возможность пользователю самому выбрать свое приветствие, и так далее.
Не забывайте о дизайне! Кому нужно некрасивое приложение?! Без Sass и Compass это не современное PHP Hello world!
И, видимо, уметь получать последний вариант текста из какой-нибудь Кафки или ZMQ. Ну и уметь масштабироваться, а то мало ли, вдруг мощностей не хватит.

Только вот обычно цель hello world познакомиться с базовыми возможностями языка/платформы, а не предугадать все грабли, на которые придется наступить в «реальной» жизни (кстати, реальность тоже у всех разная). Такое нагромождение сложности у здравомыслящего новичка вызовет только рвотный рефлекс.
Это уже изменение в ТЗ и последующий рефакторинг.
НЛО прилетело и опубликовало эту надпись здесь

Незаслуженно забыли о микросервисах. Очевидно же, что вывод Hello нужно реализовать в сервисе приветствий (Greeting), а вывод World нужно делегировать сервису геолокации.

ахаха
Сажусь писать следующую статью ))
Сейчас тяжело найти нормального программиста, который умеет писать на ГОЛОМ PHP! Нет, движок у проекта есть, но модули почти на 80% состоят из обычного функционального программирования. Попросишь написать небольшую фичу, так он привязывает еще тонну файлов фреймворка.

Это относится как к Frontend, и в большей степени к Backend — где одни демоны, кроны и консоли.

Если программист может решить задачу на голом php, то он сможет и разобраться в фреймворке. А вот наоборот — не суждено.

Крайне спорное утверждение

НЛО прилетело и опубликовало эту надпись здесь
А чего допиливать? Например, проекту более 5 лет, он не падает после yum update, нет каких-либо зависимостей, нет отсутствия обратной зависимости. 100% кода в бэкенде такое, выглядит красиво, работает стабильно, дебажится стандартными инструментами, нет скрытой логики.

Frontend действительно стоить держать в современном виде, но главное не зарываться. Сайт Тинькофф-банка грузится сотней запросов к серверам (бесит). Сайт старого банка — двумя запросами. Скорость не в пользу Тинькова…

выглядит красиво

весьма субъективная метрика.


работает стабильно

А насколько хорошо расширяется? Есть ли статистика регрессий в коде?


нет скрытой логики.

Неужели нет ни одной глобальной переменной?

Расширять бизнес-логику приложения, которое не меняется годами — не нужно. Если необходимо запилить в приложении с названием «калькулятор» вызов такси Убер (или Умер), то это неправильно поставленная задача.

Нет, глобальных переменных нет. Хотя в блоке управления двигателем одного производителя нашли с тысячу таких переменных…

Расширять бизнес-логику приложения, которое не меняется годами — не нужно.

Даже не знаю сочувствовать или завидовать. Что программист-то делать должен с этим приложением? Многолетние баги фиксить? Микрооптимизациями заниматься? На новые версии PHP переводить?

Не трогать это приложение :) Заняться другими вещами, например, новым приложением «Умер» )))

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

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

На голый пхп, да ещё процедурный, для приложений с запретом использования сторонних библиотек на пхп вернусь только за очень большие деньги.
Значит вы не «нормальный программист» и вас легко найти, тяжело потерять, ну и все в таком духе )

Понятие "нормальный программист" сильно изменилось даже за то время, что я пишу на ПХП как основном языке (а это без малого лет 20).

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

Автор, В 2017 держать под гитом .idea моветон)

да, забыл добавить в .gitignore )

Есть мнение что помещать в .gitignore тоже моветон) Нужно в .git/info/exclude. Но это конечно спорно

Век живи, век учись. Спасибо.
Есть мнение, что лучше для этого использовать пользовательский файл исключений:
~/.gitconfig
[core]
    excludesfile = ~/.gitexclude

~/.gitexclude
.idea/
Поддерживаю! .idea это часть окружения конкретного разработчика, а не проекта.
Вообще-то проекта. Из локального там только workspace.xml и tasks.xml.
И если его правильно (https://www.gitignore.io/api/phpstorm) игнорить, то можно таскать настройки стилей кода и прочие вкусности в проекте, а не настраивать все время.
Общие настройки хранятся в ~/.PhpStorm/config по дефолту.
Открою вам секрет — в хороших проектах «настройки стилей кода» применяются на уровне git push hook'ов, и являются уровнем инфраструктуры разработки, а не частью проекта.

Зачем мне, как разработчику не использующему PhpStorm, таскать к себе ваши настройки стилей кода, применимые только в контексте PhpStorm? )
являются уровнем инфраструктуры разработки, а не частью проекта.

Принятая/согласованная/утвержденная инфраструктура разработки неотъемлемая часть проекта. Пускай лучше вы лишний раз скачаете не нужные вам файлы, чем 90% команды будут "переводить" стайл-гайд на "язык" настроек продуктов джетбрэйнс, а то и вычислять стайл-гайд по реджектам от хуков.

Принятая/согласованная/утвержденная инфраструктура разработки

90% команды будут «переводить» стайл-гайд на «язык» настроек продуктов джетбрэйнс, а то и вычислять стайл-гайд по реджектам от хуков

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

./idea в репе — часть этого окружения.

Так себе аргумент. С тем же успехом можно хранить в репе образ ОСи, на которой должен работать проект )
Ну так в папочке (docker || ansible || provision ||… ) оно обычно и лежит.
Конфиги PhpStorm тоже обычно лежат в .idea, но это ведь не означает, что их надо пушать в репу )
Вот именно — напрячься и сделать по хорошему, запушив после этого в репу то что нужно.
В частности и настройки IDE если 90% пользуются ей. Там можно не только стиль кода, там например могут быть уже преднастроенные параметры запуска тестов локально, vagrant или docker, деплой на тестовые сервера. Разумеется это никак не заменяет скажем билд сервера с запуском тестов, анализаторов и форматеров кода, взаимодействие с которыми кстати тоже можно в эти конфиги сохранить.
А зачем в репу пушить? Распространять решения можно не только через git.

А зачем распространять не через уже существующее средство распространения решений среди команды?