Passed
Push — master ( 0d7392...c861dd )
by Chauncey
04:13
created

CacheBuilder::isAccessible()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Charcoal\Cache;
4
5
use ArrayAccess;
6
use Traversable;
7
use InvalidArgumentException;
8
9
// From 'tedivm/stash'
10
use Stash\Interfaces\ItemInterface;
11
use Stash\Interfaces\PoolInterface;
12
use Stash\Interfaces\DriverInterface;
13
use Stash\Pool;
14
15
/**
16
 * Model Loader Builder.
17
 *
18
 * Build custom ModelLoader objects with a certain obj type / optional obj key.
19
 */
20
final class CacheBuilder
21
{
22
    /**
23
     * List of available cache driver names and classes.
24
     *
25
     * @var ArrayAccess|array
26
     */
27
    private $drivers;
28
29
    /**
30
     * Defaultlogger instance.
31
     *
32
     * @var \Psr\Log\LoggerInterface|null
33
     */
34
    private $logger;
35
36
    /**
37
     * Default namespace for new pools.
38
     *
39
     * @var string|null
40
     */
41
    private $namespace;
42
43
    /**
44
     * Default "Pool" class to use for making new pools.
45
     *
46
     * @var string
47
     */
48
    private $poolClass = Pool::class;
49
50
    /**
51
     * Default "Item" class to use for making new items.
52
     *
53
     * @var string|null
54
     */
55
    private $itemClass;
56
57
    /**
58
     * Create a cache pool builder.
59
     *
60
     * @param array $data The cache builder dependencies.
61
     */
62
    public function __construct(array $data)
63
    {
64
        $this->setDrivers($data['drivers']);
65
66
        if (isset($data['logger'])) {
67
            $this->setLogger($data['logger']);
68
        }
69
70
        if (isset($data['namespace'])) {
71
            $this->setNamespace($data['namespace']);
72
        }
73
74
        if (isset($data['pool_class'])) {
75
            $this->setPoolClass($data['pool_class']);
76
        }
77
78
        if (isset($data['item_class'])) {
79
            $this->setItemClass($data['item_class']);
80
        }
81
    }
82
83
    /**
84
     * Invoke a new cache pool.
85
     *
86
     * @param  mixed $cacheDriver The name of a registered cache driver,
87
     *     the class name or instance of a {@see DriverInterface cache driver}.
88
     *     An array may be used to designate fallback drivers.
89
     * @param  mixed $poolOptions Optional settings for the new pool.
90
     * @return PoolInterface
91
     */
92
    public function __invoke($cacheDriver, $poolOptions = null)
93
    {
94
        return $this->build($cacheDriver, $poolOptions);
95
    }
96
97
    /**
98
     * Create a new cache pool.
99
     *
100
     * @param  mixed $cacheDriver The name of a registered cache driver,
101
     *     the class name or instance of a {@see DriverInterface cache driver}.
102
     *     An array may be used to designate fallback drivers.
103
     * @param  mixed $poolOptions Optional settings for the new pool.
104
     *     If a string is specified, it is used as the namespace for the new pool.
105
     *     If an array is specified, it is assumed to be associative and is merged with the default settings.
106
     *     Otherwise, the default settings are used.
107
     * @return PoolInterface
108
     */
109
    public function build($cacheDriver, $poolOptions = null)
110
    {
111
        if (!($cacheDriver instanceof DriverInterface)) {
112
            $cacheDriver = $this->resolveDriver($cacheDriver);
113
        }
114
115
        $poolOptions  = $this->parsePoolOptions($poolOptions);
116
        $poolInstance = new $poolOptions['pool_class']($cacheDriver);
117
118
        $this->applyPoolOptions($poolInstance, $poolOptions);
119
120
        return $poolInstance;
121
    }
122
123
    /**
124
     * Prepare any pool options for the new pool object.
125
     *
126
     * @param  mixed $options Settings for the new pool.
127
     * @return array
128
     */
129
    private function parsePoolOptions($options)
130
    {
131
        $defaults = [
132
            'pool_class' => $this->poolClass,
133
            'item_class' => $this->itemClass,
134
            'namespace'  => $this->namespace,
135
            'logger'     => $this->logger,
136
        ];
137
138
        if ($options === null) {
139
            return $defaults;
140
        }
141
142
        if (is_string($options)) {
143
            $options = [
144
                'namespace' => $options
145
            ];
146
        }
147
148
        if (!is_array($options)) {
149
            return $defaults;
150
        }
151
152
        return array_replace($defaults, $options);
153
    }
154
155
    /**
156
     * Apply any pool options on the new pool object.
157
     *
158
     * @param  PoolInterface $pool    The new pool.
159
     * @param  array         $options Settings for the new pool.
160
     * @return void
161
     */
162
    private function applyPoolOptions(PoolInterface $pool, array $options)
163
    {
164
        if (isset($options['logger'])) {
165
            $pool->setLogger($options['logger']);
166
        }
167
168
        if (isset($options['item_class'])) {
169
            $pool->setItemClass($options['item_class']);
170
        }
171
172
        if (isset($options['namespace'])) {
173
            $pool->setNamespace($options['namespace']);
174
        }
175
    }
176
177
    /**
178
     * Resolve one or many cache drivers, if available.
179
     *
180
     * @param  mixed $driver The name of a registered cache driver,
181
     *     the class name or instance of a {@see DriverInterface cache driver}.
182
     *     An array may be used to designate fallback drivers.
183
     * @throws InvalidArgumentException When an array of drivers cannot be resolved.
184
     * @return DriverInterface
185
     */
186
    private function resolveDriver($driver)
187
    {
188
        if ($this->isIterable($driver)) {
189
            foreach ($driver as $drv) {
190
                try {
191
                    return $this->resolveOneDriver($drv);
192
                } catch (InvalidArgumentException $e) {
193
                    continue;
194
                }
195
            }
196
197
            throw new InvalidArgumentException(
198
                'Drivers cannot be resolved'
199
            );
200
        }
201
202
        return $this->resolveOneDriver($driver);
203
    }
204
205
    /**
206
     * Resolve the given cache driver, if available.
207
     *
208
     * @param  mixed $driver The name of a registered cache driver,
209
     *     the class name or instance of a {@see DriverInterface cache driver}.
210
     * @throws InvalidArgumentException When passed an invalid or nonexistant driver name, class name, or object.
211
     * @return DriverInterface
212
     */
213
    private function resolveOneDriver($driver)
214
    {
215
        if (empty($driver)) {
216
            throw new InvalidArgumentException(
217
                'Driver is empty'
218
            );
219
        }
220
221
        if (is_object($driver)) {
222
            if ($driver instanceof DriverInterface) {
223
                return $driver;
224
            } else {
225
                throw new InvalidArgumentException(sprintf(
226
                    'Driver class %s must implement %s',
227
                    get_class($driver),
228
                    DriverInterface::class
229
                ));
230
            }
231
        }
232
233
        $name = $driver;
234
        if (isset($this->drivers[$name])) {
235
            $driver = $this->drivers[$name];
236
237
            if (empty($driver)) {
238
                throw new InvalidArgumentException(
239
                    sprintf('Driver "%s" does not exist', $name)
240
                );
241
            }
242
243
            if (is_object($driver)) {
244
                if ($driver instanceof DriverInterface) {
245
                    return $driver;
246
                } else {
247
                    throw new InvalidArgumentException(sprintf(
248
                        'Driver "%s": Class %s must implement %s',
249
                        $name,
250
                        get_class($driver),
251
                        DriverInterface::class
252
                    ));
253
                }
254
            }
255
        }
256
257
        if (is_a($driver, DriverInterface::class, true)) {
258
            return new $driver();
259
        }
260
261
        throw new InvalidArgumentException(
262
            sprintf('Driver "%s" cannot be resolved', $name)
263
        );
264
    }
265
266
    /**
267
     * Sets the collection of available cache drivers.
268
     *
269
     * @param  ArrayAccess|array $drivers The driver list used to create cache drivers.
270
     * @throws InvalidArgumentException If the drivers list is invalid.
271
     * @return void
272
     */
273
    private function setDrivers($drivers)
274
    {
275
        if ($this->isAccessible($drivers)) {
276
            $this->drivers = $drivers;
277
        } else {
278
            throw new InvalidArgumentException(
279
                'Driver list must be an accessible array'
280
            );
281
        }
282
    }
283
284
    /**
285
     * Sets the specific PSR logging client to enable the tracking of errors.
286
     *
287
     * @param  \Psr\Log\LoggerInterface $logger A PSR-3 logger.
288
     * @throws InvalidArgumentException If the logger is invalid PSR-3 client.
289
     * @return void
290
     */
291
    private function setLogger($logger)
292
    {
293
        $psr = 'Psr\\Log\\LoggerInterface';
294
        if (!is_a($logger, $psr)) {
295
            throw new InvalidArgumentException(
296
                sprintf('Expected an instance of %s', $psr)
297
            );
298
        }
299
300
        $this->logger = $logger;
301
    }
302
303
    /**
304
     * Sets the specific Pool "namespace" assigned by the cache builder.
305
     *
306
     * Using this function developers can segment cache items.
307
     *
308
     * @param  string $namespace The pool namespace.
309
     * @throws InvalidArgumentException If the namespaces is invalid.
310
     * @return void
311
     */
312
    private function setNamespace($namespace)
313
    {
314
        if (!ctype_alnum($namespace)) {
315
            throw new InvalidArgumentException(
316
                'Namespace must be alphanumeric'
317
            );
318
        }
319
320
        $this->namespace = $namespace;
321
    }
322
323
    /**
324
     * Sets the specific Pool class generated by the cache builder.
325
     *
326
     * Using this function developers can have the builder generate custom Pool objects.
327
     *
328
     * @param  string $class The pool class name.
329
     * @throws InvalidArgumentException When passed an invalid or nonexistant class.
330
     * @return void
331
     */
332
    private function setPoolClass($class)
333
    {
334
        if (!class_exists($class)) {
335
            throw new InvalidArgumentException(
336
                sprintf('Pool class %s does not exist', $class)
337
            );
338
        }
339
340
        $interfaces = class_implements($class, true);
341
342
        if (!in_array(PoolInterface::class, $interfaces)) {
343
            throw new InvalidArgumentException(sprintf(
344
                'Pool class %s must inherit from %s',
345
                $class,
346
                PoolInterface::class
347
            ));
348
        }
349
350
        $this->poolClass = $class;
351
    }
352
353
    /**
354
     * Changes the specific Item class generated by the Pool objects.
355
     *
356
     * Using this function developers can have the pool class generate custom Item objects.
357
     *
358
     * @param  string $class The item class name.
359
     * @throws InvalidArgumentException When passed an invalid or nonexistant class.
360
     * @return void
361
     */
362
    private function setItemClass($class)
363
    {
364
        if (!class_exists($class)) {
365
            throw new InvalidArgumentException(
366
                sprintf('Item class %s does not exist', $class)
367
            );
368
        }
369
370
        $interfaces = class_implements($class, true);
371
372
        if (!in_array(ItemInterface::class, $interfaces)) {
373
            throw new InvalidArgumentException(sprintf(
374
                'Item class %s must inherit from %s',
375
                $class,
376
                ItemInterface::class
377
            ));
378
        }
379
380
        $this->itemClass = $class;
381
    }
382
383
    /**
384
     * Determine if the variable is an iterable value.
385
     *
386
     * @param  mixed $var The value to check
387
     * @return boolean TRUE if $var is iterable, FALSE otherwise.
388
     */
389
    private function isIterable($var)
390
    {
391
        return is_array($var) || ($var instanceof Traversable);
392
    }
393
394
    /**
395
     * Determine if the variable is array accessible.
396
     *
397
     * @param  mixed $var The value to check
398
     * @return boolean TRUE if $var is an array or accessible like an array, FALSE otherwise.
399
     */
400
    private function isAccessible($var)
401
    {
402
        return is_array($var) || ($var instanceof ArrayAccess);
403
    }
404
}
405