Completed
Push — master ( c89067...3b0f09 )
by Hong
04:25
created

Container::__callStatic()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 4
nc 3
nop 2
1
<?php
2
3
/**
4
 * Phoole (PHP7.2+)
5
 *
6
 * @category  Library
7
 * @package   Phoole\Di
8
 * @copyright Copyright (c) 2019 Hong Zhang
9
 */
10
declare(strict_types=1);
11
12
namespace Phoole\Di;
13
14
use Phoole\Config\Config;
15
use Phoole\Di\Util\ContainerTrait;
16
use Phoole\Di\Util\StaticAccessTrait;
17
use Psr\Container\ContainerInterface;
18
use Phoole\Config\ConfigAwareInterface;
19
use Phoole\Di\Exception\LogicException;
20
use Phoole\Di\Exception\NotFoundException;
21
use Phoole\Base\Reference\ReferenceInterface;
22
use Phoole\Di\Exception\UnresolvedClassException;
23
24
/**
25
 * Dependency Injection
26
 *
27
 * @package Phoole\Di
28
 */
29
class Container implements ContainerInterface, ReferenceInterface, ConfigAwareInterface
30
{
31
    use ContainerTrait;
32
    use StaticAccessTrait;
33
34
    /**
35
     * Constructor
36
     *
37
     * $config    for config & reference lookup
38
     * $delegator for object lookup. If NULL will use $this
39
     *
40
     * @param  Config             $config
41
     * @param  ContainerInterface $delegator
42
     */
43
    public function __construct(
44
        Config $config,
45
        ?ContainerInterface $delegator = NULL
46
    ) {
47
        $this->setConfig($config);
48
        $this->setDelegator($delegator);
49
50
        // for static access
51
        self::setContainer($this);
52
53
        // create all objects now
54
        $this->initContainer();
55
    }
56
57
    /**
58
     *
59
     * ```php
60
     * // get the cache object
61
     * $cache = $container->get('cache');
62
     *
63
     * // always get a NEW cache object
64
     * $cacheNew = $container->get('cache@');
65
     *
66
     * // get an object shared in SESSION scope
67
     * $sessCache = $container->get('cache@SESSION');
68
     *
69
     * // get object (created already by definition) by classname/interface name
70
     * // useful for dependency injection
71
     * $cache = $container->get(CacheInterface::class);
72
     *
73
     * // get a NEW object by classname
74
     * // useful for creating object and utilize 'di.before' & 'di.after'
75
     * $obj = $container->get(myClass::class);
76
     * ```
77
     *
78
     * {@inheritDoc}
79
     */
80
    public function get($id): object
81
    {
82
        // check definition
83
        if ($this->hasDefinition($id)) {
84
            return $this->getInstance($id);
85
        }
86
        // check classmap
87
        if (is_object($object = $this->matchClass($id))) {
88
            return $object;
89
        }
90
        throw new NotFoundException("Object '$id' not found");
91
    }
92
93
    /**
94
     * {@inheritDoc}
95
     */
96
    public function has($id): bool
97
    {
98
        return $this->hasDefinition($id) || NULL !== $this->hasClass($id);
99
    }
100
101
    /**
102
     * Reload & resolve all service definitions
103
     *
104
     * @return void
105
     */
106
    protected function initContainer(): void
107
    {
108
        // set object reference pattern
109
        $this->setReferencePattern('${#', '}');
110
111
        // predefine couple of ids
112
        $tree = $this->getConfig()->getTree();
113
        $tree->add($this->getRawId('config'), $this->getConfig());
114
        $tree->add($this->getRawId('container'), $this->delegator);
115
116
        // resolve all services
117
        $this->autoResolve();
118
119
        // resolve all object reference in the config
120
        $settings = &$tree->get('');
121
        $this->deReference($settings);
122
    }
123
124
    /**
125
     * Resolve defined ids
126
     *
127
     * @param  array $ids
128
     * @param  bool  $autoClass
129
     * @return void
130
     */
131
    protected function resolveAllIds(array &$ids, bool $autoClass = FALSE): void
132
    {
133
        $this->autoLoad = $autoClass;
134
        $max = count($ids) * 3;
135
        $cnt = 0;
136
        while ($id = array_shift($ids)) {
137
            if ($cnt++ > $max) {
138
                $ids[] = $id;
139
                break;
140
            }
141
            try {
142
                $this->get($id);
143
            } catch (UnresolvedClassException $e) {
144
                $ids[] = $id;
145
            }
146
        }
147
        $this->autoLoad = FALSE;
148
    }
149
150
    protected function autoResolve(): void
151
    {
152
        // get all ids of the defined services
153
        $ids = array_keys($this->getConfig()->get($this->prefix . 'service'));
154
155
        // resolve in service definition only
156
        if (!empty($ids)) {
157
            $this->resolveAllIds($ids);
158
        }
159
160
        // try autoload class
161
        if (!empty($ids)) {
162
            $this->resolveAllIds($ids, TRUE);
163
        }
164
165
        // error
166
        if (!empty($ids)) {
167
            throw new LogicException("Container error for ID " . $ids[0]);
168
        }
169
    }
170
}