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