Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Архитектура Symfony 3, применение паттернов про...

Архитектура Symfony 3, применение паттернов проектирования

Москва, Symfoniacs, 17.11.2016

Avatar for Gleb Tiltikov

Gleb Tiltikov

November 16, 2016
Tweet

Other Decks in Programming

Transcript

  1. это кладезь паттернов В разработке программного обеспечения паттерн проектирования является

    абстрактным общим подходом для решения конкретной распространенной проблемы
  2. Типы паттернов Порождающие Порождающие это такие паттерны которые имеют дело

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

    источников данных • Паттерны объектно реляционной логики структурирования и метаданных • Паттерны логики сущности • Паттерны распределения данных • Паттерны локальной конкуренции
  4. → • Вначале фронт контроллер выполняет загрузку приложения используя класс

    ядра • Затем создает объект используя глобальные переменные и направляет его ядру • На последнем шаге происходит отправка пользователю содержательной части ответа возвращенного от ядра т е объекта 5
  5. /* app.php */ $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); $request

    = Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response); /* bin/console */ $loader = require __DIR__.'/../app/autoload.php'; $input = new ArgvInput(); $env = $input->getParameterOption([ '--env', '-e'], getenv('SYMFONY_ENV') ?: 'dev'); $debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption([ '--no-debug', '']) && $env !== 'prod'; if ($debug) { Debug::enable(); } $kernel = new AppKernel($env, $debug); $application = new Application( $kernel); $application ->run($input); web/app.php & bin/console Основной целью, является создание объекта ядра AppKernel, обработка с его помощью запроса и возвращение соответствующего ответа.
  6. Команда Реализует такую структуру в которой класс отправитель и класс

    получатель не зависят друг от друга напрямую а также преобразовывает запрос на выполнение действия в отдельный объект команду
  7. protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { if

    (null === $this->dispatcher) { return $command->run($input, $output); } } Команда [Command] Symfony/Component/Console/Application.php
  8. Фасад Скрывает сложность системы путем сведения всех возможных внешних вызовов

    к одному объекту делегирующему их соответствующим объектам системы
  9. Контроллер Фасад [Facade] Предоставляет высокоуровневый унифицированный интерфейс к набору интерфейсов

    некоторой подсистемы, тем самым облегчая работу по ее использованию.
  10. Цепочка обязанностей Позволяет обработать запрос нескольким объектам получателям которые связываются

    в последовательность а запрос передается по цепочке пока он не будет обработан каким то объектом получателем Избегая жесткой зависимости между отправителем и получателями запроса
  11. class ParserChain { protected $first = null; public function __construct(array

    $parsers) { $current = null; foreach ($parsers as $parser) { if (is_null($this->first)) { $this->first = $parser; } if (!is_null($current)) { $current->setNext($parser); } $current = $parser; } } public function parse(SplFileObject $file) { if ($this->first) { return $this->first->parse($file); } throw new RuntimeException( "Empty chain!"); } } Цепочка обязанностей [Chain of responsibility] Реализация цепочки обязанностей, с помощью контейнера Symfony. Вначале создаем главный класс нашей цепочки
  12. abstract class Parser { protected static $format; protected $next; public

    function setNext(Parser $next) { $this->next = $next; } protected function canHandle(SplFileObject $file) { return $file->getExtension() == static::$format; } public function parse(SplFileObject $file) { if ($this->canHandle($file)) { return $this->doParse($file); } else { if (is_null($this->next)) { throw new InvalidArgumentException($file->getExtension(). " extension is not supported"); } return $this->next->parse($file); } } abstract public function doParse(SplFileObject $file); } Цепочка обязанностей [Chain of responsibility] Определим классы, которые потребуются для непосредственных обработчиков цепочки.
  13. class JsonParser extends Parser { public static $format = "json";

    public function doParse(SplFileObject $file) { //parse the file } } /* ... */ class XmlParser extends Parser { public static $format = "xml"; public function doParse(SplFileObject $file) { //parse the file } } Цепочка обязанностей [Chain of responsibility] Определим несколько классов, парсеров-обработчиков.
  14. services: my.parser.json: class: JsonParser public: false my.parser.xml: class: XmlParser public:

    false my.parser_chain: class: ParserChain arguments: - [@my.parser.json, @my.parser.xml] $file = new SplFileObject(__DIR__ . "/assets/test.xml"); $chain = $this->container->get("my.parser_chain"); $parsed = $chain->parse($file); Цепочка обязанностей [Chain of responsibility] Связываем все воедино, используя контейнер. Парсеры объявлены закрытыми сервисами, они передаются параметрами в конструктор ParserChain.
  15. Фабричный метод Основной целью этого паттерна является вложение порождающей процедуры

    реализованной в подклассах и которая может свести различные классы в одну функцию Если фабричному методу обеспечить надлежащий контекст то он будет в состоянии вернуть правильный объект Factory method [Фабричный метод]
  16. Фабрика для создания сервиса class NewsletterManagerFactory { public static function

    createNewsletterManager() { $newsletterManager = new NewsletterManager(); return $newsletterManager ; } } /* конфиг */ services: # ... app.newsletter_manager_factory: class: AppBundle\Email\NewsletterManagerFactory app.newsletter_manager: class: AppBundle\Email\NewsletterManager factory: ['@newsletter_manager_factory', createNewsletterManager] Фабричный метод [Factory method] Наилучшей ситуацией для использования шаблона «Фабричный метод» является наличие нескольких различных вариантов одного объекта.
  17. Стратегия Если поведение системы настраивается согласно одному из нескольких алгоритмов

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

    основываясь на определенном контексте при выполнении Такую систему проще расширять и поддерживать
  19. Стратегия хранения данных для • Профайлер сохраняет собранные данные в

    некое место хранения • Алгоритмы сохранения должны быть взаимозаменяемыми interface ProfilerStorageInterface { public function find($ip, $url, $limit, $method, $start = null, $end = null); public function read($token); public function write(Profile $profile); public function purge(); } Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface Стратегия [Strategy] Интерфейс профайлера, определены все необходимые возможности
  20. Место хранения по умолчанию class FileProfilerStorage implements ProfilerStorageInterface { /*

    ... */ public function read($token) { if (!$token || !file_exists($file = $this->getFilename( $token))) { return; } return $this->createProfileFromData( $token, unserialize(file_get_contents($file))); } public function write(Profile $profile) { // добавление данных в хранилище $file = $this->getFilename( $profile->getToken()); $data = array(/* ... */); if (false === file_put_contents($file, serialize($data))) { return false; } /* ... */ } } Symfony/Component/HttpKernel/Profiler/FileProfilerStorage Стратегия [Strategy] По-умолчанию профайлер сохраняет данные на диск
  21. Место хранения abstract class PdoProfilerStorage implements ProfilerStorageInterface { public function

    read($token) { $db = $this->initDb(); $args = array(':token' => $token); $data = $this->fetch($db, 'SELECT ...', $args); $this->close($db); if (isset($data[0]['data'])) { return $this->createProfileFromData( $token, $data[0]); } } } Symfony/Component/HttpKernel/Profiler/PdoProfilerStorage Стратегия [Strategy] Альтернативный механизм сохранения, сохраняет в базу данных
  22. Использование профайлера и выбор стратегии // Строка данных ‘abcdef’, которую

    нужно сохранить $profile = new Profile('abcdef'); // Место хранения — файловая система $fsStorage = new FileProfilerStorage('/tmp/profiler'); $profiler = new Profiler($fsStorage); $profiler->saveProfile($profile); // Место хранения — Mysql $dbStorage = new MySQLProfilerStorage('mysql:host=localhost'); $profiler = new Profiler($dbStorage); $profiler->saveProfile($profile); Стратегия [Strategy] MySQLProfilerStorage - это некий класс, наследованный от PdoProfilerStorage
  23. Строитель Определяет процесс поэтапного конструирования сложного объекта отделяет конструирование сложных

    объектов от их представлений в результате которого могут получаться разные представления этого объекта
  24. interface FormBuilderInterface extends \Traversable, \Countable, FormConfigBuilderInterface { public function add($child,

    $type = null, array $options = array()); public function create($name, $type = null, array $options = array()); public function get($name); public function remove($name); public function has($name); public function all(); public function getForm(); } /* пример */ $task = new Post(); $task->setTitle("Название"); $task->setPublishedAt( new \DateTime("tomorrow")); $form = $this->createFormBuilder( $task) ->add("title", TextType::class) ->add("published_at", DateType::class) ->add("save", SubmitType:: class, array('label' => 'Добавить')) ->getForm(); Строитель [Builder] Symfony/Component/Form/FormBuilderInterface
  25. Ленивая инициализация Приём в программировании когда некоторая ресурсоемкая операция выполняется

    непосредственно перед тем как будет использован её результат Таким образом инициализация выполняется не заблаговременно а по требованию
  26. Пул объектов $this->container->set('myService', 'stringValue'); $myService = $this->container->get('myService'); Symfony/Component/DependencyInjection/Container Предназначен для

    хранения записей которые в него помещают и соответственно возвращения этих записей, если они потребуются.
  27. protected $services = array(); protected $methodMap = array(); public function

    get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) { // Использование существующего экземпляра сервиса if (isset($this->services[$id])) { return $this->services[$id]; } if (isset($this->methodMap[$id])) { $method = $this->methodMap[$id]; } $this->loading[$id] = true; try { $service = $this->$method(); } catch (\Exception $e) { unset($this->services[$id]); throw $e; } finally { unset($this->loading[$id]); } return $service; } Symfony/Component/DependencyInjection/Container.php • Ленивая инициализация • Фабричный метод • Пул объектов // Logger создан по требованию $log1 = $container->get('logger'); // Logger просто извлечен из массива $log2 = $container->get('logger');
  28. Адаптер Некая программная обертка над уже существующими классами и предназначена

    для преобразования их интерфейсов к виду пригодному для последующего использования в новом программном проекте
  29. Адаптер [Adapter] /* BookInterface.php */ interface BookInterface { public function

    turnPage(); public function open(); public function getPage(): int; } /* Book.php */ class Book implements BookInterface { private $page; public function open() { $this->page = 1; } public function turnPage() { $this->page++; } public function getPage(): int { return $this->page; } }
  30. Адаптер [Adapter] /* Kindle.php */ class Kindle implements EBookInterface {

    private $page = 1; private $totalPages = 100; public function pressNext() { $this->page++; } public function unlock() {} public function getPage(): array { return [$this->page, $this->totalPages]; } } /* EBookInterface.php */ interface EBookInterface { public function unlock(); public function pressNext(); public function getPage(): array; }
  31. Адаптер [Adapter] /* EBookAdapter.php */ class EBookAdapter implements BookInterface {

    protected $eBook; public function __construct(EBookInterface $eBook) { $this->eBook = $eBook; } public function open() { $this->eBook->unlock(); } public function turnPage() { $this->eBook->pressNext(); } public function getPage(): int { return $this->eBook->getPage()[0]; } } EBookAdapter — приводит возможности электронной книги к обычной
  32. Адаптер [Adapter] // Обычная книга $book = new Book(); $book->open();

    $book->turnPage(); // $book->getPage() -> 2 // Электронная книга, пролистываем страницу, как в обычной книге $kindle = new Kindle(); $book = new EBookAdapter($kindle); $book->open(); $book->turnPage(); // $book->getPage() -> 2
  33. Мост [Bridge] /* FormatterInterface.php */ interface FormatterInterface { public function

    format(string $text); } /* PlainTextFormatter.php */ class PlainTextFormatter implements FormatterInterface { public function format(string $text) { return $text; } } /* HtmlFormatter.php */ class HtmlFormatter implements FormatterInterface { public function format(string $text) { return sprintf('<p>%s</p>', $text); } } Например, по условиям задачи, необходимо в определенный момент, использовать определенный класс форматирования текста
  34. Мост [Bridge] /* Service.php */ abstract class Service { protected

    $implementation; public function __construct(FormatterInterface $printer) { $this->implementation = $printer; } public function setImplementation(FormatterInterface $printer) { $this->implementation = $printer; } abstract public function get(); } Абстрактный класс, который отделен от реализации различных методов форматирования текста, но имеет внутри свойство, содержащее объект, реализующий необходимый функционал.
  35. Мост [Bridge] /* HelloWorldService.php */ class HelloWorldService extends Service {

    public function get() { return $this->implementation ->format('Hello World'); } } /* Test.php */ $service = new HelloWorldService(new PlainTextFormatter()); // $service->get() -> 'Hello World' $service->setImplementation(new HtmlFormatter()); // $service->get() -> '<p>Hello World</p>' Необходимый объект, умеющий форматировать текст, — передан “мостом” в сервис, которому необходима такая функциональность. Имплементации обоих объектов полностью отделены друг от друга.
  36. Декоратор Динамически расширяет функциональность объектов добавляя объекту новые возможности при

    этом весь оригинальный интерфейс объекта остается доступным Возможность безболезненного комбинирования различных декораторов в произвольном порядке навешивая их на различные объекты и в то же время иметь возможность создать оригинальный экземплярю
  37. Декоратор [Decorator] Стандартная реализация HttpKernel не поддерживает возможности кэширования. HttpCache

    класс, для декорирования экземпляра HttpKernel, эмулируя HTTP реверсивный прокси кэш.
  38. $dispatcher = new EventDispatcher(); $resolver = new ControllerResolver(); $store =

    new Store(__DIR__. '/http_cache' ); $httpKernel = new HttpKernel($dispatcher, $resolver); $httpKernel = new HttpCache($httpKernel, $store); /* объект $httpKernel помимо имплементации возможностей HttpKernelInterface также обладает дополнительными возможностями класса HttpCache */ $httpKernel ->handle(Request:: createFromGlobals()) ->send() ; Декоратор [Decorator]
  39. Посредник Инкапсулирует взаимодействие совокупности объектов в отдельный объект посредник Уменьшает

    степень связанности взаимодействующих объектов им не нужно хранить ссылки друг на друга они взаимодействуют друг с другом косвенно
  40. Управляет соединениями между событием и настроенными на него наблюдателями Посредник

    [Mediator] • Цепочка обязанностей • Посредник • Наблюдатель
  41. class EventDispatcher implements EventDispatcherInterface { public function dispatch($eventName, Event $event

    = null); public function getListeners($eventName = null); public function getListenerPriority( $eventName, $listener); public function hasListeners($eventName = null); public function addListener($eventName, $listener, $priority = 0); public function removeListener( $eventName, $listener); public function addSubscriber(EventSubscriberInterface $subscriber); public function removeSubscriber(EventSubscriberInterface $subscriber); protected function doDispatch($listeners, $eventName, Event $event); private function sortListeners( $eventName); } Посредник [Mediator] Symfony/Component/EventDispatcher/EventDispatcher
  42. Создаем собственное событие class OrderEvent extends Event { private $order;

    public function __construct(Order $order) { $this->order = $order; } public function getOrder() { return $this->order; } } Посредник [Mediator] Например, есть заказ, и нам необходимо будет производить какие-то действия, в определенные моменты.
  43. Пример класса слушателя class CustomerListener { private $mailer; public function

    __construct(\Swift_Mailer $mailer) { $this->mailer = $mailer; } public function onOrderPaid(Event $event) { $mail = $this->mailer->createMessage(...); $this->mailer->send($mail); } } Посредник [Mediator] Класс, реализующий алгоритм обработки заказа, в случае, если наступило событие onOrderPaid
  44. Назначаем событиям слушателей $listener1 = new CustomerListener( $mailer); $listener2 =

    new SalesListener( $mailer); $listener3 = new StockListener( $stockHandler); $dp = new EventDispatcher(); $dp->addListener( 'order.paid', [ $listener1, 'onOrderPaid' ]); $dp->addListener( 'order.paid', [ $listener2, 'onOrderPaid' ]); $dp->addListener( 'order.paid', [ $listener3, 'onOrderPaid' ], 100); $dp->dispatch('order.paid', new Event()); // Уведомляем подписчиков Посредник [Mediator] Например, есть кастомное событие, происходящее с заказом «order» , можно очень просто начать наблюдение за ними. И уведомлять всех подписчиков.
  45. Паттерны не панацея не лекарство от кривых рук и не

    замена для мозгов Этот трафарет сам не рисует рисуем им мы
  46. Узнайте паттерн по коду // Целевой интерфейс, клиент умеет работать

    только с ним interface iTarget { public function query(); } // Другой интерфейс. Клиент с ним не умеет работать, но очень хочет interface iOther { public function request(); } /* пример */ $Target = new MyClass(); print $Target->query(); // "Other::request" # 1 // Класс, реализующий другой интерфейс class Other implements iOther { public function request() { return __CLASS__ . "::" . __METHOD__; } } class MyClass implements iTarget { protected $other = null; public function __construct() { $this->other = new Other(); } public function query() { return $this->other->request(); } }
  47. Узнайте паттерн по коду # 2 abstract class DevPol {

    abstract public function make(); } class iOS extends DevPol { public function make() { /* подключение доп. стилей */ } } class Android extends DevPol { public function make() { /* подключение доп. стилей */ } } class AppProc { public function __construct() { $app = App::get_device(); switch ($app->os) { case 'iOS': $obj = new iOS(); $obj->make(); break; case 'Android': $obj = new Android(); $obj->make(); break; } } }
  48. Узнайте паттерн по коду # 3 interface Logger { function

    onChanged($sender, $args); } interface MyInterface { function addLogger($logger); } class CustomerListLogger implements Logger { public function onChanged($sender, $args) { echo( "'$args' заказчик добавлен в список \n" ); } } class CustomerList implements MyInterface { private $_loggers = []; public function addCustomer( $name) { foreach($this->_loggers as $obs) { $obs->onChanged( $this, $name); } } public function addLogger($logger) { $this->_loggers[] = $logger; } } /* пример */ $cl = new CustomerList(); $cl->addLogger( new CustomerListLogger()); $cl->addCustomer( "Symfoniac");