Completed
Push — master ( 888ea9...2d4347 )
by Tony
02:35
created

ClassConfig::createClass()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 52
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 52
rs 7.2396
c 0
b 0
f 0
cc 7
eloc 32
nc 7
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace ClassConfig;
4
5
use ClassConfig\Annotation\Config;
6
use ClassConfig\Annotation\ConfigArray;
7
use ClassConfig\Annotation\ConfigBoolean;
8
use ClassConfig\Annotation\ConfigEntryInterface;
9
use ClassConfig\Annotation\ConfigFloat;
10
use ClassConfig\Annotation\ConfigInteger;
11
use ClassConfig\Annotation\ConfigObject;
12
use ClassConfig\Annotation\ConfigString;
13
use ClassConfig\Exceptions\ClassConfigAlreadyRegisteredException;
14
use ClassConfig\Exceptions\ClassConfigNotRegisteredException;
15
use Doctrine\Common\Annotations\AnnotationReader;
16
17
/**
18
 * Class ClassConfig
19
 * @package ClassConfig
20
 */
21
class ClassConfig
22
{
23
    /**
24
     * Config files are always re-generated when requested.
25
     */
26
    const CACHE_NEVER       = 0;
27
28
    /**
29
     * Config files are re-generated if older than the source (filemtime).
30
     */
31
    const CACHE_VALIDATE    = 1;
32
33
    /**
34
     * Config files are only generated once (or after being manually deleted).
35
     */
36
    const CACHE_ALWAYS      = 2;
37
38
    /**
39
     * Flag to determine whether the register() method has been called.
40
     *
41
     * @var bool
42
     */
43
    protected static $registered = false;
44
45
    /**
46
     * In-memory cache for the annotation reader.
47
     *
48
     * @var AnnotationReader
49
     */
50
    protected static $annotationReader;
51
52
    /**
53
     * The registered path to a cache folder.
54
     *
55
     * @var string
56
     */
57
    protected static $cachePath;
58
59
    /**
60
     * The registered caching strategy.
61
     *
62
     * @var int
63
     */
64
    protected static $cacheStrategy;
65
66
    /**
67
     * The registered class namespace for config classes.
68
     * This will be used as prefix to source classes.
69
     *
70
     * @var string
71
     */
72
    protected static $classNamespace;
73
74
    /**
75
     * A classmap of generated config files for the autoloader.
76
     *
77
     * @var string[]
78
     */
79
    protected static $classmap;
80
81
    /**
82
     * A flag to determine that the classmap is dirty and should be written to the filesystem.
83
     *
84
     * @var bool
85
     */
86
    protected static $classmapDirty = false;
87
88
    /**
89
     * @param string $path
90
     */
91
    protected static function createDirectories(string $path)
92
    {
93
        if (!is_dir($path)) {
94
            static::createDirectories(dirname($path));
95
            mkdir($path);
96
        }
97
    }
98
99
    /**
100
     * Lazy getter for the annotation reader.
101
     *
102
     * @return AnnotationReader
103
     * @throws \Doctrine\Common\Annotations\AnnotationException
104
     */
105
    protected static function getAnnotationReader(): AnnotationReader
106
    {
107
        if (!isset(static::$annotationReader)) {
108
            static::$annotationReader = new AnnotationReader();
109
        }
110
        return static::$annotationReader;
111
    }
112
113
    /**
114
     * Getter for the registered cache path.
115
     * Throws a ClassConfigNotRegisteredException if register() wasn't called prior.
116
     *
117
     * @return string
118
     * @throws ClassConfigNotRegisteredException
119
     */
120
    protected static function getCachePath(): string
121
    {
122
        if (!static::$registered) {
123
            throw new ClassConfigNotRegisteredException();
124
        }
125
        return static::$cachePath;
126
    }
127
128
    /**
129
     * Getter for the registered class namespace.
130
     * Throws a ClassConfigNotRegisteredException if register() wasn't called prior.
131
     *
132
     * @return string
133
     * @throws ClassConfigNotRegisteredException
134
     */
135
    protected static function getClassNamespace(): string
136
    {
137
        if (!static::$registered) {
138
            throw new ClassConfigNotRegisteredException();
139
        }
140
        return self::$classNamespace;
141
    }
142
143
    /**
144
     * @param Config $annotation
145
     * @param string $className
146
     * @param string $classNamespace
147
     * @param string $targetClassNamespace
148
     * @param string $targetCanonicalClassName
149
     * @param int $time
150
     * @param int $subClassIteration
151
     * @return string
152
     */
153
    protected static function generate(
154
        Config $annotation,
155
        string $className,
156
        string $classNamespace,
157
        string $targetClassNamespace,
158
        string $targetCanonicalClassName,
159
        int $time,
160
        int &$subClassIteration = 0
161
    ): string {
162
        // a suffix of _0, _1, _2 etc. is added to generated sub-classes
163
        $suffix = 0 < $subClassIteration ? '_' . $subClassIteration : '';
164
165
        $effectiveClassName = $className . $suffix;
166
        $effectiveTargetCanonicalClassName = $targetCanonicalClassName . $suffix;
167
168
        $generator = new ClassGenerator($annotation, $effectiveClassName, $targetClassNamespace);
169
170
        /**
171
         * @var string $key
172
         * @var ConfigEntryInterface $entry
173
         */
174
        foreach ($annotation->value as $key => $entry) {
175
            switch (true) {
176
                case $entry instanceof ConfigString:
177
                case $entry instanceof ConfigInteger:
178
                case $entry instanceof ConfigFloat:
179
                case $entry instanceof ConfigBoolean:
180
                case $entry instanceof ConfigObject:
181
                    $type = $entry->getType();
182
                    $generator
183
                        ->generateProperty($key, $type, isset($entry->default) ? $entry->default : null)
184
                        ->generateGet($key, $type)
185
                        ->generateSet($key, $type)
186
                        ->generateIsset($key)
187
                        ->generateUnset($key);
188
                    break;
189
190
                case $entry instanceof ConfigArray:
191
                    $type = $entry->value->getType();
192
                    $generator
193
                        ->generateProperty($key, $type . '[]')
194
                        ->generateArrayGet($key, $type . '[]')
195
                        ->generateArraySet($key, $type . '[]')
196
                        ->generateArrayGetAt($key, $type)
197
                        ->generateArraySetAt($key, $type)
198
                        ->generateArrayClear($key)
199
                        ->generateArrayPush($key, $type)
200
                        ->generateArrayUnshift($key, $type)
201
                        ->generateArrayPop($key, $type)
202
                        ->generateArrayShift($key, $type)
203
                        ->generateIsset($key)
204
                        ->generateUnset($key);
205
                    break;
206
207
                case $entry instanceof Config:
208
                    $subClassIteration++;
209
                    $entryCanonicalClassName = static::generate(
210
                        $entry,
211
                        $className,
212
                        $classNamespace,
213
                        $targetClassNamespace,
214
                        $targetCanonicalClassName,
215
                        $time,
216
                        $subClassIteration
217
                    );
218
                    $generator
219
                        ->generateProperty($key, $entryCanonicalClassName)
220
                        ->generateConfigGet($key, $entryCanonicalClassName)
221
                        ->generateConfigSet($key)
222
                        ->generateConfigIsset($key)
223
                        ->generateConfigUnset($key);
224
                    break;
225
226
                default:
227
                    throw new \RuntimeException(sprintf(
228
                        'Invalid or unsupported configuration entry type: "%s".',
229
                        get_class($entry)
230
                    ));
231
            }
232
        }
233
234
        $generator
235
            ->generateMagicGet()
236
            ->generateMagicSet()
237
            ->generateMagicIsset()
238
            ->generateMagicUnset();
239
240
        $targetPath = static::getCachePath() . '/CC__' . str_replace('\\', '_', $classNamespace) . '_' .
241
            $effectiveClassName . '.php';
242
243
        file_put_contents($targetPath, (string) $generator);
244
        touch($targetPath, $time);
245
        clearstatcache();
246
247
        static::$classmapDirty = true;
248
        static::$classmap[$effectiveTargetCanonicalClassName] = $targetPath;
249
250
        return $effectiveTargetCanonicalClassName;
251
    }
252
253
    /**
254
     * Register the environment.
255
     * This must be called once and only once (on each request) before working with the library.
256
     *
257
     * @param string $cachePath
258
     * @param int $cacheStrategy
259
     * @param string $classNamespace
260
     */
261
    public static function register(
262
        string $cachePath,
263
        int $cacheStrategy = self::CACHE_VALIDATE,
264
        string $classNamespace = 'ClassConfig\Cache'
265
    ) {
266
        if (static::$registered) {
267
            throw new ClassConfigAlreadyRegisteredException();
268
        }
269
270
        // ensure the cache folder exists
271
        static::createDirectories($cachePath);
272
273
        static::$registered = true;
274
        static::$cachePath = $cachePath;
275
        static::$cacheStrategy = $cacheStrategy;
276
        static::$classNamespace = $classNamespace;
277
278
        // load the classmap
279
        $classmapPath = $cachePath . '/classmap.php';
280
        if (static::CACHE_NEVER === $cacheStrategy) {
281
            static::$classmap = [];
282
        } else {
283
            static::$classmap = is_file($classmapPath) ? include $classmapPath : [];
284
        }
285
286
        // flush the classmap if dirty on shutdown
287
        register_shutdown_function(function () use ($classmapPath) {
288
            if (static::$classmapDirty) {
289
                file_put_contents(
290
                    $classmapPath,
291
                    '<?php' . PHP_EOL . PHP_EOL .
292
                    '/**' . PHP_EOL .
293
                    ' * THIS IS AN AUTOMATICALLY GENERATED FILE, PLEASE DO NOT MODIFY IT.' . PHP_EOL .
294
                    ' * YOU MAY SAFELY DELETE THE FILE AS IT WILL BE REGENERATED ON-DEMAND.' . PHP_EOL .
295
                    ' */' . PHP_EOL .
296
                    'return ' . var_export(static::$classmap, true) . ';'
297
                );
298
            }
299
        });
300
301
        // autoload classes from the classmap
302
        spl_autoload_register(function (string $class) {
303
            if (isset(static::$classmap[$class])) {
304
                include static::$classmap[$class];
305
            }
306
        });
307
    }
308
309
    /**
310
     * @param string $canonicalClassName
311
     * @return string
312
     * @throws \Doctrine\Common\Annotations\AnnotationException
313
     * @throws \ReflectionException
314
     * @throws ClassConfigNotRegisteredException
315
     */
316
    public static function createClass(string $canonicalClassName): string
317
    {
318
        $classNamespace = dirname($canonicalClassName);
319
        $className = basename($canonicalClassName);
320
321
        $targetClassNamespace = static::getClassNamespace() . '\\' . $classNamespace;
322
        $targetCanonicalClassName = $targetClassNamespace . '\\' . $className;
323
324
        switch (static::$cacheStrategy) {
325
            case static::CACHE_NEVER:
326
                // always regenerate
327
                $time = time();
328
                break;
329
330
            case static::CACHE_ALWAYS:
331
                // only generate if not in the classmap
332
                if (isset(static::$classmap[$targetCanonicalClassName])) {
333
                    return $targetCanonicalClassName;
334
                }
335
336
                $time = time();
337
                break;
338
339
            case static::CACHE_VALIDATE:
340
            default:
341
                // validate by last modified time
342
                $time = filemtime((new \ReflectionClass($canonicalClassName))->getFileName());
343
344
                if (
345
                    isset(static::$classmap[$targetCanonicalClassName]) &&
346
                    @filemtime(static::$classmap[$targetCanonicalClassName]) === $time
347
                ) {
348
                    return $targetCanonicalClassName;
349
                }
350
                break;
351
        }
352
353
        /** @var Config $annotation */
354
        $annotation = static::getAnnotationReader()->getClassAnnotation(
355
            new \ReflectionClass($canonicalClassName),
356
            Config::class
357
        );
358
359
        return static::generate(
360
            $annotation,
361
            $className,
362
            $classNamespace,
363
            $targetClassNamespace,
364
            $targetCanonicalClassName,
365
            $time
366
        );
367
    }
368
369
    /**
370
     * @param string $class
371
     * @return AbstractConfig
372
     * @throws \Doctrine\Common\Annotations\AnnotationException
373
     * @throws \ReflectionException
374
     * @throws ClassConfigNotRegisteredException
375
     */
376
    public static function createInstance(string $class): AbstractConfig
377
    {
378
        $canonicalClassName = static::createClass($class);
379
        return new $canonicalClassName;
380
    }
381
}