Pimple 浅析

最近项目里面要封装一些composer包,在查询资料的过程中,发现了牛逼的项目,强啊!Github

Pimple

官方的介绍就一句话:Pimple - 一个简单的 PHP 依赖注入容器;哈哈哈,果然简介也很简单,接下来看看源码,先从简单的1.x看起

ArrayAccess

看pimple的时候,就不得不提到一个常用的接口ArrayAccess,提供像访问数组一样访问对象的能力的接口。

举个例子

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
<?php

class ArrayAccessObj implements ArrayAccess
{
private $container = [];

public function __construct()
{
$this->container = [
'one' => 1,
'two' => 2,
'three' => 3,
];
}

/**
* 检查一个偏移位置是否存在.
* @param mixed $offset
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->container[$offset]);
}

/**
* 获取一个偏移位置的值
* @param mixed $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->container[$offset] ?? null;
}

/**
* 设置一个偏移位置的值
* @param mixed $offset
* @param mixed $value
*/
public function offsetSet($offset, $value)
{
$this->container[$offset] = $value;
}

/**
* 复位一个偏移位置的值
* @param mixed $offset
*/
public function offsetUnset($offset)
{
unset($this->container[$offset]);
}
}

$foo = new ArrayAccessObj();

var_dump($foo['one']); // int(1)
unset($foo['one']);
var_dump($foo['one']); // NULL
$foo['four'] = 4;
var_dump($foo['four']); // int(4)

那么你会获得结果:

1
2
3
int(1)
NULL
int(4)

简单的总结一下啦:

  • 类实现了ArrayAccess接口,那么这个类的对象就可以使用$foo[‘xxx’]这种结构了。
  • $foo[‘xxx’] 对应调用offsetGet方法。
  • $foo[‘xxx’] = ‘yyy’ 对应调用offsetSet方法。
  • isset($foo[‘xxx’]) 对应调用offsetExists方法。
  • unset($foo[‘xxx’]) 对应调用offsetUnset方法。

比如我将offsetGet改写成

1
2
3
4
5
public function offsetGet($offset)
{
echo '进入offsetGet方法,参数为:' . $offset . PHP_EOL;
return $this->container[$offset] ?? null;
}

那么结果将会变成

1
2
3
4
5
6
进入offsetGet方法,参数为:one
int(1)
进入offsetGet方法,参数为:one
NULL
进入offsetGet方法,参数为:four
int(4)

果然,ArrayAccess并没有限制你的想象,只要在定义的几个实现中完成你自己的天马行空,哈哈哈

闭包和匿名函数

都开始php8了,大家应该都知道这是啥了吧

1
2
3
4
5
6
<?php
$closure = function ($name) {
return 'Hello ' . $name;
};
echo $closure('Lihq');//Hello Lihq
var_dump(method_exists($closure, '__invoke'));//true

1.x

Pimple是一个用于PHP 5.3的小型依赖项注入容器,它仅由一个文件和一个类(约80行代码)组成。

通过ArrayAccess、闭包、匿名函数的结合,创造了一个艺术品。源码很简单的

定义参数

存入与读取,仅仅用到了ArrayAccess

1
2
3
4
5
6
7
<?php

require __DIR__ . '/vendor/autoload.php';

$container = new Pimple();
$container['bar'] = 'foo';
var_dump($container['bar']); // string(3) "foo"

定义服务

使用匿名函数定义服务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

require __DIR__ . '/vendor/autoload.php';

class Service
{
public function bar()
{
return 'bar';
}
}

$container = new Pimple();
$container['service'] = function () {
return new Service();
};

/** @var Service $service */
$service = $container['service'];
echo $service->bar(); // bar

定义共享服务

这样实例过的服务类就不会再次实例了

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
<?php

require __DIR__ . '/vendor/autoload.php';

class Service
{
public function __construct()
{
echo 'share仅调用一次实例' . PHP_EOL;
}

public function bar()
{
return 'bar';
}
}

$container = new Pimple();

$container['service'] = $container->share(function () {
return new Service();
});

/** @var Service $service */
$service = $container['service']; // share仅调用一次实例
echo $service->bar() . PHP_EOL; // bar

/** @var Service $service */
$service = $container['service']; // 再次获取不会重新new
echo $service->bar() . PHP_EOL; // bar

保护参数

原汁原味的匿名函数保护,没有被保护的会直接执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

require __DIR__ . '/vendor/autoload.php';

$container = new Pimple();

$container['bar'] = function () {
return 'bar';
};

$container['foo'] = $container->protect(function () {
return 'foo';
});

var_dump($container['bar']); // string(3) "bar"
var_dump($container['foo']); // object(Closure)#4 (0) {}
var_dump($container['foo']()); // string(3) "foo"

扩展服务

可以在原服务的基础上,进行功能修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

