Completed
Push — master ( bd8648...1004b2 )
by Tony
01:35
created

ClassConfig::generate()   D

Complexity

Conditions 13
Paths 20

Size

Total Lines 106
Code Lines 81

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 106
rs 4.9922
cc 13
eloc 81
nc 20
nop 8

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace ClassConfig;
4
5
use ClassConfig\Annotation\Config;
6
use ClassConfig\Annotation\ConfigList;
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
     * @param string $path
76
     */
77
    protected static function createDirectories(string $path)
78
    {
79
        if (!is_dir($path)) {
80
            static::createDirectories(dirname($path));
81
            mkdir($path);
82
        }
83
    }
84
85
    /**
86
     * Lazy getter for the annotation reader.
87
     *
88
     * @return AnnotationReader
89
     * @throws \Doctrine\Common\Annotations\AnnotationException
90
     */
91
    protected static function getAnnotationReader(): AnnotationReader
92
    {
93
        if (!isset(static::$annotationReader)) {
94
            static::$annotationReader = new AnnotationReader();
95
        }
96
        return static::$annotationReader;
97
    }
98
99
    /**
100
     * Getter for the registered cache path.
101
     * Throws a ClassConfigNotRegisteredException if register() wasn't called prior.
102
     *
103
     * @return string
104
     * @throws ClassConfigNotRegisteredException
105
     */
106
    protected static function getCachePath(): string
107
    {
108
        if (!static::$registered) {
109
            throw new ClassConfigNotRegisteredException();
110
        }
111
        return static::$cachePath;
112
    }
113
114
    /**
115
     * Getter for the registered class namespace.
116
     * Throws a ClassConfigNotRegisteredException if register() wasn't called prior.
117
     *
118
     * @return string
119
     * @throws ClassConfigNotRegisteredException
120
     */
121
    protected static function getClassNamespace(): string
122
    {
123
        if (!static::$registered) {
124
            throw new ClassConfigNotRegisteredException();
125
        }
126
        return self::$classNamespace;
127
    }
128
129
    /**
130
     * @param Config $annotation
131
     * @param string $className
132
     * @param string $classNamespace
133
     * @param string $canonicalClassName
134
     * @param string $targetClassNamespace
135
     * @param string $targetCanonicalClassName
136
     * @param int $time
137
     * @param int $subClassIteration
138
     * @return string
139
     */
140
    protected static function generate(
141
        Config $annotation,
142
        string $className,
143
        string $classNamespace,
144
        string $canonicalClassName,
145
        string $targetClassNamespace,
146
        string $targetCanonicalClassName,
147
        int $time,
148
        int &$subClassIteration = 0
149
    ): string {
150
        // a suffix of _0, _1, _2 etc. is added to generated sub-classes
151
        $suffix = 0 < $subClassIteration ? '_' . $subClassIteration : '';
152
153
        $effectiveClassName = $className . $suffix;
154
        $effectiveTargetCanonicalClassName = $targetCanonicalClassName . $suffix;
155
156
        $generator = new ClassGenerator($annotation, $effectiveClassName, $targetClassNamespace, $canonicalClassName);
157
158
        /**
159
         * @var string $key
160
         * @var ConfigEntryInterface $entry
161
         */
162
        foreach ($annotation->value as $key => $entry) {
163
            switch (true) {
164
                case $entry instanceof ConfigString:
165
                case $entry instanceof ConfigInteger:
166
                case $entry instanceof ConfigFloat:
167
                case $entry instanceof ConfigBoolean:
168
                case $entry instanceof ConfigObject:
169
                    $type = $entry->getType();
170
                    $generator
171
                        ->generateProperty($key, $type, isset($entry->default) ? $entry->default : null)
172
                        ->generateGet($key, $type)
173
                        ->generateSet($key, $type)
174
                        ->generateIsset($key)
175
                        ->generateUnset($key);
176
                    break;
177
178
                case $entry instanceof ConfigList:
179
                    $type = isset($entry->value) ? $entry->value->getType() : 'mixed';
180
                    $generator
181
                        ->generateProperty($key, $type . '[]', isset($entry->default) ?
0 ignored issues
show
Bug introduced by
It seems like isset($entry->default) ?...$entry->default) : null can also be of type array; however, ClassConfig\ClassGenerator::generateProperty() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
182
                            array_values($entry->default) : null)
183
                        ->generateGet($key, $type . '[]')
184
                        ->generateListSet($key, $type . '[]')
185
                        ->generateListGetAt($key, $type)
186
                        ->generateListSetAt($key, $type)
187
                        ->generateListPush($key, $type)
188
                        ->generateListUnshift($key, $type)
189
                        ->generateArrayPop($key, $type)
190
                        ->generateArrayShift($key, $type)
191
                        ->generateArrayClear($key)
192
                        ->generateIsset($key)
193
                        ->generateUnset($key);
194
                    break;
195
196
                case $entry instanceof Config:
197
                    $subClassIteration++;
198
                    $entryCanonicalClassName = static::generate(
199
                        $entry,
200
                        $className,
201
                        $classNamespace,
202
                        $canonicalClassName,
203
                        $targetClassNamespace,
204
                        $targetCanonicalClassName,
205
                        $time,
206
                        $subClassIteration
207
                    );
208
                    $generator
209
                        ->generateProperty($key, $entryCanonicalClassName)
210
                        ->generateConfigGet($key, $entryCanonicalClassName)
211
                        ->generateConfigSet($key)
212
                        ->generateConfigIsset($key)
213
                        ->generateConfigUnset($key);
214
                    break;
215
216
                default:
217
                    throw new \RuntimeException(sprintf(
218
                        'Invalid or unsupported configuration entry type: "%s".',
219
                        get_class($entry)
220
                    ));
221
            }
222
        }
