Issues (13)

src/DIC.php (6 issues)

1
<?php
2
3
namespace SimpleDIC;
4
5
use SimpleDIC\Exceptions\ConfigException;
6
use SimpleDIC\Parser\Parser;
7
8
class DIC
9
{
10
    /**
11
     * @var array
12
     */
13
    private static $values;
14
15
    /**
16
     * @var array
17
     */
18
    private static $cache;
19
20
    /**
21
     * @var string
22
     */
23
    private static $cacheDir;
24
25
    /**
26
     * @var string
27
     */
28
    private static $sha;
29
30
    /**
31
     * @param string $filename
32
     *
33
     * @return DIC
34
     * @throws Exceptions\ParserException
35
     */
36
    public static function initFromFile($filename)
37
    {
38
        self::$values = [];
39
        self::$sha = sha1_file($filename);
40
41
        $cacheFile = self::getCacheDir(). DIRECTORY_SEPARATOR .self::$sha.'.php';
42
43
        if (false === file_exists($cacheFile)) {
44
            if (false === is_dir(self::getCacheDir()) and false === mkdir(self::getCacheDir(), 0755, true)) {
45
                throw new \Exception(self::getCacheDir() . ' is not a writable directory.');
46
            }
47
48
            if (false === file_put_contents($cacheFile, '<?php return unserialize(\'' . serialize(Parser::parse($filename)) . '\');' . PHP_EOL)) {
49
                throw new \Exception(' Can\'t write cache file.');
50
            }
51
        }
52
53
        self::$cache = include($cacheFile);
54
55
        return new self;
56
    }
57
58
    /**
59
     * @param string $dir
60
     *
61
     * @return mixed
62
     */
63
    public static function setCacheDir($dir)
64
    {
65
        return self::$cacheDir = $dir;
66
    }
67
68
    /**
69
     * @return string
70
     */
71
    private static function getCacheDir()
72
    {
73
        return (!empty(self::$cacheDir)) ? self::$cacheDir : __DIR__.'/../_cache';
74
    }
75
76
    /**
77
     * @return int
78
     * @throws ConfigException
79
     */
80
    public static function count()
81
    {
82
        self::checkForCache();
83
84
        return count(self::$values);
85
    }
86
87
    /**
88
     * @throws ConfigException
89
     */
90
    private static function checkForCache()
91
    {
92
        if(empty(self::$cache)){
93
            throw new ConfigException('No config file was provided. You MUST use before initFromFile() method.');
94
        }
95
    }
96
97
    /**
98
     * @param string $cacheDir
99
     */
100
    public static function destroyCacheDir($cacheDir)
101
    {
102
        if (false === is_dir($cacheDir)) {
103
            throw new \InvalidArgumentException($cacheDir . ' is not a valid dir');
104
        }
105
106
        foreach (scandir($cacheDir) as $file) {
107
            if (!in_array($file, ['.', '..'])) {
108
109
                // destroy apcu
110
                if (self::isApcuEnabled()) {
111
                    $filePath = $cacheDir . DIRECTORY_SEPARATOR . $file;
112
                    $array = include($filePath);
113
114
                    foreach ($array as $id => $entry) {
115
                        apcu_delete(self::getApcuKey($id));
116
                    }
117
                }
118
119
                // delete file
120
                unlink($filePath);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $filePath does not seem to be defined for all execution paths leading up to this point.
Loading history...
121
            }
122
        }
123
    }
124
125
    /**
126
     * @param string $id
127
     *
128
     * @return mixed
129
     * @throws ConfigException
130
     */
131
    public static function get($id)
132
    {
133
        self::checkForCache();
134
135
        // if APCU is enabled return the entry from APCU store
136
        if (self::isApcuEnabled() and apcu_exists(self::getApcuKey($id))) {
137
            return apcu_fetch(self::getApcuKey($id));
138
        }
139
140
        // otherwise set the value in memory if it is not present
141
        if (false === isset(self::$values[$id])) {
142
            self::setValue($id);
143
        }
144
145
        // return the entry from memory
146
        return self::$values[$id];
147
    }
148
149
    /**
150
     * Set an entry in the container.
151
     *
152
     * @param string $id
153
     *
154
     * @return bool
155
     * @throws ConfigException
156
     */
157
    private static function setValue($id)
158
    {
159
        $content = self::$cache[$id];
160
161
        // if is not a class set the entry value in DIC
162
        if (false === isset($content['class'])) {
163
            self::$values[$id] = self::getFromEnvOrDICParams($content);
164
165
            if (self::isApcuEnabled()) {
166
                self::tryToStoreInApcu($id);
167
            }
168
169
            return true;
170
        }
171
172
        // otherwise it's a class, so extract variables
173
        extract($content);
174
175
        $methodArgsToInject = self::getArgumentsToInject(isset($method_arguments) ? $method_arguments : null);
176
        $classArgsToInject = self::getArgumentsToInject(isset($arguments) ? $arguments : null);
177
178
        try {
179
            self::$values[$id] = self::instantiateTheClass($class, $classArgsToInject, isset($method) ? $method : null, $methodArgsToInject);
180
181
            if (self::isApcuEnabled()) {
182
                self::tryToStoreInApcu($id);
183
            }
184
185
            return true;
186
        } catch (\Error $error) {
187
            return false;
188
        } catch (\Exception $exception) {
189
            return false;
190
        }
191
    }
192
193
    /**
194
     * @return bool
195
     */
196
    private static function isApcuEnabled()
197
    {
198
        return (extension_loaded('apc') && ini_get('apc.enabled'));
199
    }
200
201
    /**
202
     * @param string $id
203
     *
204
     * @return string
205
     */
206
    private static function getApcuKey($id)
207
    {
208
        return md5(self::$sha . DIRECTORY_SEPARATOR . $id);
209
    }
210
211
    /**
212
     * @param string $id
213
     */
214
    private static function tryToStoreInApcu($id)
215
    {
216
        try {
217
            apcu_add(self::getApcuKey($id), self::$values[$id]);
218
        } catch (\Exception $e) {
219
            // nothing to do, continue
220
        }
221
    }
222
223
    /**
224
     * @param string $id
225
     *
226
     * @return bool
227
     * @throws ConfigException
228
     */
229
    public static function has($id)
230
    {
231
        self::checkForCache();
232
233
        if (self::isApcuEnabled() and apcu_exists(self::getApcuKey($id))) {
234
            return true;
235
        }
236
237
        return isset(self::$values[$id]);
238
    }
239
240
    /**
241
     * @return array
242
     * @throws ConfigException
243
     */
244
    public static function keys()
245
    {
246
        self::checkForCache();
247
248
        return array_keys(self::$cache);
249
    }
250
251
    /**
252
     * @param string $class
253
     * @param array $classArguments
254
     * @param null $method
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $method is correct as it would always require null to be passed?
Loading history...
255
     * @param array $methodArguments
256
     *
257
     * @return mixed|bool
258
     *
259
     * @throws \ReflectionException
260
     */
261
    private static function instantiateTheClass($class, array $classArguments = [], $method = null, array $methodArguments = [])
262
    {
263
        if (false === class_exists($class)) {
264
            return false;
265
        }
266
267
        $reflected = new \ReflectionClass($class);
268
269
        // 1. the class has no method to call
270
        if (null == $method) {
0 ignored issues
show
The condition null == $method is always true.
Loading history...
271
            return new $class(...$classArguments);
272
        }
273
274
        if (false === $reflected->hasMethod($method)) {
275
            return false;
276
        }
277
278
        // 2. the method to call is static
279
        if ($reflected->getMethod($method)->isStatic()) {
280
            return $class::$method(...$methodArguments);
281
        }
282
283
        // 3. the class has a private constructor
284
        if ($reflected->hasMethod('__construct') and $reflected->getConstructor()->isPrivate()) {
285
            return call_user_func_array([$class, $method], $methodArguments);
286
        }
287
288
        // 4. the class has a public constructor
289
        return (new $class(...$classArguments))->$method(...$methodArguments);
290
    }
291
292
    /**
293
     * @param null $providedArguments
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $providedArguments is correct as it would always require null to be passed?
Loading history...
294
     *
295
     * @return array
296
     * @throws ConfigException
297
     */
298
    private static function getArgumentsToInject($providedArguments = null)
299
    {
300
        $returnArguments = [];
301
302
        if (null != $providedArguments) {
0 ignored issues
show
The condition null != $providedArguments is always false.
Loading history...
303
            foreach ($providedArguments as $argument) {
304
                $returnArguments[] = self::getArgumentToInject($argument);
305
            }
306
        }
307
308
        return $returnArguments;
309
    }
310
311
    /**
312
     * @param string $argument
313
     *
314
     * @return mixed|string|null
315
     * @throws ConfigException
316
     */
317
    private static function getArgumentToInject($argument)
318
    {
319
        $id = ltrim($argument, '@');
320
321
        return (isset(self::$cache[$id])) ? self::get($id) : self::getFromEnvOrDICParams($argument);
322
    }
323
324
    /**
325
     * @param string $parameter
326
     *
327
     * @return mixed|string|null
328
     */
329
    private static function getFromEnvOrDICParams($parameter)
330
    {
331
        if (is_string($parameter)) {
0 ignored issues
show
The condition is_string($parameter) is always true.
Loading history...
332
            if (null !== self::getEnvKey($parameter)) {
333
                return (getenv(self::getEnvKey($parameter))) ? getenv(self::getEnvKey($parameter)) : $parameter;
334
            }
335
336
            return (DICParams::has(self::getParamKey($parameter))) ? DICParams::get(self::getParamKey($parameter)) : $parameter;
337
        }
338
339
        return $parameter;
340
    }
341
342
    /**
343
     * Extract from a string like %env(FOO)%
344
     *
345
     * @param string $string
346
     *
347
     * @return mixed|null
348
     */
349
    private static function getEnvKey($string)
350
    {
351
        preg_match('~%env\((.*?)\)%~', $string, $matches);
352
353
        return (count($matches) > 0) ? $matches[1] : null;
354
    }
355
356
    /**
357
     * @param string $string
358
     *
359
     * @return string
360
     */
361
    private static function getParamKey($string)
362
    {
363
        return trim($string, '%');
364
    }
365
}
366