require __DIR__ . '/vendor/autoload.php';

$container = new Pimple();
$container['service'] = function () {
return 'service';
};

$container['service'] = $container->extend('service', function ($service) {
return $service . ' extend';
});

var_dump($container['service']); // string(14) "service1 extend"

原始访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

require __DIR__ . '/vendor/autoload.php';

class Service
{
public function bar()
{
return 'bar';
}
}

$container = new Pimple();

$container['service'] = $container->share(function () {
return new Service();
});

var_dump($container['service']); // object(Service)#5 (0) {}

var_dump($container->raw('service')); // object(Closure)#4 (2) {}

3.x

为啥跳过了2.x,其实版本是有2的,但是我看官方的分支已经只有1和master了,是不是代表2不怎么维护了,嘻嘻嘻,直接看3吧,毕竟项目里面用到的是3啦,下面来讲讲有区别的地方吧

PSR Container

在安装3.x的时候,发现它引入了”psr/container”: “^1.0”,并且”php”: “>=7.2.5”,666,果然要与时并进,使用当下最好的规范与语言。其中,PSR Container即大名鼎鼎的PSR 11

PSR 是 php-fig 提供的标准化建议,虽然不是官方组织,但是得到广泛认可。PSR-11 提供了容器接口。它包含 ContainerInterface 和 两个异常接口,并提供使用建议。

SplObjectStorage

1
2
3
4
5
6
7
8
9
public function __construct(array $values = [])
{
$this->factories = new \SplObjectStorage();
$this->protected = new \SplObjectStorage();

foreach ($values as $key => $value) {
$this->offsetSet($key, $value);
}
}

可以看到,在源码里,factories和protected都使用了SplObjectStorage

即同时继承了Countable , Iterator , Serializable , ArrayAccess接口,拥有很多方法

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
SplObjectStorage implements Countable , Iterator , Serializable , ArrayAccess {
// 从另一个存储添加所有对象
public addAll ( SplObjectStorage $storage ) : void

// 在存储中添加一个对象
public attach ( object $object , mixed $data = null ) : void

// 检查存储是否包含特定对象
public contains ( object $object ) : bool

// 返回存储中的对象数
public count ( ) : int

// 返回当前的存储条目
public current ( ) : object

// 从存储中删除对象
public detach ( object $object ) : void

// 计算所包含对象的唯一标识符
public getHash ( object $object ) : string

// 返回与当前迭代器条目关联的数据
public getInfo ( ) : mixed

// 返回当前迭代器所在的索引
public key ( ) : int

// 移至下一个条目
public next ( ) : void

// 检查存储中是否存在对象
public offsetExists ( object $object ) : bool

// 返回与对象关联的数据
public offsetGet ( object $object ) : mixed

// 将数据关联到存储中的对象
public offsetSet ( object $object , mixed $data = null ) : void

// 从存储中删除一个对象
public offsetUnset ( object $object ) : void

// 从当前存储中删除另一个存储中包含的对象
public removeAll ( SplObjectStorage $storage ) : void

// 从当前存储中删除除另一个存储中包含的对象以外的所有对象
public removeAllExcept ( SplObjectStorage $storage ) : void

// 将迭代器后退到第一个存储元素
public rewind ( ) : void

// 序列化存储
public serialize ( ) : string

// 设置与当前迭代器条目关联的数据
public setInfo ( mixed $data ) : void

// 从其字符串表示形式反序列化存储
public unserialize ( string $serialized ) : void

// 返回当前迭代器条目是否有效
public valid ( ) : bool
}

工厂factory

现在的默认情况,每次获得实例都会返回相同的实例,使用factory将每次返回新的实例,跟1.x是反过来的

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

use Pimple\Container;

require __DIR__ . '/vendor/autoload.php';

class Service
{
public function __construct()
{
echo 'share仅调用一次实例' . PHP_EOL;
}

public function bar()
{
return 'bar';
}
}

$container = new Container();

$container['service'] = function () {
return new Service();
};

/** @var Service $service */
$service = $container['service']; // share仅调用一次实例
echo $service->bar() . PHP_EOL; // bar

/** @var Service $service */
$service = $container['service']; // 不出现实例信息
echo $service->bar() . PHP_EOL; // bar

注册容器

通过接口定义,注册服务,方便解耦

1
2
3
4
5
6
7
8
9
10
use Pimple\Container;

class FooProvider implements Pimple\ServiceProviderInterface
{
public function register(Container $pimple)
{
// register some services and parameters
// on $pimple
}
}
1
$pimple->register(new FooProvider());

使用方式

哈哈哈,我自己的代码太low了,这里介绍一个用pimple写的开源代码

了解了一些基础概念[ArrayAccess、闭包和匿名函数、SplObjectStorage、服务、容器、注册]之后,应该是很好理解这些代码的

个人学习代码