223
224
        $generator
225
            ->generateMagicGet()
226
            ->generateMagicSet()
227
            ->generateMagicIsset()
228
            ->generateMagicUnset();
229
230
        $targetDir = static::getCachePath() . '/' . str_replace('\\', '/', $classNamespace);
231
        $targetPath = $targetDir . '/' . $effectiveClassName . '.php';
232
233
        static::createDirectories($targetDir);
234
235
        file_put_contents($targetPath, (string) $generator);
236
        touch($targetPath, $time);
237
        clearstatcache();
238
239
        // as optimization measure composer's autoloader remembers that a class does not exist on the first requested
240
        // it will refuse to autoload the class even if it subsequently becomes available
241
        // for this reason we need to manually load the newly generated class
242
        include_once $targetPath;
243
244
        return $effectiveTargetCanonicalClassName;
245
    }
246
247
    /**
248
     * Register the environment.
249
     * This must be called once and only once (on each request) before working with the library.
250
     *
251
     * @param string $cachePath
252
     * @param int $cacheStrategy
253
     * @param string $classNamespace
254
     */
255
    public static function register(
256
        string $cachePath,
257
        int $cacheStrategy = self::CACHE_VALIDATE,
258
        string $classNamespace = 'ClassConfig\Cache'
259
    ) {
260
        if (static::$registered) {
261
            throw new ClassConfigAlreadyRegisteredException();
262
        }
263
264
        // ensure the cache folder exists
265
        static::createDirectories($cachePath);
266
267
        static::$registered = true;
268
        static::$cachePath = $cachePath;
269
        static::$cacheStrategy = $cacheStrategy;
270
        static::$classNamespace = $classNamespace;
271
    }
272
273
    /**
274
     * @param string $canonicalClassName
275
     * @return string
276
     * @throws \Doctrine\Common\Annotations\AnnotationException
277
     * @throws \ReflectionException
278
     * @throws ClassConfigNotRegisteredException
279
     */
280
    public static function createClass(string $canonicalClassName): string
281
    {
282
        $parts = explode('\\', $canonicalClassName);
283
284
        $className = $parts[count($parts) - 1];
285
        $classNamespace = implode('\\', array_slice($parts, 0, -1));
286
287
        $targetClassNamespace = static::getClassNamespace() . '\\' . $classNamespace;
288
        $targetCanonicalClassName = $targetClassNamespace . '\\' . $className;
289
290
        switch (static::$cacheStrategy) {
291
            case static::CACHE_NEVER:
292
                // always regenerate
293
                $time = time();
294
                break;
295
296
            case static::CACHE_ALWAYS:
297
                // only generate if class does not exist
298
                if (class_exists($targetCanonicalClassName)) {
299
                    return $targetCanonicalClassName;
300
                }
301
                $time = time();
302
                break;
303
304
            case static::CACHE_VALIDATE:
305
            default:
306
                // validate by last modified time
307
                $time = filemtime((new \ReflectionClass($canonicalClassName))->getFileName());
308
                if (
309
                    class_exists($targetCanonicalClassName) &&
310
                    filemtime((new \ReflectionClass($canonicalClassName))->getFileName()) ===
311
                    filemtime((new \ReflectionClass($targetCanonicalClassName))->getFileName())
312
                ) {
313
                    return $targetCanonicalClassName;
314
                }
315
                break;
316
        }
317
318
        /** @var Config $annotation */
319
        $annotation = static::getAnnotationReader()->getClassAnnotation(
320
            new \ReflectionClass($canonicalClassName),
321
            Config::class
322
        );
323
324
        return static::generate(
325
            $annotation,
326
            $className,
327
            $classNamespace,
328
            $canonicalClassName,
329
            $targetClassNamespace,
330
            $targetCanonicalClassName,
331
            $time
332
        );
333
    }
334
335
    /**
336
     * @param string $class
337
     * @param object $owner
338
     * @return AbstractConfig
339
     * @throws \Doctrine\Common\Annotations\AnnotationException
340
     * @throws \ReflectionException
341
     * @throws ClassConfigNotRegisteredException
342
     */
343
    public static function createInstance(string $class, $owner): AbstractConfig
344
    {
345
        $canonicalClassName = static::createClass($class);
346
        return new $canonicalClassName($owner);
347
    }
348
}