首頁>技術>

前段時間,公司的專案從PHP5.3升級到PHP7,現在專案裡開始使用PHP7的一些新語法和特性。反觀PHP的5.4、5.5、5.6版本,有點認知缺失的感覺。所以,決定看《Modern PHP》補一補裡面的一些概念。

一、名稱空間

名稱空間用的比較多,不詳細寫了,記錄幾個值得注意的實踐和細節。

多重匯入

別這麼做,這樣寫容易讓人困惑。

<?phpuse Symfony\\Component\\HttpFoundation\\Request, Symfony\\Component\\HttpFoundation\\Response, Symfony\\Component\\HttpFoundation\\Cookie;

建議一行寫一個use語句:

<?phpuse Symfony\\Component\\HttpFoundation\\Request;use Symfony\\Component\\HttpFoundation\\Response;use Symfony\\Component\\HttpFoundation\\Cookie;

一個檔案中使用多個名稱空間

你可以這麼做,但這違背了“一個檔案定義一個類”的良好實踐。

<?phpnamespace Foo { //code}namespace Bar { //code }

全域性名稱空間

想要使用PHP原生的Exception類,需要在類名前加 \\ 符號。

<?phpnamespace My\\App;class Foo{ public function doSomething() { $exception = new \\Exception(); }}

如果Exception前不加 \\ 符號,會在My\\App名稱空間下尋找Exception類。

二、使用介面

使用介面編寫的程式碼更靈活,能委託其他人實現細節。使用的人只需要關心有什麼介面,而不需要關心實現。能夠很好地解耦程式碼,方便擴充套件,比較常用就不說啦。

三、性狀

在學習laravel框架之前都沒弄清楚性狀(trait)。這是PHP5.4.0引入的新概念,既像類又像介面。但它兩個都不是。

性狀是類的部分實現,可以混入一個或多個現有PHP類中。類似Ruby的組合模組活混入(mixin)。

為什麼使用性狀

舉個具體的例子,比如有兩個類,Car 和 Phone,他們都需要GPS功能。為了解決這個問題,第一反應建立一個父類,然後讓Car和Phone繼承它。但因為很明顯,這個祖先不屬於各自的繼承層次結構。

第二反應建立一個GPS的介面,定義好GPS的功能介面,然後讓Car和Phone兩個類都實現這個介面。這樣做能實現功能,同時也能保持自然的繼承層級結構。不過,這就使得在兩個都要實現重複的GPS功能,這不符合DRY(dont repeat yourself)原則。

第三反應建立實現GPS功能的性狀(trait),然後在Car和Phone類中混入這個性狀。能實現功能,不影響繼承結構,不重複實現,完美。

建立與使用性狀

建立trait

<?phptrait MyTrait{ //實現}

使用trait

<?phpclass MyClass{ use MyTrait; // 類的實現}
四、生成器

PHP生成器(generator)是PHP5.5.0引入的新功能,很多PHP開發者生成器不了解。生成器是個簡單的迭代器,但生成器不要求實現Iterator介面。生成器會根據需要計算併產生要迭代的值。如果不查詢,生成器永遠不知道下一個要迭代的值是什麼,在生成器中無法後退或快進。具體看如下兩個例子:

簡單的生成器

<?phpfunction makeRange($length) { for ($i = 0; $i < $length; $i++) { yield $i; }}foreach (makeRange(1000000) as $i) { echo $i, PHP_EOL;}

具體場景:使用生成器處理CSV檔案

<?phpfunction getRows($file) { $handle = fopen($file, 'rb'); if ($handle === false) { throw new Exception(); } while (feof($handle) === false) { yield fgetcsv($handle); }}foreach (getRows('data.csv') as $row) { print_r($row);}

處理這種場景,習慣的處理方法是先讀取檔案的所有內容放到陣列中,然後再做處理等等。這種的處理存在的問題是:當檔案特別大,一次讀取就佔用很多記憶體資源。而生成器最適合這種場景,因為這樣佔用的系統記憶體量極少。

五、閉包

理論上,閉包和匿名函式是不同的概念。不過,PHP將其視作相同的概念。

簡單閉包

<?php$closure = function ($name) { return sprintf('Hello %s', $name);}echo $closure("Beck");// 輸出 --> “Hello Beck”

注意:我們之所以能呼叫$closure變數,是因為這個變數的值是個閉包,而且閉包物件實現了__invoke()魔術方法。只要變數名後有(),PHP就會查詢並呼叫__invoke()方法。

附加狀態

使用use關鍵字可以把多個引數傳入閉包,此時要像PHP函式或方法的引數一樣,使用逗號分隔多個引數。

<?phpfunction enclosePerson($name) { return function ($doCommand) use ($name) { return sprintf('%s, %s', $name, $doCommand); };}// 把字串“Clay”封裝在閉包中$clay = enclosePerson('Clay');// 傳入引數,呼叫閉包echo $clay('get me sweet tea!');// 輸出 --> "Clay, get me sweet tea!"

使用bindTo()方法附加閉包的狀態

PHP框架經常使用bindTo()方法把路由URL對映到匿名回撥函式上,框架會把匿名函式繫結到應用物件上,這麼做可以在這個匿名函式中使用$this關鍵字引用重要的應用物件。例子如下:

<?phpclass App{ protected $routes = array(); protected $responseStatus = '200 OK'; protected $responseContentType = 'text/html'; protected $responseBody = 'Hello world'; public function addRoute($routePath, $routeCallback) { $this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__);//重點 } public function dispatch($currentPath) { foreach ($this->routes as $routePath => $callback) { if ($routePath === $currentPath) { $callback(); } } header('HTTP/1.1' . $this->responseStatus); header('Content-type:' . $this->responseContentType); header('Content-length' . mb_strlen($this->responseBody)); echo $this->responseBody; }}

第11行是重點所在,把路由回撥繫結到了當前的App例項上。這麼做能在回撥函式中處理App例項的狀態:

<?php$app = new App();$app->addRoute('/users/josh', function () { $this->responseContentType = 'application/json;charset=utf8'; $this->responseBody = '{"name": "Josh"}';});$app->dispatch('/users/josh');
六、Zend OPcache

位元組碼快取不是PHP的新特性,很多獨立的擴充套件可以實現快取。從PHP5.5.0開始,PHP內建了位元組碼快取功能,名為Zend OPcache。

位元組碼快取是什麼

PHP是解釋性語言,PHP直譯器執行PHP指令碼時會解析PHP指令碼程式碼,把PHP程式碼編譯成一系列Zend操作碼,然後執行位元組碼。每次請求PHP檔案都是這樣,會消耗很多資源。位元組碼快取能儲存預先編譯好的PHP位元組碼。這意味著,請求PHP指令碼時,PHP直譯器不用每次都讀取、解析和編譯PHP程式碼。這樣能極大地提升應用的效能。

七、內建的HTTP伺服器

從PHP5.4.0起,PHP內建了Web伺服器,這對眾多使用Apache或nginx的php開發者來說,可能是個隱藏功能。不過,這個內建的伺服器功能並不完善,不應該在生產環境中使用,但對本地開發來說是個便利的工具,可以用於快速預覽一些框架和應用。

啟動伺服器

php -S localhost:4000

配置伺服器

php -S localhost:8000 -c app/config/php.ini

路由器指令碼

與Apache和nginx不同,它不支援.htaccess檔案。因此,這個伺服器很難使用多數流行的PHP框架中常見的前端控制器。PHP內建的伺服器使用路由器指令碼彌補了這個遺漏的功能。處理每個HTTP請求前,會先經過這個路由器指令碼,如果結果為false,返回當前HTTP請求中引用的靜態資源URI。

php -S localhost:8000 route.php

是否為內建的伺服器

<?phpif (php_sapi_name() === 'cli-server') { // php 內建的web伺服器}

  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • React-Native與小程式的底層框架比較