Статьи

Как создать фотогалерею НАСА с Zend Expressive

Эта статья была рецензирована Абдул Малик Ихсан и Мэтью Вейер О’Финни . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!


В этой статье мы собираемся использовать Zend Expressive для создания фотогалереи с помощью NASA Astronomy Picture of the Day API. Конечным результатом будет веб- сайт AstroSplash, созданный для этой статьи.

НАСА Фотогалерея

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

Промежуточное программное обеспечение — это термин, который будет часто использоваться в этой статье. Хорошее определение промежуточного программного обеспечения дается в документации Zend Expressive :

«Промежуточное программное обеспечение — это любой код, находящийся между запросом и ответом; обычно он анализирует запрос для агрегирования входящих данных, передает его другому уровню для обработки, а затем создает и возвращает ответ ».

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

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

Представляем наше приложение

Мы собираемся создать приложение с использованием API, предоставленного НАСА для их веб-сайта Astronomy Picture of the Day . Это отличный веб-сайт, который предоставляет некоторые захватывающие ежедневные изображения, но он немного устарел. С некоторой работой мы могли бы использовать этот API, чтобы создать действительно легкую для просмотра фотогалерею!

Читая эту статью, она может помочь ссылаться на публичный репозиторий AstroSplash на GitHub . Он содержит исходный код готового приложения, которое доступно на astrosplash.com .

Создание Zend Expressive проекта

Рекомендуется, но не обязательно, использовать виртуальную машину Homestead Improved Vagrant для быстрого создания среды разработки.

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

composer create-project -s rc zendframework/zend-expressive-skeleton <project-directory> 

Мы должны заменить <project-directory> на имя каталога, в который мы собираемся установить Zend Expressive. При использовании Homestead Improved Vagrant VM это будет Project и команда должна быть запущена в каталоге Code . Если установщик жалуется на уже существующий каталог Project , просто удалите его и снова введите команду.

Установщик даст нам возможность выбрать один из нескольких различных компонентов, поддерживаемых платформой. Мы будем в основном придерживаться значений по умолчанию и использовать FastRoute, Zend ServiceManager и обработчик ошибок Whoops. Не существует выбора по умолчанию для шаблонизатора, поэтому мы собираемся использовать пластины.

Zend выразительный установщик скелета

