Laravel 框架大量运用了 PHP 的特性与新语法,本文章简述一下。
前言
Laravel 框架因为其组件化的设计并恰当使用设计模式,使得框架本身简洁易扩展。区别于 ThinkPHP 那种整合式功能的框架(功能要么全用要么全不用),Laravel 使用 composer 工具进行 package 的管理,想加功能直接添加组件即可。比如你写爬虫使用页面采集组件: composer require jaeger/querylist
本文简要介绍 Laravel 中频繁用到的 PHP 特性与新语法,具体可参考。
组件化开发
Laravel 进行组件化开发,得益于遵循 PSR-4 规范的 composer 工具,其利用命名空间和自动加载来组织项目文件。更多参考:composer 自动加载机制
命名空间
命名冲突
在团队协作、引入第三方依赖代码时,往往可能会出现类、函数和接口重名的情况。比如:
1 2 3 4 5 6
| <?php
class User { private $name; }
|
1 2 3 4 5 6 7 8 9 10 11
| <?php
include 'google.php';
class User { private $name; }
$user = new User();
|
因为同时定义了类 User
导致命名冲突:
解决办法
从 PHP 5.3 开始引入,参考 PHP 手册 能知道命名空间有 2 个作用:避免命名冲突、保持命名简短。比如使用命名空间后:
1 2 3 4 5 6 7 8 9 10 11 12
| <?php
namespace Google;
class User { private $name = 'google';
public function getName() { echo $this->name . PHP_EOL; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?php
namespace Mine;
use Google as G;
include 'google.php';
class User { private $name = 'mine';
public function getName() { echo $this->name . PHP_EOL; } }
$user = new G\User();
$user->getName();
$user = new User(); $user->getName();
|
运行:
1 2 3
| $ php demo.php google mine
|
PSR 规范
其实 namespace 与文件名无关,但按 PSR 标准要求:命名空间与文件路径一致 & 文件名与类名一致。比如 Laravel 默认生成的 laravel-demo/app/Http/Controllers/Auth/LoginController.php
,其命名空间为 App\Http\Controllers\Auth
& 类名为 LoginController
遵循规范,上边的 mine.php
和 google.php
都应叫 User.php
namespace 操作符与__NAMESPACE__
魔术常量
1 2 3 4 5 6
| ...
$user = new namespace\User(); $user->getName();
echo __NAMESPACE__ . PHP_EOL;
|
三种命名空间的导入
1 2 3 4 5 6 7 8 9 10 11
| <?php namespace CurrentNameSpace;
$user = new User();
$user = new Google\User();
$user = new \Google\User();
|
全局命名空间
如果引用的类、函数没有指定命名空间,则会默认在当在 __NAMESPACE__
下寻找。若要引用全局类:
1 2 3 4 5 6 7 8 9 10 11
| <?php namespace Demo;
function strlen() {} const INI_ALL = 3; class Exception {}
$a = \strlen('hi'); $b = \CREDITS_GROUP; $c = new \Exception('error');
|
多重导入与多个命名空间
1 2 3 4 5 6 7
| use Google, Microsoft;
use Google; use Microsoft;
|
1 2 3 4 5 6 7 8 9 10 11
| <?php
namespace Google { class User {} } namespace Microsoft { class User {} }
// 良好实践:“一个文件一个类”
|
导入常量、函数
从 PHP 5.6 开始,可使用 use function
和 use const
分别导入函数和常量使用:
1 2 3 4 5
| const CEO = 'Sundar Pichai'; function getMarketValue() { echo '770 billion dollars' . PHP_EOL; }
|
1 2 3 4 5 6
| use function Google\getMarketValue as thirdMarketValue; use const Google\CEO as third_CEO;
thirdMarketValue(); echo third_CEO;
|
运行:
1 2 3 4 5
| $ php mine.php google 770 billion dollars Sundar Pichaimine Mine
|
文件包含
手动加载
使用 include
或 require
引入指定的文件,(字面理解)需注意 require 出错会报编译错误中断脚本运行,而 include 出错只会报 warning 脚本继续运行。
include 文件时,会先去 php.ini 中配置项 include_path
指定的目录找,找不到才在当前目录下找:
1 2 3 4
| <?php
include 'System.php';
|
自动加载
void __autoload(string $class )
能进行类的自动加载,但一般都使用 spl_autoload_register 手动进行注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
function __autoload($class) { include 'classes/' . $class . '.class.php'; }
$throw = true; $prepend = false;
spl_autoload_register(function ($class) { include 'classes/' . $class . '.class.php'; }, $throw, $prepend);
|
在 composer 生成的自动加载文件 laravel-demo/vendor/composer/autoload_real.php
中可看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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 个类:
1 2 3 4 5
| ReflectionClass ReflectionProperty ReflectionMethod ReflectionParameter ReflectionFunction
|
比如 ReflectionClass
的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?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');
function reflect($class) { try { $ref = new ReflectionClass($class); 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) { echo '[reflection exception: ]' . $e->getMessage(); } }
|
运行:
1 2 3 4 5
| $ php reflect.php name age [name]: Laruence [age]: 35
|
其余 4 个反射类参考手册 demo 即可。
后期静态绑定
参考 PHP 手册,先看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
class Base { public static function call() { echo '[called]: ' . __CLASS__ . PHP_EOL; }
public static function test() { self::call(); static::call(); } }
class Child extends Base { public static function call() { echo '[called]: ' . __CLASS__ . PHP_EOL; } }
Child::test();
|
输出:
1 2 3
| $ php late_static_bind.php [called]: Base [called]: Child
|
在对象实例化时,self::
会实例化根据定义所在的类,static::
会实例化调用它的类。
trait
基本使用
参考 PHP 手册,PHP 虽然是单继承的,但从 5.4 后可通过 trait 水平组合“类”,来实现“类”的多重继承,其实就是把重复的函数拆分成 triat 放到不同的文件中,通过 use 关键字按需引入、组合。可类比 Golang 的 struct 填鸭式组合来实现继承。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?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 { use Loggable; }
$foo = new Foo; $foo->setLogger(new DemoLogger); $foo->log('trait works', 1);
|
运行:
1 2 3
| $ php trait.php [message]: trait works [level]: 1
|
更多参考:我所理解的 PHP Trait
重要性质
优先级
当前类的函数会覆盖 trait 的同名函数,trait 会覆盖父类的同名函数( use trait
相当于当前类直接覆写了父类的同名函数)
trait 函数冲突
同时引入多个 trait 可用 ,
隔开,即多重继承。
多个 trait 有同名函数时,引入将发生命名冲突,使用 insteadof
来指明使用哪个 trait 的函数。
重命名与访问控制
使用 as
关键字可以重命名的 trait 中引入的函数,还可以修改其访问权限。
其他
trait 类似于类,可以定义属性、方法、抽象方法、静态方法和静态属性。
下边的苹果、微软和 Linux 的小栗子来说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| <?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; } }
trait Top { 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();
$linux->getMarketValue();
$linux::staticFunc();
$linux->getCEO();
$linux->staticValue(); $linux->staticValue();
|
运行:
1 2 3 4 5 6
| $ php trait.php [Linux Market Value]: Infinity [MicroSoft Static Function] [Apple CEO]: Tim Cook [MicroSoft Static Value]: 1 [MicroSoft Static Value]: 2
|
总结
本节简要提及了命名空间、文件自动加载、反射机制与 trait 等,Laravel 正是恰如其分的利用了这些新特性,才实现了组件化开发、服务加载等优雅的特性。