PHP: Я смешиваю программирование, управляемое событиями, с сигнальными интерфейсами (сигналом и слотом / шаблоном наблюдателя)?

Я видел много людей, говорящих, что Symfony2, Zend Framework 2 и другие управляются событиями.

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

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

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

Здесь вы можете прочитать оба:

http://en.wikipedia.org/wiki/Event-driven_programming

http://en.wikipedia.org/wiki/Event-driven_architecture

А вот другой:

http://en.wikipedia.org/wiki/Signal_programming

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

  • Описание тега Stackoverflow [singals]

Более того, то, что я использовал для вызова событий, похоже, больше связано с шаблоном сигналов и слотов, введенным Qt (реализация шаблона наблюдателя)

Например, есть Prado Framework, который утверждает, что управляется событиями:

http://www.pradosoft.com/demos/quickstart/?page=Fundamentals.Applications (раздел Application Lifecycles)

http://www.pradosoft.com/docs/manual/System/TApplication.html#methodonEndRequest

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

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

Итак, какова на самом деле связь между этими терминами и как они отличаются друг от друга?

  1. Программирование, управляемое событиями
  2. Архитектура, управляемая событиями
  3. Шаблоны сигналов и слотов

Являются ли эти термины просто универсальными шаблонами? Следовательно, все, что реализует шаблон наблюдателя, можно считать управляемым событиями?

ОБНОВИТЬ

Zend Framework 2

