Laravel核心概念
服务容器
Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具。
容器
简单来说,容器是一个装载对象、对象的描述(类、接口)或者是提供对象的回调。
一个简单的容器类:
class Container
{
protected $params = [];
public function __construct($params){
$this -> params = $params;
}
public function getMailTransport()
{
return new Zend_Mail_Transport_Smtp('smtp.gmail.com', [
'auth' => 'login',
'username' => $this->params['username'],
'password' => $this->params['password'],
'ssl' => 'ssl',
'port' => 465,
]);
}
public function getMailer()
{
$mailer = new Zend_Mail();
$mailer->setDefaultTransport($this->getMailTransport());
return $mailer;
}
}
在了解服务容器之前,需要先了解什么是依赖注入。
依赖注入
通过构造函数,把一个外部的类的实例注入到某一个类的实例内部,而不是在这个类实例内部创建被注入的类的实例,就是依赖注入(Dependency Injection)。
假设有一个SessionStorage
管理类和一个User
类,User
类需要依赖于SessionStorage
类才能正常工作,代码如下:
Session管理类
class SessionStorage
{
function __construct($cookieName='PHPSESSID')
{
session_name($cookieName);
session_start();
}
function set($key, $value)
{
$_SESSION[$key] = $value;
}
function get($key)
{
return $_SESSION[$key];
}
// ...
}
用户管理类
class User
{
protected $storage;
function __construct()
{
$this->storage = new SessionStorage();
}
function setLanguage($language)
{
$this->storage->set('language', $language);
}
function getLanguage()
{
return $this->storage->get('language');
}
// ...
}
此时如果想要修改CookieName
,可通过修改SessionStorage
类中的参数,或者将CookieName
作为User
类的参数传入,但这两种方法均使跟User
类无关的东西掺杂到了构造函数中。此时可以采用依赖注入
的方法:
class User
{
// 将 SessionStorage 类的实例作为参数 进行传递
function __construct($storage)
{
$this->storage = $storage;
}
// ...
}
使用时:
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
通过构造函数,把一个外部SessionStorage
的实例注入到User
的实例内部,而不是在User
实例内部创建SessionStorage
实例,就是依赖注入
。
优势:当测试我们的类时,我们可以模拟依赖类并将其作为参数传递。每个类必须专注于一个特定的任务,而不应该关心解决它们的依赖性。这样,你将拥有一个更专注和可维护的应用程序。
扩展:依赖注入不仅限于在构造函数中使用,也可通过以下场景使用:
-
Setter
注入:class User { function setSessionStorage($storage) { $this->storage = $storage; } }
-
属性注入:
class User { public $sessionStorage; } $user->sessionStorage = $storage;
-
接口注入:
interface IUser{ public function injectUser( SessionStorage $storage ); } class UserProvider implements IUser{ protected $storage; public function __construct(){ ... } public function injectUser( SessionStorage $storage ){ $this->storage = $storage; } }
作为经验, Constructor 注入
最适合必须的依赖关系,比如示例中的情况; Setter 注入
最适合可选依赖关系,比如缓存一个对象实例。
控制反转[1]
简单说来,就是一个类把自己的的控制权交给另外一个对象,类间的依赖由这个对象去解决。
刚刚列了一段代码:
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
在这里我们手动的创建了一个用户,并为其注入了Session存储实例,但显然,手动并不是我们的最终目标,我们需要自动化。为此,我们需要一种高级的生产车间,我们只需要向生产车间提交一个脚本,工厂便能够通过指令自动化生产。这种更为高级的工厂,也就是工厂模式的升华 —— 容器(IOC)。
class Container
{
protected $binds;
protected $instances;
public function bind($abstract, $concrete)
{
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}
public function make($abstract, $parameters = [])
{
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
array_unshift($parameters, $this);
return call_user_func_array($this->binds[$abstract], $parameters);
}
}
这时候,一个十分粗糙的服务容器
就诞生了。先来看看这个容器如何使用吧!
// 创建一个容器
$container = new Container;
// 向容器 添加User的生产脚本
$container->bind('user', function($container, $moduleName) {
return new User($container->make($moduleName));
});
// 向容器添加 各存储 模块的脚本
$container->bind('storage', function($container) {
return new SessionStorage;
});
...
// ****************** 华丽丽的分割线 **********************
// 开始启动生产
$user1 = $container->make('user', 'storage');
// ...随意添加
看到没?通过最初的 绑定(bind
) 操作,我们向容器
注册了一些生产脚本,这些生产脚本在生产指令下达之时便会执行。发现没有?我们彻底的解除了User
与SessionStorage
的依赖关系,更重要的是,容器类也丝毫没有和他们产生任何依赖!我们通过注册、绑定的方式向容器中添加一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)作为生产一个类的实例的脚本,只有在真正的生产(make
)操作被调用执行时,才会触发。
在Laravel
中的应用:
注入一个类:
App::bind('foo', function($app)
{
return new FooBar;
});
这个例子的意思是创建一个别名为 foo
的类,使用时实际实例化的是 FooBar
。
使用这个类的方法是:
$value = App::make('foo');
$value
实际上是 FooBar
对象。
如果希望使用单例模式来实例化类,那么使用:
App::singleton('foo', function()
{
return new FooBar;
});
这样的话每次实例化后的都是同一个对象。
门面模式
它为子系统中的一组接口提供一个統一的高层接口,使得子系統更容易使用。
在Laravel
中的使用:
Route::get('/',function(){
return view('welcome');
})
我们来看看Route
为何能以静态方法进行访问,简单说就是模拟一个类,提供一个静态魔术方法__callStatic
,并将该静态方法映射到真正的方法上。
我们使用的Route
类实际上是Illuminate\Support\Facades\Route
通过class_alias()
函数创造的别名而已,这个类被定义在文件vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php
。
<?php
namespace Illuminate\Support\Facades;
/**
* @see \Illuminate\Routing\Router
*/
class Route extends Facade {
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'router';
}
}
其实仔细看,会发现这个类继承了一个叫做Facade
的类,到这里谜底差不多要解开了。上述定义中,我们看到了getFacadeAccessor
方法返回了一个字符route
,这是什么意思呢?事实上,这个值被一个 ServiceProvider
注册过,大家应该知道注册了个什么,当然是那个真正的路由类。