Passed
Pull Request — master (#2)
by Riikka
02:22
created

Container::isConstantValue()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 1
dl 0
loc 13
ccs 7
cts 7
cp 1
crap 5
rs 9.5222
c 0
b 0
f 0
1
<?php
2
3
namespace Simply\Container;
4
5
use Psr\Container\ContainerExceptionInterface;
6
use Psr\Container\ContainerInterface;
7
use Psr\Container\NotFoundExceptionInterface;
8
use Simply\Container\Entry\EntryInterface;
9
use Simply\Container\Entry\MixedEntry;
10
use Simply\Container\Exception\ContainerException;
11
use Simply\Container\Exception\NotFoundException;
12
13
/**
14
 * Dependency Injection Container that supports different types of entries.
15
 *
16
 * @author Riikka Kalliomäki <[email protected]>
17
 * @copyright Copyright (c) 2017-2018 Riikka Kalliomäki
18
 * @license http://opensource.org/licenses/mit-license.php MIT License
19
 */
20
class Container implements ContainerInterface, \ArrayAccess
21
{
22
    /** @var string[] Lists the names of classes used by different container entries */
23
    protected $types = [];
24
25
    /** @var array[] Cached container entry parameters for initializing the entries */
26
    protected $parameters = [];
27
28
    /** @var EntryInterface[] Cached entries used to resolve values */
29
    private $entryCache;
30
31
    /** @var array Cached values for container entries */
32
    private $valueCache;
33
34
    /** @var ContainerInterface Delegate container provided for dependency resolution */
35
    private $delegate;
36
37
    /**
38
     * Container constructor.
39
     */
40 12
    public function __construct()
41
    {
42 12
        $this->entryCache = [];
43 12
        $this->valueCache = [];
44 12
        $this->delegate = $this;
45 12
    }
46
47
    /**
48
     * Sets the delegate container that is provided for dependency resolution.
49
     * @param ContainerInterface $delegate Delegate container to use for dependency resolution
50
     */
51 1
    public function setDelegate(ContainerInterface $delegate): void
52
    {
53 1
        $this->delegate = $delegate;
54 1
    }
55
56
    /**
57
     * Returns PHP code for a cached container that can be loaded quickly on runtime.
58
     * @return string The PHP code for the cached container
59
     * @throws ContainerException If the container contains entries that cannot be cached
60
     */
61 5
    public function getCacheFile(): string
62
    {
63 5
        $this->loadCacheParameters();
64
65 4
        ksort($this->types);
66 4
        ksort($this->parameters);
67
68
        $template = <<<'TEMPLATE'
69 4
<?php return new class extends \Simply\Container\Container {
70
    protected $types = ['TYPES'];
71
    protected $parameters = ['PARAMETERS'];
72
};
73
TEMPLATE;
74
75 4
        return strtr($template, [
76 4
            "['TYPES']" => var_export($this->types, true),
77 4
            "['PARAMETERS']" => var_export($this->parameters, true),
78
        ]);
79
    }
80
81
    /**
82
     * Loads the cache parameters for all entries.
83
     * @throws ContainerException If the container contains entries that cannot be cached
84
     */
85 5
    private function loadCacheParameters(): void
86
    {
87 5
        foreach ($this->types as $id => $class) {
88 5
            if (!isset($this->entryCache[$id])) {
89 1
                continue;
90
            }
91
92 5
            $parameters = $this->entryCache[$id]->getCacheParameters();
93
94 5
            if (!$this->isConstantValue($parameters)) {
95 1
                throw new ContainerException("Unable to cache entry '$id', the cache parameters are not static");
96
            }
97
98 4
            $this->parameters[$id] = $parameters;
99
        }
100 4
    }
101
102
    /**
103
     * Tells if the given value is a static PHP value.
104
     * @param mixed $value The value to test
105
     * @return bool True if the value is a static PHP value, false if not
106
     */
107 5
    private function isConstantValue($value): bool
108
    {
109 5
        if (\is_array($value)) {
110 5
            foreach ($value as $item) {
111 5
                if (!$this->isConstantValue($item)) {
112 5
                    return false;
113
                }
114
            }
115
116 4
            return true;
117
        }
118
119 5
        return $value === null || is_scalar($value);
120
    }
121
122
    /**
123
     * Adds an entry to the container.
124
     * @param string $id The identifier for the container entry
125
     * @param EntryInterface $type The container entry to add
126
     * @throws ContainerException If trying to add an entry to an identifier that already exists
127
     */
128 11
    public function addEntry(string $id, EntryInterface $type): void
129
    {
130 11
        if (isset($this->types[$id])) {
131 1
            throw new ContainerException("Entry for identifier '$id' already exists");
132
        }
133
134 11
        $this->types[$id] = \get_class($type);
135 11
        $this->entryCache[$id] = $type;
136 11
    }
137
138
    /**
139
     * Returns value for the container entry with the given identifier.
140
     * @param string $id The entry identifier to look for
141
     * @return mixed The value for the entry
142
     * @throws NotFoundExceptionInterface If the entry cannot be found
143
     * @throws ContainerExceptionInterface If there are errors trying to load dependencies
144
     */
145 10
    public function get($id)
146
    {
147 10
        if (array_key_exists($id, $this->valueCache)) {
148 2
            return $this->valueCache[$id];
149
        }
150
151 10
        $entry = $this->getEntry($id);
152 9
        $value = $entry->getValue($this->delegate);
153
154 9
        if ($entry->isFactory()) {
155 1
            $this->entryCache[$id] = $entry;
156
        } else {
157 9
            $this->valueCache[$id] = $value;
158
        }
159
160 9
        return $value;
161
    }
162
163
    /**
164
     * Returns container entry for the given identifier.
165
     * @param string $id The entry identifier to look for
166
     * @return EntryInterface The container entry for the given identifier
167
     * @throws NotFoundExceptionInterface If the entry cannot be found
168
     */
169 10
    private function getEntry(string $id): EntryInterface
170
    {
171 10
        if (isset($this->entryCache[$id])) {
172 8
            return $this->entryCache[$id];
173
        }
174
175 5
        if (isset($this->types[$id])) {
176
            /** @var EntryInterface $entryClass */
177 4
            $entryClass = $this->types[$id];
178 4
            return $entryClass::createFromCacheParameters($this->parameters[$id]);
179
        }
180
181 1
        throw new NotFoundException("No entry was found for the identifier '$id'");
182
    }
183
184
    /**
185
     * Tells if the entry with the given identifier exists in the container.
186
     * @param string $id The entry identifier to look for
187
     * @return bool True if an entry with the given identifier exists, false otherwise
188
     */
189 3
    public function has($id): bool
190
    {
191 3
        return isset($this->types[$id]);
192
    }
193
194
    /**
195
     * Tells if the entry with the given identifier exists in the container.
196
     * @param string $offset The entry identifier to look for
197
     * @return bool True if an entry with the given identifier exists, false otherwise
198
     */
199 1
    public function offsetExists($offset): bool
200
    {
201 1
        return $this->has($offset);
202
    }
203
204
    /**
205
     * Returns value for the container entry with the given identifier.
206
     * @param string $offset The entry identifier to look for
207
     * @return mixed The value for the entry
208
     * @throws NotFoundExceptionInterface If the entry cannot be found
209
     * @throws ContainerExceptionInterface If there are errors trying to load dependencies
210
     */
211 1
    public function offsetGet($offset)
212
    {
213 1
        return $this->get($offset);
214
    }
215
216
    /**
217
     * Adds a container entry with a mixed type.
218
     * @param string $offset The identifier for the container entry
219
     * @param mixed $value The value for the container entry
220
     * @throws ContainerException If trying to add an entry to an identifier that already exists
221
     */
222 2
    public function offsetSet($offset, $value): void
223
    {
224 2
        $this->addEntry($offset, new MixedEntry($value));
225 2
    }
226
227
    /**
228
     * Removes an entry from the container.
229
     * @param string $offset The identifier of the container entry to remove
230
     */
231 1
    public function offsetUnset($offset): void
232
    {
233
        unset(
234 1
            $this->types[$offset],
235 1
            $this->parameters[$offset],
236 1
            $this->entryCache[$offset],
237 1
            $this->valueCache[$offset]
238
        );
239 1
    }
240
}
241