原文:wuYin/blog,轉載註明來源便可。php
Laravel 框架由於其組件化的設計並恰當使用設計模式,使得框架自己簡潔易擴展。區別於 ThinkPHP 那種整合式功能的框架(功能要麼全用要麼全不用),Laravel 使用 composer 工具進行 package 的管理,想加功能直接添加組件便可。好比你寫爬蟲使用頁面採集組件: composer require jaeger/querylist
html
本文簡要介紹 Laravel 中頻繁用到的 PHP 特性與新語法,具體可參考。linux
Laravel 進行組件化開發,得益於遵循 PSR-4 規範的 composer 工具,其利用命名空間和自動加載來組織項目文件。更多參考:composer 自動加載機制laravel
在團隊協做、引入第三方依賴代碼時,每每可能會出現類、函數和接口重名的狀況。好比:git
<?php # google.php class User { private $name; }
<?php # mine.php // 引入第三方依賴 include 'google.php'; class User { private $name; } $user = new User(); // 命名衝突
由於同時定義了類 User
致使命名衝突:github
從 PHP 5.3 開始引入,參考 PHP 手冊 能知道命名空間有 2 個做用:避免命名衝突、保持命名簡短。好比使用命名空間後:shell
<?php # google.php namespace Google; // 模擬第三方依賴 class User { private $name = 'google'; public function getName() { echo $this->name . PHP_EOL; } }
<?php # mine.php namespace Mine; // 導入並命名別名 use Google as G; // 導入文件使得 google.php 命名空間變爲 mine.php 的子命名空間 include 'google.php'; /* 避免了命名衝突 */ class User { private $name = 'mine'; public function getName() { echo $this->name . PHP_EOL; } } /* 保持了命名簡短 */ // 若是沒有命名空間,爲了類名也不衝突,可能會出現這種函數名 // $user = new Google_User(); // Zend 風格並不提倡 $user = new G\User(); // 爲了函數名也不衝突,可能會出現這種函數名 // $user->google_get_name() $user->getName(); $user = new User(); $user->getName();
運行:設計模式
$ php demo.php google mine
其實 namespace 與文件名無關,但按 PSR 標準要求:命名空間與文件路徑一致 & 文件名與類名一致。好比 Laravel 默認生成的 laravel-demo/app/Http/Controllers/Auth/LoginController.php
,其命名空間爲 App\Http\Controllers\Auth
& 類名爲 LoginController
app
遵循規範,上邊的 mine.php
和 google.php
都應叫 User.php
composer
__NAMESPACE__
魔術常量... // $user = new User(); $user = new namespace\User(); // 值爲當前命名空間 $user->getName(); echo __NAMESPACE__ . PHP_EOL; // 直接獲取當前命名空間字符串 // 輸出 Mine
<?php namespace CurrentNameSpace; // 不包含前綴 $user = new User(); # CurrentNameSpace\User(); // 指定前綴 $user = new Google\User(); # CurrentNameSpace\Google\User(); // 根前綴 $user = new \Google\User(); # \Google\User();
若是引用的類、函數沒有指定命名空間,則會默認在當在 __NAMESPACE__
下尋找。若要引用全局類:
<?php namespace Demo; // 均不會被使用到 function strlen() {} const INI_ALL = 3; class Exception {} $a = \strlen('hi'); // 調用全局函數 strlen $b = \CREDITS_GROUP; // 訪問全局常量 CREDITS_GROUP $c = new \Exception('error'); // 實例化全局類 Exception
// use 可一次導入多個命名空間 use Google, Microsoft; // 良好實踐:每行一個 use use Google; use Microsoft;
<?php // 一個文件可定義多個命名空間 namespace Google { class User {} } namespace Microsoft { class User {} } // 良好實踐:「一個文件一個類」
從 PHP 5.6 開始,可以使用 use function
和 use const
分別導入函數和常量使用:
# google.php const CEO = 'Sundar Pichai'; function getMarketValue() { echo '770 billion dollars' . PHP_EOL; }
# mine.php use function Google\getMarketValue as thirdMarketValue; use const Google\CEO as third_CEO; thirdMarketValue(); echo third_CEO;
運行:
$ php mine.php google 770 billion dollars Sundar Pichaimine Mine
使用 include
或 require
引入指定的文件,(字面理解)需注意 require 出錯會報編譯錯誤中斷腳本運行,而 include 出錯只會報 warning 腳本繼續運行。
include 文件時,會先去 php.ini 中配置項 include_path
指定的目錄找,找不到纔在當前目錄下找:
<?php // 引入的是 /usr/share/php/System.php include 'System.php';
void __autoload(string $class )
能進行類的自動加載,但通常都使用 spl_autoload_register 手動進行註冊:
<?php // 自動加載子目錄 classes 下 *.class.php 的類定義 function __autoload($class) { include 'classes/' . $class . '.class.php'; } // PHP 5.3 後直接使用匿名函數註冊 $throw = true; // 註冊出錯時是否拋出異常 $prepend = false; // 是否將當前註冊函數添加到隊列頭 spl_autoload_register(function ($class) { include 'classes/' . $class . '.class.php'; }, $throw, $prepend);
在 composer 生成的自動加載文件 laravel-demo/vendor/composer/autoload_real.php
中可看到:
class ComposerAutoloaderInit8b41a { private static $loader; public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { // 加載當前目錄下文件 require __DIR__ . '/ClassLoader.php'; } } public static function getLoader() { if (null !== self::$loader) { return self::$loader; } // 註冊本身的加載器 spl_autoload_register(array('ComposerAutoloaderInit8b41a6', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit8b41a6a', 'loadClassLoader')); ... } ... }
這裏只提一下,具體 Laravel 總體是怎麼作自動加載的,後邊的文章會細說。
參考 PHP 手冊,可簡單的理解爲在運行時獲取對象的完整信息。反射有 5 個類:
ReflectionClass // 解析類名 ReflectionProperty // 獲取和設置類屬性的信息(屬性名和值、註釋、訪問權限) ReflectionMethod // 獲取和設置類函數的信息(函數名、註釋、訪問權限)、執行函數等 ReflectionParameter // 獲取函數的參數信息 ReflectionFunction // 獲取函數信息
好比 ReflectionClass
的使用:
<?php class User { public $name; public $age; public function __construct($name = 'Laruence', $age = 35) { $this->name = $name; $this->age = $age; } public function intro() { echo '[name]: ' . $this->name . PHP_EOL; echo '[age]: ' . $this->age . PHP_EOL; } } reflect('User'); // ReflectionClass 反射類使用示例 function reflect($class) { try { $ref = new ReflectionClass($class); // 檢查是否可實例化 // interface、abstract class、 __construct() 爲 private 的類均不可實例化 if (!$ref->isInstantiable()) { echo "[can't instantiable]: ${class}\n"; } // 輸出屬性列表 // 還能獲取方法列表、靜態常量等信息,具體參考手冊 foreach ($ref->getProperties() as $attr) { echo $attr->getName() . PHP_EOL; } // 直接調用類中的方法,我的認爲這是反射最好用的地方 $obj = $ref->newInstanceArgs(); $obj->intro(); } catch (ReflectionException $e) { // try catch 機制真的不優雅 // 相比之下 Golang 的錯誤處理雖然繁瑣,但很簡潔 echo '[reflection exception: ]' . $e->getMessage(); } }
運行:
$ php reflect.php name age [name]: Laruence [age]: 35
其他 4 個反射類參考手冊 demo 便可。
參考 PHP 手冊,先看一個例子:
<?php class Base { // 後期綁定不侷限於 static 方法 public static function call() { echo '[called]: ' . __CLASS__ . PHP_EOL; } public static function test() { self::call(); // self 取值爲 Base 直接調用本類中的函數 static::call(); // static 取值爲 Child 調用者 } } class Child extends Base { public static function call() { echo '[called]: ' . __CLASS__ . PHP_EOL; } } Child::test();
輸出:
$ php late_static_bind.php [called]: Base [called]: Child
在對象實例化時,self::
會實例化根據定義所在的類,static::
會實例化調用它的類。
參考 PHP 手冊,PHP 雖然是單繼承的,但從 5.4 後可經過 trait 水平組合「類」,來實現「類」的多重繼承,其實就是把重複的函數拆分紅 triat 放到不一樣的文件中,經過 use 關鍵字按需引入、組合。可類比 Golang 的 struct 填鴨式組合來實現繼承。好比:
<?php class DemoLogger { public function log($message, $level) { echo "[message]: $message", PHP_EOL; echo "[level]: $level", PHP_EOL; } } trait Loggable { protected $logger; public function setLogger($logger) { $this->logger = $logger; } public function log($message, $level) { $this->logger->log($message, $level); } } class Foo { // 直接引入 Loggable 的代碼片斷 use Loggable; } $foo = new Foo; $foo->setLogger(new DemoLogger); $foo->log('trait works', 1);
運行:
$ php trait.php [message]: trait works [level]: 1
更多參考:我所理解的 PHP Trait
當前類的函數會覆蓋 trait 的同名函數,trait 會覆蓋父類的同名函數( use trait
至關於當前類直接覆寫了父類的同名函數)
同時引入多個 trait 可用 ,
隔開,即多重繼承。
多個 trait 有同名函數時,引入將發生命名衝突,使用 insteadof
來指明使用哪一個 trait 的函數。
使用 as
關鍵字能夠重命名的 trait 中引入的函數,還能夠修改其訪問權限。
trait 相似於類,能夠定義屬性、方法、抽象方法、靜態方法和靜態屬性。
下邊的蘋果、微軟和 Linux 的小栗子來講明:
<?php trait Apple { public function getCEO() { echo '[Apple CEO]: Tim Cook', PHP_EOL; } public function getMarketValue() { echo '[Apple Market Value]: 953 billion', PHP_EOL; } } trait MicroSoft { public function getCEO() { echo '[MicroSoft CEO]: Satya Nadella', PHP_EOL; } public function getMarketValue() { echo '[MicroSoft Market Value]: 780 billion', PHP_EOL; } abstract public function MadeGreatOS(); static public function staticFunc() { echo '[MicroSoft Static Function]', PHP_EOL; } public function staticValue() { static $v; $v++; echo '[MicroSoft Static Value]: ' . $v, PHP_EOL; } } // Apple 最終登頂,成爲第一家市值超萬億美圓的企業 trait Top { // 處理引入的 trait 之間的衝突 use Apple, MicroSoft { Apple::getCEO insteadof MicroSoft; Apple::getMarketValue insteadof MicroSoft; } } class Linux { use Top { // as 關鍵字能夠重命名函數、修改權限控制 getCEO as private noCEO; } // 引入後必須實現抽象方法 public function MadeGreatOS() { echo '[Linux Already Made]', PHP_EOL; } public function getMarketValue() { echo '[Linux Market Value]: Infinity', PHP_EOL; } } $linux = new Linux(); // 和 extends 繼承同樣 // 當前類中的同名函數也會覆蓋 trait 中的函數 $linux->getMarketValue(); // trait 中能夠定義靜態方法 $linux::staticFunc(); // 在 trait Top 中已解決過沖突,輸出庫克 $linux->getCEO(); // $linux->noCEO(); // Uncaught Error: Call to private method Linux::noCEO() // trait 中能夠定義靜態變量 $linux->staticValue(); $linux->staticValue();
運行:
$ php trait.php [Linux Market Value]: Infinity [MicroSoft Static Function] [Apple CEO]: Tim Cook [MicroSoft Static Value]: 1 [MicroSoft Static Value]: 2
本節簡要說起了命名空間、文件自動加載、反射機制與 trait 等,Laravel 正是恰如其分的利用了這些新特性,才實現了組件化開發、服務加載等優雅的特性。