Laravel 框架中經常使用的 PHP 語法

原文:wuYin/blog,轉載註明來源便可。php

前言

Laravel 框架由於其組件化的設計並恰當使用設計模式,使得框架自己簡潔易擴展。區別於 ThinkPHP 那種整合式功能的框架(功能要麼全用要麼全不用),Laravel 使用 composer 工具進行 package 的管理,想加功能直接添加組件便可。好比你寫爬蟲使用頁面採集組件: composer require jaeger/querylisthtml

本文簡要介紹 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

image-20180609193721860

解決辦法

從 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

PSR 規範

其實 namespace 與文件名無關,但按 PSR 標準要求:命名空間與文件路徑一致 & 文件名與類名一致。好比 Laravel 默認生成的 laravel-demo/app/Http/Controllers/Auth/LoginController.php,其命名空間爲 App\Http\Controllers\Auth & 類名爲 LoginControllerapp

遵循規範,上邊的 mine.phpgoogle.php 都應叫 User.phpcomposer

namespace 操做符與__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 functionuse 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

文件包含

手動加載

使用 includerequire 引入指定的文件,(字面理解)需注意 require 出錯會報編譯錯誤中斷腳本運行,而 include 出錯只會報 warning 腳本繼續運行。

include 文件時,會先去 php.ini 中配置項 include_path 指定的目錄找,找不到纔在當前目錄下找:

image-20180609203210194

<?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:: 會實例化調用它的類。

trait

基本使用

參考 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 可用 , 隔開,即多重繼承。

多個 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 正是恰如其分的利用了這些新特性,才實現了組件化開發、服務加載等優雅的特性。

相關文章
相關標籤/搜索