摘要:在本文中,我們將借助天文圖庫,使用建立圖片庫。在使用虛擬機時,此處應為,命令將在目錄下運行。我們建議在選擇服務名時,盡量使用完整的類名。這樣,相當于告訴它必須使用指定的類來創建服務。在返回中的最后一個響應之前,應用會緩存該響應以備下次使用。
在本文中,我們將借助 NASA 天文圖庫 API,使用 Zend Expressive 建立圖片庫。最后的結果將顯示在 AstroSplash 網站,該網站是為了文本特意搭建的。本文系 OneAPM 工程師編譯整理。
Zend Expressive 是用于創建 PSR-7 中間件程序的全新微框架。微框架相較于全??蚣艿暮锰幵谟诟?、更快、更加靈活,適用于設計應用時無需多余幫助、喜歡使用多帶帶組件靈活創建應用的開發老手。
中間件一詞將在本文中多次出現。其完善定義可在 Zend Expressive 文檔 中找到:
“中間件是位于請求與響應間的任意代碼。通常,中間件負責分析請求以收集輸入數據,將數據分配給其他層進行處理,之后創建并返回響應。”
從2013年開始,StackPHP 為 PHP 開發者提供了創建中間件的方法。然而,StackPHP 定義的中間件與本文將會提到的中間件有些不同。考慮到本文的意圖,兩者的兼容性只在理論層面有效。
如果你仍感到困惑,無需擔心。所有的概念都會輔之以詳盡的例子,讓我們馬上動手創建應用吧。
應用簡介我們即將創建的應用會用到 NASA 為其天文圖庫網站提供的 API,該網站提供了許多美輪美奐的天文圖片,雖然現在看來有些過時。只要花一些功夫,我們就能用這個 API 創造一個方便瀏覽的圖片庫。
在閱讀本文時,你也可以參考 GitHub 中的 AstroSplash 公共資源庫。該庫包含本應用的完整源碼,而應用的最終效果則在 astrosplash.com 呈現。
創建 Zend Expressive 項目為了快速搭建開發環境,建議(但非必須)使用 Homestead Improved Vagrant 虛擬機。
Zend Expressive 提供了一個非常實用的項目框架安裝程序,可用于配置框架及所選的組件。使用下面的 composer 命令,開始創建應用:
composer create-project -s rc zendframework/zend-expressive-skeleton
此處,需要將
安裝程序會讓我們選擇框架支持的不同組件。大部分情況下,我們會選擇默認設置,使用 FastRoute、Zend ServiceManager 與 Whoops 錯誤處理器。模板引擎沒有默認選項,我們將使用 Plates。
現在,如果我們在瀏覽器中加載該應用,就能看到歡迎我們使用 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 文檔中的服務命名策略。
“我們建議在選擇服務名時,盡量使用完整的類名。唯一的例外是:當某個服務實現了用于 typehints 的接口時,選用接口名。”
基于這一策略,打開 config/autoload/dependencies.global.php,用以下代碼替換其內容:
[ "factories" => [ ZendExpressiveApplication::class => ZendExpressiveContainerApplicationFactory::class, ], ], ];
此處,我們刪除了 invokables 鍵,因為在應用中無需定義此類服務。Invokable 服務無需構造函數參數即可實例化。
首先創建的服務是應用服務。如果你看一下前端控制器 (public/index.php),就會發現該控制器從容器中調用應用服務以運行應用。該服務包含依賴關系,我們必須在 factories 鍵下列出。這樣,相當于告訴 Zend ServiceManager 它必須使用指定的 factory 類來創建服務。Zend Expressive 還提供了許多 factories 用于創建核心服務。
接下來,打開 config/autoload/routes.global.php,用以下代碼替換其內容:
[ "invokables" => [ ZendExpressiveRouterRouterInterface::class => ZendExpressiveRouterFastRouteRouter::class, ], "factories" => [ AppActionIndexAction::class => AppActionIndexFactory::class, ] ], "routes" => [ [ "name" => "index", "path" => "/", "middleware" => AppActionIndexAction::class, "allowed_methods" => ["GET"], ], ], ];
dependencies 鍵下的第一個條目告訴框架,它會實例化 FastRoute adapter 類以創建 router 對象,無需傳入構造函數參數。factories 鍵下的條目用于索引操作服務。我們會在下一節為該服務及其 factory 填寫代碼。
routes 鍵會由 Zend Expressive 載入 router,且需包含一組 route 描述符。在我們定義的單一 route 描述符中,path 鍵與索引 route 的條目相符,middleware 鍵會告訴框架將哪個服務作為處理程序, allowed_methods 鍵則會指定允許的 HTTP 方法。將 allowed_methods 設置為 ZendExpressiveRouterRoute::HTTP_METHOD_ANY ,即為允許任意的 HTTP 方法。
Route 中間件下面將創建在 routes 配置文件中與索引 route 關聯的索引操作服務。操作類套用 Zend Expressive 中 route 中間件的形式,也即用于綁定至特定 routes 的中間件。
操作類將位于項目根目錄的 src/Action/IndexAction.php。其內容如下:
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"); } }
此處,我們使用依賴注入獲取模板渲染器接口的實現。之后,我們需要為處理該依賴注入創建 factory 類。
__invoke 魔術方法的出現使該類變成可調用的。調用時,以 PSR-7 消息為參數。由于所有的索引請求都由該中間件處理,我們無需調用鏈中其他的中間件,可以直接返回響應。此處用于標識可調用中間件的簽名非常常見:
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null);
用此模式創建的中間件,PSR-7 中間件調度器 Relay 也會支持。相應地,用于 Slim v3 框架——另一種 PSR-7 中間件框架的中間件也與 Zend Expressive 兼容。Slim 現在提供的中間件可用于 CSRF 保護與 HTTP 緩存。
當操作被調用時,它會渲染 app::index 模板,將其寫入響應中,并以 text/html 內容類型返回該響應。由于 PSR-7 消息是不可變的,每次給響應添加 header ,必須創建一個新的響應對象。原因在 PSR-7 規范 meta 文檔中有說明。
接下來要寫容器賴以實例化索引操作類的 factory 類。factory 類將位于項目根目錄的 src/Action/IndexFactory.php。其內容如下:
get(TemplateRendererInterface::class); return new IndexAction($templateRenderer); } }
再一次地,使用 __invoke 魔術方法將該類變成可調用的。容器會調用該類,傳入自身實例作為唯一參數。之后,可使用該容器獲得模板渲染器服務的實現,將之注入操作并返回。此處,可以仔細看看容器的配置,從而了解其中原理。
模板現在,唯一缺少的組件就是模板了。在之前的索引操作中,我們向模板渲染器索取 app::index 模板,但是該模板還未創建。Zend Expressive 使用 namespace::template 注釋指代模板。在容器配置中,Plates 了解到 app 命名空間中的所有模板都能在 templates/app 目錄下找到,且它該以 use .phtml 為模板文件擴展名。另外兩個配置過的命名空間為 error 與 layout。
首先,我們要創建 layout 模板。該模板的名字為 layout::default,根據配置,其路徑為 templates/layout/default.phtml。
=$this->e($title);?> =$this->section("content")?>
接下來,創建 templates/app/index.phtml 中的 app::index 模板。我們會使之擴展之前創建的 layout::default 模板。error 命名空間中的模板已經配置為擴展 layout::default 模板。
layout("layout::default", ["title" => "Astronomy Picture of the Day"]) ?>Astronomy Picture of the Day App
Welcome to my Astronomy Picture of the Day App. It will use an API provided by NASA to deliver awesome astronomy pictures.
在瀏覽器中加載應用,你就能看到剛才創建的模板了。
Pipe 中間件Zend Expressive 文檔中關于 pipe 中間件的說明如下:
“當你在應用中 pipe 中間件時,它會被添加到隊列中,當某個中間件返回響應實例時才會按順序從隊列中移除。如果沒有中間件返回響應實例,會由‘最終處理器’進行處理,后者會決定是否返回錯誤,若返回,則由其決定錯誤類型。”
pipe 中間件可用于創建應用防火墻、認證層、分析程序等等。實際上,Zend Expressive 將 pipe 中間件用于路由。在本應用中,我們會使用 pipe 中間件創建應用層緩存。
首先,需要獲取緩存庫。
composer require doctrine/cache ^1.5
其次,在 config/autoload/dependencies.global.php 文件添加以下代碼:
[ "factories" => [ // ... DoctrineCommonCacheCache::class => AppDoctrineCacheFactory::class, ], ], "application" => [ "cache_path" => "data/doctrine-cache/", ], ];
我們添加了一個 doctrine 緩存服務,該服務所需的自定義 factory 類會在之后創建。使用文件系統緩存是使應用上線運行的最快方法,我們需要為此服務創建一個目錄。
mkdir data/doctrine-cache
配置文件中的最后改動,是在路由開始之前將中間件服務報告給 Zend Expressive,并將其加入到中間件 pipe 中。打開 config/autoload/middleware-pipeline.global.php 文件,用以下代碼替換其內容:
[ "factories" => [ AppMiddlewareCacheMiddleware::class => AppMiddlewareCacheFactory::class, ] ], "middleware_pipeline" => [ "pre_routing" => [ [ "middleware" => AppMiddlewareCacheMiddleware::class ], ], "post_routing" => [ ], ], ];
用于 doctrine 緩存的 factory 會保存在 src/DoctrineCacheFactory.php 文件中。如果需要改變應用使用的緩存,我們只需改變該文件(及其配置),使用另一個 doctrine 緩存驅動程序即可。
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 的中間件 factory 會將緩存服務注入中間件:
get(Cache::class); return new CacheMiddleware($cache); } }
最后剩下中間件。創建 src/Middleware/CacheMiddleware.php,輸入以下代碼:
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; } } } }
中間件會首先嘗試從緩存處獲取響應。如果緩存中包含有效響應,則返回之,下一個中間件不會被調用。然而,如果緩存中沒有有效響應,生成響應的任務就會由 pipe 中的下一個中間件負責。
在返回 pipe 中的最后一個響應之前,應用會緩存該響應以備下次使用。因此,會簡單檢查該響應是否可以緩存。
如果回到索引操作類,我們可以給響應對象添加一個緩存控制 header,該 header 用來告訴剛剛創建的緩存中間件,將此響應緩存一個小時:
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 中之后的中間件返回的響應對象較為簡單時才有效。有一系列的 header 都能影響緩存處理響應的方式。此處,作為 pipe 中間件利用應用層級設計的演示代碼,已經夠用。
在創建應用的同時,我們可以禁用緩存控制 header 以防止緩存舊的響應。清除緩存的指令如下:
rm -rf data/doctrine-cache/*
請注意,Cache-Control header 會激活客戶端的緩存。瀏覽器會記下其緩存的響應,即便這些響應已經在服務端刪除。
集成 NASA API盡管可以直接使用 NASA API,這種方法還是有些復雜之處。最主要的兩個問題是 NASA API 并未提供任何獲取結果集和縮略圖的方法。我們的解決方案是使用一個本文專屬的 wrapper API。
在項目根目錄運行以下指令:
composer require andrewcarteruk/astronomy-picture-of-the-day ^0.1
在 config/autoload/dependencies.global.php 文件添加以下代碼:
[ "factories" => [ // ... AndrewCarterUKAPODAPIInterface::class => AppAPIFactory::class, ], ], "application" => [ // ... "results_per_page" => 24, "apod_api" => [ "store_path" => "public/apod", "base_url" => "/apod", ], ], ];
我們還需在 config/autoload/dependencies.local.php 創建本地依賴文件:
[ "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 文件添加路由信息:
[ // ... "factories" => [ // ... AppActionPictureListAction::class => AppActionPictureListFactory::class, ], ], "routes" => [ // ... [ "name" => "picture-list", "path" => "/picture-list[/{page:d+}]", "middleware" => AppActionPictureListAction::class, "allowed_methods" => ["GET"], ], ], ];
所以,以上配置修改會產生什么效果呢?我們添加的路由可以從 NASA API 獲取近期的圖片列表。該路由會接收任意的整數型分頁屬性,我們可將之作為頁碼。我們還為 API wrapper 及此路由附屬的操作創建了服務。
我們需要創建在 apod_api 鍵中指定的存儲路徑,如果可行,將此路徑添加至 .gitignore 文件。API wrapper 將在該路徑下存儲縮略圖,因此它必須保存在公共目錄下。否則就無法為縮略圖創建公共 URL。
mkdir public/apod
此 API 的 factory 比較簡單。創建 src/APIFactory.php 文件,填入以下代碼:
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 wrapper 使用 Guzzle 向 API 終端提交 HTTP 請求。我們只需注入客戶端實例以及 config 服務中的配置即可。
處理路由的操作需要與 API 服務一起注入。操作 factory 位于 /src/Action/PictureListFactory.php 文件,內容如下:
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 文件,填入如下代碼:
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 格式將之導出。示例展示了如何為緩存中間件的響應添加緩存控制 header。然而,在開發時還是將這部分注釋掉比較穩妥。
現在,我們只需創建一個容納內容的工具。下面的文檔可以在命令行運行。它包含了配置中的容器,會安裝一個信號處理器,因此可以快速關閉程序,運行 API wrapper 中的 updateStore 方法。 創建 bin/update.php 文件:
get(AndrewCarterUKAPODAPIInterface::class)->updateStore(20, $newPictureHandler, $errorHandler);
現在,我們可以運行該命令以更新內容,從 API 處獲取最近20天的圖片。這會需要一點時間,但更新完成后,我們可以在瀏覽器中監控 /picture-list 路由,并看到一組 JSON 圖片數據。在監控圖片流時,最好禁用響應中的緩存 header,否則可能無法更新。
確保從 NASA 獲取專屬的 API 鍵,DEMO_KEY 很快就會達到請求上線,并返回 429 響應碼。
php bin/update.php
若想要應用自動更新,需要將命令設置為每日運行。此外,還需將 updateStore 方法的第一個參數設置為1,使其只下載當天的圖片。
至此,本應用的 Zend Expressive 部分就介紹完畢了。然后只需修改模板,用 AJAX 從新的路由加載圖片即可。AstroSplash 資源庫 展示了一種實現方法(templates/app/index.phtml 與 templates/layout/default.phtml)。不過,這更應該我們發揮各人特色的地方。
最后需要做的就是不斷的對網站的性能進行優化了,如果是在本地通過壓測工具進行優化,那么使用 JMeter+XHProf 就可以了,不過這個方法不能完全的重現真實環境的性能狀況,因此針對這種方式的結果進行優化,不一定是最優結果,這時候使用 OneAPM PHP 探針 就能解決這個問題。
使用 OneAPM 提供的 PHP 探針只需要直接在生產環境安裝好探針,進行一些簡單的配置,就能自動完成性能數據的收集和分析工作了,性能瓶頸準確度直達代碼行,而且因為分析結果是基于真實數據,對于性能優化來說更具有參考價值,所以只需要經常按照慢事務堆棧圖對標紅的方法進行持續優化就可以很好的優化應用性能了。
總結使用 Zend Expressive 這類以中間件為基礎的框架使我們在設計應用時以層級為基礎。依照最簡單的形式,我們可以使用 route 中間件模擬在其他框架中可能熟悉的控制器操作。然而,中間件的好處在于它能在應用的任何階段攔截并修改請求與響應。
Zend Expressive 是一種很好用的框架,因為它容易移植。之前所寫的全部代碼都可以輕易地移植到不同的框架使用,甚至用在沒有框架的應用中,再配合 PHP 探針就能輕松搭建高性能的PHP應用程序了。
Zend Expressive 還支持許多意想不到的組件,使其很難不讓人喜愛。目前,該框架支持三種路由(FastRoute, Aura.Router, ZF2 Router),三種容器(Zend ServiceManager, Pimple, Aura.DI)以及三種模板引擎(Plates, Twig, Zend View)。
此外,Zend Expressive 文檔提供了有關該框架與其支持組件的深入文檔,還包含了快速上手的簡便指導教程。
原文地址:http://www.sitepoint.com/build-nasa-photo-gallery-zend-expressive/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31944.html
摘要:結語考慮到在國內的流行度并不高,可能幫不了國內的多少開發者,本文只為做一個引導,有興趣的可以直接查看官方文檔獲得更多信息,同時還自行實現了更好支持度的靜態資源訪問支持,有興趣的也可以了解一下。 前言 Zend Framework 是 PHP 的官方框架,隨著 Zend-Expressive-Swoole 0.2.2 的發布,率先支持了 Swoole 4 的協程功能,現在可以僅通過一個配...
摘要:三句話說完的話,完善文檔和測試優化接口使之能無縫升級到第一個長期支持的,以及可能的話建立基礎的社區。實際項目例子代碼在目錄目標版本暫時是你能幫上我的試用。 介紹站點還沒做,先直接甩代碼鏈接了 https://github.com/litphp/litphp Lit是什么? Lit是我一直在擼的個人框架,按第一次上傳代碼來說歷史 超過4年 了,從還能支持PHP5.2的第一版開始一直(龜速...
摘要:上次的訪談,介紹了下可愛的依云醬,回憶傳送門。這里簡單地介紹下龍女仆,全名小林家的龍女仆,為什么介紹這部劇呢因為設計獅顏值同學也安利了這部。劇情簡介在獨身又勞累的小林劃重點一名程序員身邊突然出現的穿著女仆服裝的美少女托爾。 showImg(https://segmentfault.com/img/bVR6p5?w=900&h=385); 上次的訪談,介紹了下可愛的依云醬,回憶傳送門。不...
摘要:上次的訪談,介紹了下可愛的依云醬,回憶傳送門。這里簡單地介紹下龍女仆,全名小林家的龍女仆,為什么介紹這部劇呢因為設計獅顏值同學也安利了這部。劇情簡介在獨身又勞累的小林劃重點一名程序員身邊突然出現的穿著女仆服裝的美少女托爾。 showImg(https://segmentfault.com/img/bVR6p5?w=900&h=385); 上次的訪談,介紹了下可愛的依云醬,回憶傳送門。不...
摘要:在中有相當多的解決方案,其中有語言內置功能,也有開源社區貢獻的開發庫。缺點是與其他解決方案相比,用起來不是那么友好。默認情況下,可以解析響應,非常方便。與類似,是另一個流行的庫,主要用于瀏覽器中的請求,但也適用于。 翻譯:瘋狂的技術宅英文標題:5 Ways to Make HTTP Requests in Node.js原文鏈接:https://www.twilio.com/blog/...
閱讀 3020·2021-11-24 10:32
閱讀 678·2021-11-24 10:19
閱讀 5068·2021-08-11 11:17
閱讀 1455·2019-08-26 13:31
閱讀 1259·2019-08-23 15:15
閱讀 2286·2019-08-23 14:46
閱讀 2264·2019-08-23 14:07
閱讀 1073·2019-08-23 14:03