PHP容器--Pimple运行流程浅析
需要具备的知识点
闭包
闭包和匿名函数在PHP5.3.0中引入的。
闭包是指:创建时封装周围状态的函数。即使闭包所处的环境不存在了,闭包中封装的状态依然存在。
理论上,闭包和匿名函数是不同的概念。但是PHP将其视作相同概念。
实际上,闭包和匿名函数是伪装成函数的对象。他们是Closure类的实例。
闭包和字符串、整数一样,是一等值类型。
创建闭包:
<?php
$closure = function ($name) {
return 'Hello ' . $name;
};
echo $closure('nesfo');//Hello nesfo
var_dump(method_exists($closure, '__invoke'));//true
我们之所以能调用
$closure变量,是因为这个变量的值是一个闭包,而且闭包对象实现了__invoke()魔术方法。只要变量名后有(),PHP就会查找并调用__invoke()方法。
通常会把PHP闭包当作函数的回调使用。
array_map(), preg_replace_callback()方法都会用到回调函数,这是使用闭包的最佳时机!
举个例子:
<?php
$numbersPlusOne = array_map(function ($number) {
return $number + 1;
}, [1, 2, 3]);
print_r($numbersPlusOne);
得到结果:
[2, 3, 4]
在闭包出现之前,只能单独创建具名函数,然后使用名称引用那个函数。这么做,代码执行会稍微慢点,而且把回调的实现和使用场景隔离了。
<?php
function incrementNum ($number) {
return $number + 1;
}
$numbersPlusOne = array_map('incrementNum', [1, 2, 3]);
print_r($numbersPlusOne);
SPL
ArrayAccess
实现ArrayAccess接口,可以使得object像array那样操作。ArrayAccess接口包含四个必须实现的方法:
interface ArrayAccess {
//检查一个偏移位置是否存在
public mixed offsetExists ( mixed $offset );
//获取一个偏移位置的值
public mixed offsetGet( mixed $offset );
//设置一个偏移位置的值
public mixed offsetSet ( mixed $offset );
//复位一个偏移位置的值
public mixed offsetUnset ( mixed $offset );
}
SplObjectStorage
SplObjectStorage类实现了以对象为键的映射(map)或对象的集合(如果忽略作为键的对象所对应的数据)这种数据结构。这个类的实例很像一个数组,但是它所存放的对象都是唯一。该类的另一个特点是,可以直接从中删除指定的对象,而不需要遍历或搜索整个集合。
::class语法
因为 ::class 表示是字符串。用 ::class 的好处在于 IDE 里面可以直接改名一个 class,然后 IDE 自动处理相关引用。
同时,PHP 执行相关代码时,是不会先加载相关 class 的。
同理,代码自动化检查 inspect 也可以正确识别 class。
Pimple容器流程浅析
Pimpl是php社区中比较流行的容器。代码不是很多,详见https://github.com/silexphp/Pimple/blob/master/src/Pimple/Container.php 。
我们的应用可以基于Pimple开发:
namespace EasyWeChat\Foundation;
use Pimple\Container;
class Application extends Container
{
/**
* Service Providers.
*
* @var array
*/
protected $providers = [
ServiceProviders\ServerServiceProvider::class,
ServiceProviders\UserServiceProvider::class
];
/**
* Application constructor.
*
* @param array $config
*/
public function __construct($config)
{
parent::__construct();
$this['config'] = function () use ($config) {
return new Config($config);
};
if ($this['config']['debug']) {
error_reporting(E_ALL);
}
$this->registerProviders();
}
/**
* Add a provider.
*
* @param string $provider
*
* @return Application
*/
public function addProvider($provider)
{
array_push($this->providers, $provider);
return $this;
}
/**
* Set providers.
*
* @param array $providers
*/
public function setProviders(array $providers)
{
$this->providers = [];
foreach ($providers as $provider) {
$this->addProvider($provider);
}
}
/**
* Return all providers.
*
* @return array
*/
public function getProviders()
{
return $this->providers;
}
/**
* Magic get access.
*
* @param string $id
*
* @return mixed
*/
public function __get($id)
{
return $this->offsetGet($id);
}
/**
* Magic set access.
*
* @param string $id
* @param mixed $value
*/
public function __set($id, $value)
{
$this->offsetSet($id, $value);
}
}
如何使用我们的应用:
$app = new Application([]);
$user = $app->user;
之后我们就可以使用$user对象的方法了。我们发现其实并没有$this->user这个属性,但是可以直接使用。主要是这两个方法起的作用:
public function offsetSet($id, $value){}
public function offsetGet($id){}
下面我们将解释在执行这两句代码,Pimple做了什么。但在解释这个之前,我们先看看容器的一些核心概念。
服务提供者
服务提供者是连接容器与具体功能实现类的桥梁。服务提供者需要实现接口ServiceProviderInterface:
namespace Pimple;
/**
* Pimple service provider interface.
*
* @author Fabien Potencier
* @author Dominik Zogg
*/
interface ServiceProviderInterface
{
/**
* Registers services on the given container.
*
* This method should only be used to configure services and parameters.
* It should not get services.
*
* @param Container $pimple A container instance
*/
public function register(Container $pimple);
}
所有服务提供者必须实现接口register方法。
我们的应用里默认有2个服务提供者:
protected $providers = [
ServiceProviders\ServerServiceProvider::class,
ServiceProviders\UserServiceProvider::class
];
以UserServiceProvider为例,我们看其代码实现:
namespace EasyWeChat\Foundation\ServiceProviders;
use EasyWeChat\User\User;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
/**
* Class UserServiceProvider.
*/
class UserServiceProvider implements ServiceProviderInterface
{
/**
* Registers services on the given container.
*
* This method should only be used to configure services and parameters.
* It should not get services.
*
* @param Container $pimple A container instance
*/
public function register(Container $pimple)
{
$pimple['user'] = function ($pimple) {
return new User($pimple['access_token']);
};
}
}
我们看到,该服务提供者的注册方法会给容器增加属性user,但是返回的不是对象,而是一个闭包。这个后面我再做讲解。
服务注册
我们在Application里构造函数里使用$this->registerProviders();对所有服务提供者进行了注册:
private function registerProviders()
{
foreach ($this->providers as $provider) {
$this->register(new $provider());
}
}
仔细看,我们发现这里实例化了服务提供者,并调用了容器Pimple的register方法:
public function register(ServiceProviderInterface $provider, array $values = array())
{
$provider->register($this);
foreach ($values as $key => $value) {
$this[$key] = $value;
}
return $this;
}
而这里调用了服务提供者的register方法,也就是我们在上一节中提到的:注册方法给容器增加了属性user,但返回的不是对象,而是一个闭包。
当我们给容器Pimple添加属性user的同时,会调用offsetSet($id, $value)方法:给容器Pimple的属性values、keys分别赋值:
$this->values[$id] = $value;
$this->keys[$id] = true;
到这里,我们还没有实例化真正提供实际功能的类EasyWeChat\User\Usr。但已经完成了服务提供者的注册工作。
当我们运行到这里:
$user = $app->user;
会调用offsetGet($id)并进行实例化真正的类:
$raw = $this->values[$id];
$val = $this->values[$id] = $raw($this);
$this->raw[$id] = $raw;
$this->frozen[$id] = true;
return $val;
$raw获取的是闭包:
$pimple['user'] = function ($pimple) {
return new User($pimple['access_token']);
};
$raw($this)返回的是实例化的对象User。也就是说只有实际调用才会去实例化具体的类。后面我们就可以通过$this['user']或者$this->user调用User类里的方法了。
当然,Pimple里还有很多特性值得我们去深入研究,这里不做过多讲解。
参考
1、PHP: 数组式访问 - Manual
http://php.net/manual/zh/class.arrayaccess.php
2、利用 SPL 快速实现 Observer 设计模式
https://www.ibm.com/developerworks/cn/opensource/os-cn-observerspl/
3、Pimple - A simple PHP Dependency Injection Container
https://pimple.sensiolabs.org/
4、Laravel源码里面为什么要用::class语法? - 知乎
https://www.zhihu.com/question/52656676?from=profile_question_card
5、Laravel 学习笔记 —— 神奇的服务容器 | Laravel China 社区 - 高品质的 Laravel 和 PHP 开发者社区 - Powered by PHPHub
https://laravel-china.org/topics/789/laravel-learning-notes-the-magic-of-the-service-container
6、Pimple/README_zh.rst at master · 52fhy/Pimple
https://github.com/52fhy/Pimple/blob/master/README_zh.rst
PHP容器--Pimple运行流程浅析的更多相关文章
- 玩转Windows服务系列——服务运行、停止流程浅析
通过研究Windows服务注册卸载的原理,感觉它并没有什么特别复杂的东西,Windows服务正在一步步退去它那神秘的面纱,至于是不是美女,大家可要睁大眼睛看清楚了. 接下来研究一下Windows服务的 ...
- 玩转Windows服务系列——服务运行、停止流程浅析
原文:玩转Windows服务系列——服务运行.停止流程浅析 通过研究Windows服务注册卸载的原理,感觉它并没有什么特别复杂的东西,Windows服务正在一步步退去它那神秘的面纱,至于是不是美女,大 ...
- 程序员修神之路--打通Docker镜像发布容器运行流程
菜菜哥,我看了一下docker相关的内容,但是还是有点迷糊 还有哪不明白呢? 如果我想用docker实现所谓的云原生,我的项目该怎么发布呢? 这还是要详细介绍一下docker了 Docker 是一个开 ...
- 浅析MyBatis(一):由一个快速案例剖析MyBatis的整体架构与运行流程
MyBatis 是轻量级的 Java 持久层中间件,完全基于 JDBC 实现持久化的数据访问,支持以 xml 和注解的形式进行配置,能灵活.简单地进行 SQL 映射,也提供了比 JDBC 更丰富的结果 ...
- servlet运行流程
servlet运行流程 (2013-06-19 19:16:43) 转载▼ 首先Servlet被部署到Web容器中,当客户端发送调用这个Servlet的请求到达Web容器时,Web容器会先判 ...
- Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程
本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. ...
- [MUD]MUDLIB详解/MUDOS运行流程/最小MUDLIB/mud文件结构
现在大部分中文MUD都是在东方故事(esII)基础上发展起来的,其目录结构基本一样, 也有个别MUD为了标新立异对个别目录换了个名字以示不同,但其实质没有什么变化. 这个做的最可恶的是xkx,把一个好 ...
- egametang框架服务端运行流程
et框架的构建块主要由entity和componet组成,类似unity的组件.一个Entity可以挂载多个不同Component.Entity和Component的共同基类Disposer用于提供对 ...
- Spark学习之路 (七)Spark 运行流程
一.Spark中的基本概念 (1)Application:表示你的应用程序 (2)Driver:表示main()函数,创建SparkContext.由SparkContext负责与ClusterMan ...
随机推荐
- anjular中Service、Factory、Provider的使用与js中创建对象的总结
在学习anjular中Service的使用时,发现和js中的创建对象的方式有一定的联系,所以总结了anjular中Service.Factory.Provider的使用方式与js创建对象的方式 一.先 ...
- 如何用PHP遍历文件数目 或删除目录下的全部文件?
先说一下基础知识: 文件位置如下图所示: 1.判断是文件还是目录 var_dump(filetype("./aa/bb/cc.txt")); 输出: string(4) " ...
- 【Python 函数对象 命名空间与作用域 闭包函数 装饰器 迭代器 内置函数】
一.函数对象 函数(Function)作为程序语言中不可或缺的一部分,但函数作为第一类对象(First-Class Object)却是 Python 函数的一大特性. 那到底什么是第一类对象(Firs ...
- Telegram学习解析系列(三) : Build Telegram报错分析总结
正好通过这次 Telegram 的运行,很想把常见的项目运行的错误好好的总结一下,在前面的博客中,又星星散散的总结过错误和一些警告的消除方法,这次把错误处理一下,还有Telegram项目中有999+的 ...
- 《实战java高并发程序设计》源码整理及读书笔记
日常啰嗦 不要被标题吓到,虽然书籍是<实战java高并发程序设计>,但是这篇文章不会讲高并发.线程安全.锁啊这些比较恼人的知识点,甚至都不会谈相关的技术,只是写一写本人的一点读书感受,顺便 ...
- Struts2结合Ajax实现登录
前言:Struts2作为一款优秀的MVC框架,和Ajax结合在一起,用户就会有良好的体验,本篇博文我们来模拟一个简单的登录操作,实现Ajax的异步请求,其中Struts2进行的是链接处理,Action ...
- 跟着刚哥深入学maven
前言:目前所有的项目都在使用maven,可是一直没有时间去整理学习,这两天正好有时间,好好的整理一下. 一.为什么使用Maven这样的构建工具[why] ① 一个项目就是一个工程 如果项目非常庞大,就 ...
- JavaScript 扫描枪使用(一)
JavaScript 扫描枪应用(一)com.js为主要的代码实现,test.html文件为测试的页面,其中包括了com.js文件中方法的调用.以下为测试成功代码:com.js //com.js /* ...
- mybatis批量增、删、改(更新)操作oracle和mysql批量写法小记
前言:用mybatis也好几年了,mybatis在批量的增删操作也写起来也是比较简单的,只有批量更新这一块是特别坑,特此记录. 注:本文主要用来记录oracle和mysql数据库在使用mybatis的 ...
- Java基础——抽象类和接口
之所以将抽象类和接口放在一起做笔记,是因为他们之间很难区分又各自独立.在学习完Java程序设计的三大特点(封装.继承.多态)之后,我最大的收获是,慢慢理解了Java语言这种面向对象程序设计的优越性,它 ...