Статья об АОП, которую я связал выше ( http://mwop.net/blog/251-Aspects,-Filters,-and-Signals,-Oh-My.html ), был написан Мэтью Вейером О'Пинни ( ZF Leader). IIRC, в нем нет упоминаний о «управляемом событиями», просто сигнале и слотах.

Symfony 2

В описании компонента Symfony2 EventDispatcher нет упоминаний о том, что для приложений, управляемых событиями: http://symfony.com/doc/current/components/event_dispatcher/introduction.html Он содержит только ссылки на «События» (которые, действительно, обрабатываются сигналом и слотами).

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

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

Я боролся с этими темами последние пару дней, и, если я все правильно прочитал, ответ таков:

Управляемый событиями! == Запрос

«[…] Я нахожу это самым интересным различием в совместной работе с событиями, перефразируя Jon Udell: программное обеспечение, управляемое запросами, говорит, когда говорят, программное обеспечение, управляемое событиями, говорит, когда ему есть что сказать.

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

Мартин Фаулер – Сотрудничество с событиями (раздел «Запросы»)

Основываясь на этом утверждении, IIRC, современные фреймворки PHP реализуют шаблон Observer + перехватывающие фильтры + Singal и Slots, чтобы инициировать некоторые события в течение цикла запроса.

Но, несмотря на то, что он принимает некоторые идеи управляемых событиями архитектур, он, похоже, не поддерживает то, что вся инфраструктура управляется событиями (то есть Symfony2 – это фейдверк, управляемый событиями).

Мы привыкли делиться программами на несколько компонентов, которые сотрудничают друг с другом. (Я использую неопределенное слово «компонент» здесь преднамеренно, поскольку в этом контексте я имею в виду много вещей: включая объекты внутри программы и несколько процессов, взаимодействующих по сети.) Наиболее распространенным способом их совместной работы является стиль запроса / ответа , Если объект клиента хочет получить некоторые данные от объекта продавца, он вызывает метод на объекте продавца, чтобы спросить его об этих данных.

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

Мартин Фоулер – Фокус на события (раздел: Использование событий для совместной работы)

Я думаю, что приложения PHP более тесно управляются событиями, чем запрос, только когда фокус находится на событиях . Если эти приложения / фреймворки используют события только для сквозных задач (АОП), то это не связано с событиями. Точно так же вы не будете называть его управляемым с помощью теста или управляемым доменом только потому, что у вас есть некоторые объекты домена и модульные тесты.

Примеры реальных стран

Я привел несколько примеров, чтобы показать, почему эти рамки не полностью управляются событиями. Несмотря на события АОП, все зависит от запросов :

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

Zend Framework 2

Рассмотрим компонент \ Zend \ Mvc \ Application :

Он реализует \ Zend \ EventManager \ EventManagerAwareInterface и полагается на \ Zend \ Mvc \ MvcEvent, который описывает возможные события:

 class MvcEvent extends Event { /**#@+ * Mvc events triggered by eventmanager */ const EVENT_BOOTSTRAP = 'bootstrap'; const EVENT_DISPATCH = 'dispatch'; const EVENT_DISPATCH_ERROR = 'dispatch.error'; const EVENT_FINISH = 'finish'; const EVENT_RENDER = 'render'; const EVENT_ROUTE = 'route'; // [...] } 

Компонент \ Zend \ Mvc \ Application сам управляется событиями, потому что он не взаимодействует напрямую с другими компонентами, но вместо этого он просто запускает события:

 /** * Run the application * * @triggers route(MvcEvent) * Routes the request, and sets the RouteMatch object in the event. * @triggers dispatch(MvcEvent) * Dispatches a request, using the discovered RouteMatch and * provided request. * @triggers dispatch.error(MvcEvent) * On errors (controller not found, action not supported, etc.), * populates the event with information about the error type, * discovered controller, and controller class (if known). * Typically, a handler should return a populated Response object * that can be returned immediately. * @return ResponseInterface */ public function run() { $events = $this->getEventManager(); $event = $this->getMvcEvent(); // Define callback used to determine whether or not to short-circuit $shortCircuit = function ($r) use ($event) { if ($r instanceof ResponseInterface) { return true; } if ($event->getError()) { return true; } return false; }; // Trigger route event $result = $events->trigger(MvcEvent::EVENT_ROUTE, $event, $shortCircuit); if ($result->stopped()) { $response = $result->last(); if ($response instanceof ResponseInterface) { $event->setTarget($this); $events->trigger(MvcEvent::EVENT_FINISH, $event); return $response; } if ($event->getError()) { return $this->completeRequest($event); } return $event->getResponse(); } if ($event->getError()) { return $this->completeRequest($event); } // Trigger dispatch event $result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit); // Complete response $response = $result->last(); if ($response instanceof ResponseInterface) { $event->setTarget($this); $events->trigger(MvcEvent::EVENT_FINISH, $event); return $response; } $response = $this->getResponse(); $event->setResponse($response); return $this->completeRequest($event); } 

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

Но есть одна важная вещь: это слой презентации (Controller + View). Уровень домена действительно может управляться событиями, но это не тот случай, когда почти все приложения вы видите там. ** Существует сочетание между управляемыми событиями и запросами:

 // albums controller public function indexAction() { return new ViewModel(array( 'albums' => $this->albumsService->getAlbumsFromArtist('Joy Division'), )); } 

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

Symfony 2

Теперь давайте рассмотрим то же самое на Symfony2 Application / FrontController: \ Symfony \ Component \ HttpKernel \ HttpKernel

В нем действительно есть основные события во время запроса: Symfony \ Component \ HttpKernel \ KernelEvents

 /** * Handles a request to convert it to a response. * * Exceptions are not caught. * * @param Request $request A Request instance * @param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) * * @return Response A Response instance * * @throws \LogicException If one of the listener does not behave as expected * @throws NotFoundHttpException When controller cannot be found */ private function handleRaw(Request $request, $type = self::MASTER_REQUEST) { // request $event = new GetResponseEvent($this, $request, $type); $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); if ($event->hasResponse()) { return $this->filterResponse($event->getResponse(), $request, $type); } // load controller if (false === $controller = $this->resolver->getController($request)) { throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". Maybe you forgot to add the matching route in your routing configuration?', $request->getPathInfo())); } $event = new FilterControllerEvent($this, $controller, $request, $type); $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); $controller = $event->getController(); // controller arguments $arguments = $this->resolver->getArguments($request, $controller); // call controller $response = call_user_func_array($controller, $arguments); // view if (!$response instanceof Response) { $event = new GetResponseForControllerResultEvent($this, $request, $type, $response); $this->dispatcher->dispatch(KernelEvents::VIEW, $event); if ($event->hasResponse()) { $response = $event->getResponse(); } if (!$response instanceof Response) { $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response)); // the user may have forgotten to return something if (null === $response) { $msg .= ' Did you forget to add a return statement somewhere in your controller?'; } throw new \LogicException($msg); } } return $this->filterResponse($response, $request, $type); } 

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

Вместо этого, чтобы быть полностью управляемым событиями компонентом, он должен быть как в компоненте Application ZF2:

  // Trigger dispatch event $result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit); 

Prado

У меня недостаточно времени для изучения исходного кода, но сначала он, кажется, не построен с помощью SOLID . В любом случае, что такое контроллер на MVC-подобных платформах, Prado называет его TPage (еще не уверен):

http://www.pradosoft.com/demos/blog-tutorial/?page=Day3.CreateNewUser

И он действительно общается напрямую с компонентами:

 class NewUser extends TPage { /** * Checks whether the username exists in the database. * This method responds to the OnServerValidate event of username's custom validator. * @param mixed event sender * @param mixed event parameter */ public function checkUsername($sender,$param) { // valid if the username is not found in the database $param->IsValid=UserRecord::finder()->findByPk($this->Username->Text)===null; } [...] } 

Я понимаю, что TPage является слушателем событий и может быть подключаемым. Но это не приводит к тому, что ваша модель домена управляется событиями. Поэтому я думаю, что в некоторой степени это ближе к предложению ZF2.

Примеры, основанные на событиях

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

Развязка приложений с доменами События http://www.whitewashing.de/2012/08/25/decoupling_applications_with_domain_events.html

Поиск событий http://martinfowler.com/eaaDev/EventSourcing.html

Шаблон событий домена http://martinfowler.com/eaaDev/DomainEvent.html

Совместное мероприятие http://martinfowler.com/eaaDev/EventCollaboration.html

Перехват события http://martinfowler.com/bliki/EventInterception.html

Конечная точка сообщения http://www.enterpriseintegrationpatterns.com/MessageEndpoint.html

… и так далее

PHP не является апатридом, HTTP – это. Чтобы сказать это просто, мы по существу построили слой поверх технологии без состояния, на которой мы можем реализовать проекты с сохранением состояния. В совокупности PHP и ваш выбор хранилища имеют все инструменты, необходимые для построения дизайна приложения на основе шаблонов, управляемых событиями, посредством токенизации сеансов.

В очень обобщенном виде вы можете думать о HTTP как о том, что такое BIOS для настольных компьютеров. На самом деле, сделайте это чуть чуть-чуть, и вы можете легко увидеть неявный, управляемый событиями характер сети. Вы сказали: «Это не событие, а целый новый запрос», и я возвращаюсь с «новым новым запросом это событие», и я имею в виду это в смысле шаблона дизайна. Он имеет конкретное смысловое значение, связанное с взаимодействием вашего пользователя с вашим приложением.

По существу, с помощью таких шаблонов, как MVC и Front Controller (и механизмом HTTP-файлов cookie и сеансов PHP), мы просто восстанавливаем состояние сеанса, а затем отвечаем на событие, соответствующим образом изменяя это состояние.

Мне нравится рассматривать суть REST: State State State Transfer … но я бы добавил, что мы не должны забывать о неявной импликации, что состояние передается только при возникновении события пользовательского интерфейса. Таким образом, мы поддерживаем контракты с HTTP, которые мы «говорим» только в «Представлениях» нашей модели (например, документ, JSON и т. Д.), Но это только наш диалект. Другие системы предпочитают говорить в координатах холста, сигнале db и т. Д.

редактировать / больше мысли

Поэтому я размышлял об этом некоторое время, и я думаю, что существует концепция, которая немного иллюстрирует двусмысленность при обсуждении этих шаблонов в области PHP через HTTP: детерминизм. В частности, как только запрос получен, путь выполнения PHP является детерминированным, и именно поэтому чрезвычайно сложно рассматривать архитектуру, управляемую событиями, в PHP. Мое понятие состоит в том, что мы должны рассматривать один уровень выше, чем PHP, к более крупному «сеансу» взаимодействия с пользователем.

В настольных компьютерах мы используем runloops и state-ful context для «ожидания» событий. Тем не менее, я буду утверждать, что сеть фактически является улучшением по сравнению с этой архитектурой (в большинстве случаев), но, в конечном счете, с той же моделью . Вместо состояния runloop и бесконечной продолжительности мы загружаем наше состояние, когда происходит событие, а затем обрабатываем это событие. И вместо того, чтобы просто сохранять это состояние в памяти и ждать следующего события, мы архивируем это состояние и закрываем ресурсы. Его можно считать менее эффективным в одном смысле (нам нужно загружать состояние на каждом «событии»), но его также можно назвать более эффективным, поскольку в памяти никогда не бывает бездействия . Мы загружаем только состояние, которое фактически потребляется / управляется

Таким образом, подумайте о PHP через HTTP как о событии, управляемом на уровне макросов , в то же время сохраняя уверенность в том, что любое задание действительно детерминировано и фактически не управляется событиями. Тем не менее, мы реализуем шаблон фронт-контроллера и MVC, чтобы мы могли предоставить разработчикам приложений привычную структуру даже ведомых крючков. Когда вы работаете с достойной структурой, вы просто говорите: «Я хочу знать, когда пользователь регистрируется, и пользователь должен быть доступен мне для изменения в это время». Это развитие событий. Вы не должны беспокоиться о том, что инфраструктура загрузила среду для (почти) единственной цели вызова вашего крюка (по сравнению с более традиционным понятием, что среда уже была там, и вы просто были уведомлены о событии). Это то, что означает развитие PHP в управляемом событиями пути. Контроллеры определяют (на основе запроса), какое событие происходит, и использует какой-либо механизм, который он предназначен для использования (например, шаблон наблюдателя, архитектура захвата и т. Д.), Чтобы позволить вашему коду обрабатывать событие, отвечать на событие или любую номенклатуру наиболее подходит для семантики вашей конкретной структуры.