前言
Laravel框架因為其元件化的設計並適當地使用設計模式,使得框架本身簡潔易擴展。區別於 ThinkPHP 那種整合式功能的框架(功能要么全用要么全不用),Laravel 使用 composer 工具進行 package 的管理,想加功能直接添加組件即可。例如你寫爬蟲使用頁面採集元件: composer require jaeger/querylist
#本文簡單介紹 Laravel 中頻繁用到的 PHP 特性與新語法,具體可參考。
元件化開發
Laravel 進行元件化開發,得益於遵循 PSR-4 規範的 composer 工具,其利用命名空間和自動載入來組織專案文件。更多參考:composer 自動載入機制
命名空間
命名衝突
#在團隊協作、引入第三方依賴程式碼時,往往可能會出現類別、函數和接口重名的情況。例如:
<?php # google.php class User { private $name; }
<?php # mine.php // 引入第三方依赖 include 'google.php'; class User { private $name; } $user = new User(); // 命名冲突
因為同時定義了類別User
導致命名衝突:
命名衝突、保持命名簡短。例如使用命名空間後:
<?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 minePSR 規格其實namespace 與檔案名稱無關,但按PSR 標準需求:命名空間與檔案路徑一致&檔案名稱與類別名稱一致。例如Laravel 預設產生的
laravel-demo/app/Http/Controllers/Auth/LoginController.php,其命名空間為
App\Http\Controllers\Auth & 類別名為
LoginController
mine.php 和
google.php 都應叫
User.php
__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 中設定項目
指定的目錄找,找不到才在目前目錄下找:
<?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 有同名函数时,引入将发生命名冲突,使用 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 正是恰如其分的利用了这些新特性,才实现了组件化开发、服务加载等优雅的特性。