php如何实现一个轻量级容器(代码示例)

来源:不言 发布时间:2019-02-23 15:33:16 阅读量:1513

本篇文章给大家带来的内容是关于php如何实现一个轻量级容器(代码示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

什么是容器

在开发过程中,经常会用到的一个概念就是依赖注入。我们借助依懒注入来解耦代码,选择性的按需加载服务,而这些通常都是借助容器来实现。

容器实现对对象的统一管理,并且确保对象实例的唯一性

容器可以很轻易的找到有很多实现示例,如 PHP-DI 、 YII-DI 等各种实现,通常他们要么大而全,要么高度适配特定业务,与实际需要存在冲突。

出于需要,我们自己造一个轻量级的轮子,为了保持规范,我们基于 PSR-11 来实现。

PSR-11

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

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

/**

 * Describes the interface of a container that exposes methods to read its entries.

 */

interface ContainerInterface

{

    /**

     * Finds an entry of the container by its identifier and returns it.

     *

     * @param string $id Identifier of the entry to look for.

     *

     * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.

     * @throws ContainerExceptionInterface Error while retrieving the entry.

     *

     * @return mixed Entry.

     */

    public function get($id);

 

    /**

     * Returns true if the container can return an entry for the given identifier.

     * Returns false otherwise.

     *

     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.

     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.

     *

     * @param string $id Identifier of the entry to look for.

     *

     * @return bool

     */

    public function has($id);

}

实现示例

我们先来实现接口中要求的两个方法

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

abstract class AbstractContainer implements ContainerInterface

{

 

    protected $resolvedEntries = [];

 

    /**

     * @var array

     */

    protected $definitions = [];

 

    public function __construct($definitions = [])

    {

        foreach ($definitions as $id => $definition) {

            $this->injection($id, $definition);

        }

    }

 

    public function get($id)

    {

 

        if (!$this->has($id)) {

            throw new NotFoundException("No entry or class found for {$id}");

        }

 

        $instance = $this->make($id);

 

        return $instance;

    }

 

    public function has($id)

    {

        return isset($this->definitions[$id]);

    }

实际我们容器中注入的对象是多种多样的,所以我们单独抽出实例化方法。

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

82

83

84

85

86

87

88

89

90

91

92

protected function make($name)

{

    if (isset($this->resolvedEntries[$name])) {

        return $this->resolvedEntries[$name];

    }

 

    $definition = $this->definitions[$name];

    $params = [];

    if (is_array($definition) && isset($definition['class'])) {

        $params = $definition;

        $definition = $definition['class'];

        unset($params['class']);

    }

 

    $object = $this->reflector($definition, $params);

 

    return $this->resolvedEntries[$name] = $object;

}

 

public function reflector($concrete, array $params = [])

{

    if ($concrete instanceof \Closure) {

        return $concrete($params);

    } elseif (is_string($concrete)) {

        $reflection = new \ReflectionClass($concrete);

        $dependencies = $this->getDependencies($reflection);

        foreach ($params as $index => $value) {

            $dependencies[$index] = $value;

        }

        return $reflection->newInstanceArgs($dependencies);

    } elseif (is_object($concrete)) {

        return $concrete;

    }

}

 

/**

 * @param \ReflectionClass $reflection

 * @return array

 */

private function getDependencies($reflection)

{

    $dependencies = [];

    $constructor = $reflection->getConstructor();

    if ($constructor !== null) {

        $parameters = $constructor->getParameters();

        $dependencies = $this->getParametersByDependencies($parameters);

    }

 

    return $dependencies;

}

 

/**

 *

 * 获取构造类相关参数的依赖

 * @param array $dependencies

 * @return array $parameters

 * */

private function getParametersByDependencies(array $dependencies)

{

    $parameters = [];

    foreach ($dependencies as $param) {

        if ($param->getClass()) {

            $paramName = $param->getClass()->name;

            $paramObject = $this->reflector($paramName);

            $parameters[] = $paramObject;

        } elseif ($param->isArray()) {

            if ($param->isDefaultValueAvailable()) {

                $parameters[] = $param->getDefaultValue();

            } else {

                $parameters[] = [];

            }

        } elseif ($param->isCallable()) {

            if ($param->isDefaultValueAvailable()) {

                $parameters[] = $param->getDefaultValue();

            } else {

                $parameters[] = function ($arg) {

                };

            }

        } else {

            if ($param->isDefaultValueAvailable()) {

                $parameters[] = $param->getDefaultValue();

            } else {

                if ($param->allowsNull()) {

                    $parameters[] = null;

                } else {

                    $parameters[] = false;

                }

            }

        }

    }

    return $parameters;

}

如你所见,到目前为止我们只实现了从容器中取出实例,从哪里去提供实例定义呢,所以我们还需要提供一个方法.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

/**

 * @param string $id

 * @param string | array | callable $concrete

 * @throws ContainerException

 */

public function injection($id, $concrete)

{

    if (!is_string($id)) {

        throw new \InvalidArgumentException(sprintf(

            'The id parameter must be of type string, %s given',

            is_object($id) ? get_class($id) : gettype($id)

        ));

    }

 

    if (is_array($concrete) && !isset($concrete['class'])) {

        throw new ContainerException('数组必须包含类定义');

    }

 

    $this->definitions[$id] = $concrete;

}

只有这样吗?对的,有了这些操作我们已经有一个完整的容器了,插箱即用。

不过为了使用方便,我们可以再提供一些便捷的方法,比如数组式访问。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

class Container extends AbstractContainer implements \ArrayAccess

{

 

    public function offsetExists($offset)

    {

        return $this->has($offset);

    }

 

    public function offsetGet($offset)

    {

        return $this->get($offset);

    }

 

    public function offsetSet($offset, $value)

    {

        return $this->injection($offset, $value);

    }

 

    public function offsetUnset($offset)

    {

        unset($this->resolvedEntries[$offset]);

        unset($this->definitions[$offset]);

    }

}

这样我们就拥有了一个功能丰富,使用方便的轻量级容器了,赶快整合到你的项目中去吧。


标签: PHP
分享:
评论:
你还没有登录,请先