Если мы загрузим приложение в браузере, мы должны увидеть страницу, которая приветствует нас в Zend Expressive! Просмотрите файлы, которые были созданы для нас, обращая особое внимание на каталог config . Он содержит все данные, которые Zend ServiceManager будет использовать для создания контейнера, который является сердцем нашего приложения Zend Expressive.

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

 rm public/favicon.ico rm public/zf-logo.png rm src/Action/* rm test/Action/* rm templates/app/* rm templates/layout/* 

Конфигурирование контейнера

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

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

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

Имея это в виду, перейдите в config/autoload/dependencies.global.php и замените содержимое следующим:

 <?php return [ 'dependencies' = > [ 'factories' = > [ Zend\ Expressive \ Application : : class = > Zend\ Expressive \ Container \ ApplicationFactory : : class , ] , ] , ] ; 

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

Первым сервисом, который нужно создать, является сервис приложений. Если вы посмотрите на фронтальный контроллер ( public/index.php ), то увидите, что он извлекает службу приложения из контейнера для запуска нашего приложения. Этот сервис имеет зависимости, поэтому мы должны перечислить его под ключом factories . Сделав это, мы сказали Zend ServiceManager, что он должен использовать данный класс фабрики для создания службы. Zend Expressive предоставляет множество других фабрик для создания некоторых основных сервисов.

Затем откройте config/autoload/routes.global.php и замените содержимое на:

 <?php return [ 'dependencies' = > [ 'invokables' = > [ Zend\ Expressive \ Router \ RouterInterface : : class = > Zend\ Expressive \ Router \ FastRouteRouter : : class , ] , 'factories' = > [ App\ Action \ IndexAction : : class = > App\ Action \ IndexFactory : : class , ] ] , 'routes' = > [ [ 'name' = > 'index' , 'path' = > '/' , 'middleware' = > App\ Action \ IndexAction : : class , 'allowed_methods' = > [ 'GET' ] , ] , ] , ] ; 

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

Ключ routes будет загружен в маршрутизатор Zend Expressive и должен содержать массив дескрипторов маршрута. В одном дескрипторе маршрута, который мы определили, ключ path соответствует записи в индексном маршруте, ключ middleware сообщает платформе, какую службу использовать в качестве обработчика, а ключ allowed_methods указывает, какие методы HTTP разрешены. Для ключа allowed_methods можно указать Zend\Expressive\Router\Route::HTTP_METHOD_ANY чтобы указать, что любой метод HTTP разрешен.

Маршрутное промежуточное ПО

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

Наш класс действий будет расположен относительно корня нашего проекта в src/Action/IndexAction.php . Внутри это будет выглядеть так:

 <?php namespace App \ Action ; use Psr \ Http \ Message \ ServerRequestInterface ; use Psr \ Http \ Message \ ResponseInterface ; use Zend \ Expressive \ Template \ TemplateRendererInterface ; use Zend \ Stratigility \ MiddlewareInterface ; class IndexAction implements MiddlewareInterface { private $templateRenderer ; public function __construct ( TemplateRendererInterface $templateRenderer ) { $this - > templateRenderer = $templateRenderer ; } public function __invoke ( ServerRequestInterface $request , ResponseInterface $response , callable $next = null ) { $html = $this - > templateRenderer - > render ( 'app::index' ) ; $response - > getBody ( ) - > write ( $html ) ; return $response - > withHeader ( 'Content-Type' , 'text/html' ) ; } } 

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

Наличие магического метода __invoke делает этот класс вызываемым . Он вызывается с сообщениями PSR-7 в качестве параметров, а также с дополнительным промежуточным программным обеспечением в цепочке. Поскольку все запросы на индексирование обрабатываются этим промежуточным программным обеспечением, нам не нужно вызывать следующее промежуточное программное обеспечение в цепочке, и вместо этого мы можем напрямую возвращать ответ. Подпись, используемая здесь для вызываемого промежуточного программного обеспечения, очень распространена:

 public function __invoke ( ServerRequestInterface $request , ResponseInterface $response , callable $next = null ) ; 

Промежуточное программное обеспечение, созданное с использованием этого шаблона, также будет поддерживаться Relay , диспетчером промежуточного программного обеспечения PSR-7. Аналогично, промежуточное программное обеспечение, созданное для платформы Slim v3, другой платформы промежуточного программного обеспечения PSR-7, будет совместимо с Zend Expressive. В настоящее время Slim предоставляет промежуточное ПО для защиты CSRF и HTTP-кэширования .

Когда наше действие вызывается, оно будет отображать шаблон app::index , записывать его в тело нашего ответа и возвращать ответ с типом содержимого text/html . Поскольку сообщения PSR-7 являются неизменяемыми , каждый раз, когда мы хотим добавить заголовок к ответу, мы должны создавать новый объект ответа. Причины этого объясняются в мета-документе для спецификации PSR-7 .

Затем мы должны написать фабричный класс, который контейнер будет использовать для создания экземпляра нашего класса действий индекса. Наш фабричный класс будет расположен относительно корня нашего проекта в src/Action/IndexFactory.php . Внутри наш завод будет выглядеть так:

 <?php namespace App \ Action ; use Interop \ Container \ ContainerInterface ; use Zend \ Expressive \ Template \ TemplateRendererInterface ; class IndexFactory { public function __invoke ( ContainerInterface $container ) { $templateRenderer = $container - > get ( TemplateRendererInterface : : class ) ; return new IndexAction ( $templateRenderer ) ; } } 

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

Шаблонирование

Единственная недостающая часть нашей головоломки — шаблонизация. В нашем действии index мы просим средство визуализации шаблона для шаблона app::index , но мы еще этого не создали. Zend Expressive использует нотацию namespace::template для ссылки на шаблоны. В нашей конфигурации контейнера Plates настроен так, чтобы знать, что все шаблоны в пространстве имен app могут быть найдены в templates/app относительно корня проекта и что он должен использовать .phtml в качестве расширения файла шаблона. Два других пространства имен были настроены, error и layout .

Во-первых, давайте создадим шаблон макета. Поскольку имя для этого шаблона будет layout::default , в нашей конфигурации его путь будет templates/layout/default.phtml .

 <!DOCTYPE html> < html lang = " en " > < head > < meta charset = " utf-8 " /> < title > <?=$this->e($title);?> </ title > </ head > < body > <?=$this->section('content')?> </ body > </ html > 

Далее мы создадим шаблон app::index в templates/app/index.phtml . Мы собираемся сделать так, чтобы он расширял шаблон layout::default который мы только что создали. Шаблоны в пространстве имен error уже настроены для расширения шаблона layout::default .

 <?php $this->layout('layout::default', ['title' => 'Astronomy Picture of the Day']) ?> < h1 > Astronomy Picture of the Day App </ h1 > < p > Welcome to my Astronomy Picture of the Day App. It will use an API provided by NASA to deliver awesome astronomy pictures. </ p > 

Загрузите приложение в свой браузер, и вы увидите шаблон, который мы только что создали!

Труба Middleware

В документации Zend Expressive по межплатформенному промежуточному программному обеспечению для конвейеров указано следующее:

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

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

Для начала нам нужно получить библиотеку кеширования.

 composer require doctrine/cache ^1.5 

Далее нам нужно внести следующие дополнения в наш файл config/autoload/dependencies.global.php :

 <?php return [ 'dependencies' = > [ 'factories' = > [  // ... Doctrine\ Common \ Cache \ Cache : : class = > App\ DoctrineCacheFactory : : class , ] , ] , 'application' = > [ 'cache_path' = > 'data/doctrine-cache/' , ] , ] ; 

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

 mkdir data/doctrine-cache 

Последнее изменение, которое нам необходимо внести в нашу конфигурацию, — это сообщить Zend Expressive о нашей службе промежуточного программного обеспечения и добавить ее в канал промежуточного программного обеспечения до того, как произойдет маршрутизация. Откройте config/autoload/middleware-pipeline.global.php и замените его следующим:

 <?php return [ 'dependencies' = > [ 'factories' = > [ App\ Middleware \ CacheMiddleware : : class = > App\ Middleware \ CacheFactory : : class , ] ] , 'middleware_pipeline' = > [ 'pre_routing' = > [ [ 'middleware' = > App\ Middleware \ CacheMiddleware : : class ] , ] , 'post_routing' = > [ ] , ] , ] ; 

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

 <?php namespace App ; use Doctrine \ Common \ Cache \ FilesystemCache ; use Interop \ Container \ ContainerInterface ; use Zend \ ServiceManager \ Exception \ ServiceNotCreatedException ; class DoctrineCacheFactory { public function __invoke ( ContainerInterface $container ) { $config = $container - > get ( 'config' ) ; if ( ! isset ( $config [ 'application' ] [ 'cache_path' ] ) ) { throw new ServiceNotCreatedException ( 'cache_path must be set in application configuration' ) ; } return new FilesystemCache ( $config [ 'application' ] [ 'cache_path' ] ) ; } } 

Наша фабрика промежуточного программного обеспечения, расположенная по адресу src/Middleware/CacheFactory.php , src/Middleware/CacheFactory.php службу кэширования в наше промежуточное программное обеспечение:

 <?php namespace App \ Middleware ; use Doctrine \ Common \ Cache \ Cache ; use Interop \ Container \ ContainerInterface ; class CacheFactory { public function __invoke ( ContainerInterface $container ) { $cache = $container - > get ( Cache : : class ) ; return new CacheMiddleware ( $cache ) ; } } 

Все, что уходит, — это само промежуточное ПО. Создайте src/Middleware/CacheMiddleware.php и поместите в него следующий код:

 <?php namespace App \ Middleware ; use Doctrine \ Common \ Cache \ Cache ; use Psr \ Http \ Message \ ResponseInterface ; use Psr \ Http \ Message \ ServerRequestInterface ; use Zend \ Stratigility \ MiddlewareInterface ; class CacheMiddleware implements MiddlewareInterface { private $cache ; public function __construct ( Cache $cache ) { $this - > cache = $cache ; } public function __invoke ( ServerRequestInterface $request , ResponseInterface $response , callable $next = null ) { $cachedResponse = $this - > getCachedResponse ( $request , $response ) ; if ( null ! == $cachedResponse ) { return $cachedResponse ; } $response = $next ( $request , $response ) ; $this - > cacheResponse ( $request , $response ) ; return $response ; } private function getCacheKey ( ServerRequestInterface $request ) { return 'http-cache:' . $request - > getUri ( ) - > getPath ( ) ; } private function getCachedResponse ( ServerRequestInterface $request , ResponseInterface $response ) { if ( 'GET' ! == $request - > getMethod ( ) ) { return null ; } $item = $this - > cache - > fetch ( $this - > getCacheKey ( $request ) ) ; if ( false === $item ) { return null ; } $response - > getBody ( ) - > write ( $item [ 'body' ] ) ; foreach ( $item [ 'headers' ] as $name = > $value ) { $response = $response - > withHeader ( $name , $value ) ; } return $response ; } private function cacheResponse ( ServerRequestInterface $request , ResponseInterface $response ) { if ( 'GET' ! == $request - > getMethod ( ) || ! $response - > hasHeader ( 'Cache-Control' ) ) { return ; } $cacheControl = $response - > getHeader ( 'Cache-Control' ) ; $abortTokens = array ( 'private' , 'no-cache' , 'no-store' ) ; if ( count ( array_intersect ( $abortTokens , $cacheControl ) ) > 0 ) { return ; } foreach ( $cacheControl as $value ) { $parts = explode ( '=' , $value ) ; if ( count ( $parts ) == 2 && 'max-age' === $parts [ 0 ] ) { $this - > cache - > save ( $this - > getCacheKey ( $request ) , [ 'body' = > ( string ) $response - > getBody ( ) , 'headers' = > $response - > getHeaders ( ) , ] , intval ( $parts [ 1 ] ) ) ; return ; } } } } 

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

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

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

 public function __invoke ( ServerRequestInterface $request , ResponseInterface $response , callable $next = null ) { $html = $this - > templateRenderer - > render ( 'app::index' ) ; $response - > getBody ( ) - > write ( $html ) ; return $response - > withHeader ( 'Content-Type' , 'text/html' ) - > withHeader ( 'Cache-Control' , [ 'public' , 'max-age=3600' ] ) ; } 

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

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

 rm -rf data/doctrine-cache/* 

Следует отметить, что заголовок Cache-Control активирует кэширование на стороне клиента. Наш браузер запоминает ответы, хранящиеся в его собственном кэше, даже после того, как они были удалены из кэша сервера.

Интеграция API НАСА

Хотя мы могли бы напрямую использовать API НАСА, у этого подхода есть несколько сложностей. Двумя основными из них являются то, что API NASA не предоставляет какого-либо метода для получения коллекций результатов или получения миниатюр. Наше решение заключается в использовании API-оболочки , созданной специально для этой статьи.

Запустите следующую команду в корне проекта:

 composer require andrewcarteruk/astronomy-picture-of-the-day ^0.1 

Сделайте следующие дополнения в файле config/autoload/dependencies.global.php :

 <?php return [ 'dependencies' = > [ 'factories' = > [  // ... AndrewCarterUK\ APOD \ APIInterface : : class = > App\ APIFactory : : class , ] , ] , 'application' = > [  // ... 'results_per_page' = > 24 , 'apod_api' = > [ 'store_path' = > 'public/apod' , 'base_url' = > '/apod' , ] , ] , ] ; 

Нам также необходимо создать файл локальных зависимостей в config/autoload/dependencies.local.php :

 <?php return [ 'application' = > [ 'apod_api' = > [ 'api_key' = > 'DEMO_KEY' ,  // DEMO_KEY might be good for a couple of requests  // Get your own here: https://api.nasa.gov/index.html#live_example ] , ] , ] ; 

И добавьте следующие маршруты в файл config/autoload/routes.global.php :

 <?php return [ 'dependencies' = > [  // ... 'factories' = > [  // ... App\ Action \ PictureListAction : : class = > App\ Action \ PictureListFactory : : class , ] , ] , 'routes' = > [  // ... [ 'name' = > 'picture-list' , 'path' = > '/picture-list[/{page:\d+}]' , 'middleware' = > App\ Action \ PictureListAction : : class , 'allowed_methods' = > [ 'GET' ] , ] , ] , ] ; 

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

Нам нужно будет указать путь к хранилищу, apod_api ключе apod_api и, если применимо, добавить путь к файлу .gitignore . Оболочка API будет хранить миниатюры в этом каталоге, поэтому она должна существовать в public каталоге, иначе она не сможет создавать общедоступные URL-адреса для миниатюр.

 mkdir public/apod 

Фабрика для API довольно проста. Создайте файл в src/APIFactory.php и поместите в него следующий код:

 <?php namespace App ; use AndrewCarterUK \ APOD \ API ; use GuzzleHttp \ Client ; use Interop \ Container \ ContainerInterface ; use Zend \ ServiceManager \ Exception \ ServiceNotCreatedException ; class APIFactory { public function __invoke ( ContainerInterface $container ) { $config = $container - > get ( 'config' ) ; if ( ! isset ( $config [ 'application' ] [ 'apod_api' ] ) ) { throw new ServiceNotCreatedException ( 'apod_api must be set in application configuration' ) ; } return new API ( new Client , $config [ 'application' ] [ 'apod_api' ] ) ; } } 

Оболочка API использует Guzzle для отправки HTTP-запросов к конечной точке API. Все, что нам нужно сделать, это внедрить экземпляр клиента и конфигурацию из нашей службы config и мы готовы к работе!

Действие, которое будет обрабатывать только что созданные маршруты, нужно будет внедрить с помощью службы API. Наша фабрика действий будет расположена в /src/Action/PictureListFactory.php и должна выглядеть следующим образом:

 <?php namespace App \ Action ; use AndrewCarterUK \ APOD \ APIInterface ; use Interop \ Container \ ContainerInterface ; use Zend \ ServiceManager \ Exception \ ServiceNotCreatedException ; class PictureListFactory { public function __invoke ( ContainerInterface $container ) { $apodApi = $container - > get ( APIInterface : : class ) ; $config = $container - > get ( 'config' ) ; if ( ! isset ( $config [ 'application' ] [ 'results_per_page' ] ) ) { throw new ServiceNotCreatedException ( 'results_per_page must be set in application configuration' ) ; } return new PictureListAction ( $apodApi , $config [ 'application' ] [ 'results_per_page' ] ) ; } } 

Теперь осталось только само действие. Создайте src/Action/PictureListAction.php и поместите в него следующий код:

 <?php namespace App \ Action ; use AndrewCarterUK \ APOD \ APIInterface ; use Psr \ Http \ Message \ ServerRequestInterface ; use Psr \ Http \ Message \ ResponseInterface ; use Zend \ Stratigility \ MiddlewareInterface ; class PictureListAction implements MiddlewareInterface { private $apodApi ; private $resultsPerPage ; public function __construct ( APIInterface $apodApi , $resultsPerPage ) { $this - > apodApi = $apodApi ; $this - > resultsPerPage = $resultsPerPage ; } public function __invoke ( ServerRequestInterface $request , ResponseInterface $response , callable $out = null ) { $page = intval ( $request - > getAttribute ( 'page' ) ) ? : 0 ; $pictures = $this - > apodApi - > getPage ( $page , $this - > resultsPerPage ) ; $response - > getBody ( ) - > write ( json_encode ( $pictures ) ) ; return $response  // ->withHeader('Cache-Control', ['public', 'max-age=3600']) - > withHeader ( 'Content-Type' , 'application/json' ) ; } } 

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

Теперь все, что нам нужно сделать, это создать утилиту для заполнения нашего магазина. Файл ниже может быть запущен в командной строке. Он получает контейнер из конфигурации, устанавливает обработчик сигнала, чтобы он мог updateStore и запускает метод updateStore из оболочки API. Создайте его в bin/update.php .

 <?php chdir ( __DIR__ . '/..' ) ; include 'vendor/autoload.php' ; $container = include 'config/container.php' ; // Create a SIGINT handler that sets a shutdown flag $shutdown = false ; declare ( ticks = 1 ) ; pcntl_signal ( SIGINT , function ( ) use ( & $shutdown ) { $shutdown = true ; } ) ; $newPictureHandler = function ( array $picture ) use ( & $shutdown ) { echo 'Added: ' . $picture [ 'title' ] . PHP_EOL ;  // If the shutdown flag has been set, die if ( $shutdown ) { die ; } } ; $errorHandler = function ( Exception $exception ) use ( & $shutdown ) { echo ( string ) $exception . PHP_EOL ;  // If the shutdown flag has been set, die if ( $shutdown ) { die ; } } ; $container - > get ( AndrewCarterUK\ APOD \ APIInterface : : class ) - > updateStore ( 20 , $newPictureHandler , $errorHandler ) ; 

Теперь мы можем запустить команду ниже, чтобы обновить наш магазин с фотографиями за последние 20 дней из API. Это может занять некоторое время, но, поскольку магазин обновлен, мы должны иметь возможность отслеживать маршрут /picture-list в наших браузерах, чтобы увидеть поток изображений JSON. Может быть, стоит отключить заголовки кэша в ответе при мониторинге канала, иначе он не будет обновляться!

Убедитесь, что вы получили свой собственный ключ API от НАСА , DEMO_KEY очень быстро достигнет предела запросов и начнет возвращать 429 кодов ответов.

 php bin/update.php 

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

И на этом наше путешествие заканчивается (или начинается!) С Zend Expressive для этого приложения. Осталось только изменить наши шаблоны, чтобы использовать AJAX для загрузки изображений с наших новых маршрутов. Репозиторий AstroSplash демонстрирует один из способов сделать это ( templates/app/index.phtml и templates/layout/default.phtml ) — но именно здесь мы добавим индивидуальный подход к нашему приложению, так что играйте!

Резюме

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

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

Разнообразие компонентов, которые Zend Expressive поддерживает «из коробки», очень сильно не нравится в структуре; любой компонент, который нас не устраивает, может быть легко изменен. В настоящее время платформа поддерживает три маршрутизатора ( FastRoute , Aura.Router , ZF2 Router ), три контейнера ( Zend ServiceManager , Pimple , Aura.DI ) и три шаблонизатора ( Plates , Twig , Zend View ).

Помимо всего этого, документация Zend Expressive содержит подробную документацию по платформе и всем компонентам поддержки. Он также содержит удобные руководства по быстрому запуску для немедленного начала работы.

Ты пробовал это? Вы следовали вместе? Что вам нравится / не нравится в Zend Expressive? Дайте нам знать в комментариях ниже, и не забудьте нажать кнопку «Нравится», если вы нашли этот урок полезным!