Container::isConstantValue()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5

Importance

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