DgClassLoader   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 369
Duplicated Lines 0 %

Test Coverage

Coverage 4.42%

Importance

Changes 0
Metric Value
eloc 84
dl 0
loc 369
ccs 5
cts 113
cp 0.0442
rs 8.8798
c 0
b 0
f 0
wmc 44

15 Methods

Rating   Name   Duplication   Size   Complexity  
B createDefinitions() 0 60 8
A getAspectRegister() 0 3 1
A loadClassProductionNoOmit() 0 10 2
A fillCache() 0 4 1
A getStructureMap() 0 3 1
A getConfig() 0 3 1
A init() 0 22 2
A createCache() 0 12 4
A getCacheDir() 0 3 1
B loadClass() 0 34 11
A __construct() 0 9 1
A injectAspectRegister() 0 3 1
A refillCache() 0 13 4
A register() 0 15 3
A unregister() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like DgClassLoader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DgClassLoader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * \AppserverIo\Appserver\Core\DgClassLoader
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Bernhard Wick <[email protected]>
15
 * @copyright 2015 TechDivision GmbH - <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/appserver-io/appserver
18
 * @link      http://www.appserver.io
19
 */
20
21
namespace AppserverIo\Appserver\Core;
22
23
use AppserverIo\Doppelgaenger\Config;
24
use AppserverIo\Doppelgaenger\CacheMap;
25
use AppserverIo\Doppelgaenger\Generator;
26
use AppserverIo\Doppelgaenger\AspectRegister;
27
use AppserverIo\Doppelgaenger\Entities\Definitions\Structure;
28
use AppserverIo\Appserver\Core\Interfaces\ClassLoaderInterface;
29
30
/**
31
 * This class is used to delegate to doppelgaenger's autoloader. This is needed as our
32
 * multi-threaded environment would not allow any out-of-the-box code generation in an
33
 * on-the-fly manner.
34
 *
35
 * @author    Bernhard Wick <[email protected]>
36
 * @copyright 2015 TechDivision GmbH <[email protected]>
37
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
38
 * @link      https://github.com/appserver-io/appserver
39
 * @link      http://www.appserver.io
40
 *
41
 * @property \AppserverIo\Doppelgaenger\AspectRegister         $aspectRegister Register for all aspects which have to be taken into account
42
 * @property boolean                                           $autoloaderOmit Whether or not certain namespaces get omitted
43
 * @property string                                            $cacheDir       Directory where cached definitions reside
44
 * @property boolean                                           $cacheIsReady   Whether or not the cache is ready for usage
45
 * @property \AppserverIo\Doppelgaenger\Config                 $config         THe configuration for this classloader
46
 * @property string                                            $environment    Environment, either 'production' or 'development'
47
 * @property \AppserverIo\Appserver\Core\StackableStructureMap $structureMap   Map which holds all known structures
48
 */
49
class DgClassLoader extends \Stackable implements ClassLoaderInterface
50
{
51
52
    /**
53
     * The amount of structures we will generate per thread
54
     *
55
     * @const int GENERATOR_STACK_COUNT
56
     */
57
    const GENERATOR_STACK_COUNT = 5;
58
59
    /**
60
     * The unique class loader identifier.
61
     *
62
     * @var string
63
     */
64
    const IDENTIFIER = 'doppelgaenger';
65
66
    /**
67
     * The name of our autoload method.
68
     *
69
     * @const string OUR_LOADER
70
     */
71
    const OUR_LOADER = 'loadClass';
72
73
    /**
74
     * The name of our simple/quick autoload method.
75
     *
76
     * @const string OUR_LOADER_PROD_NO_OMIT
77
     */
78
    const OUR_LOADER_PROD_NO_OMIT = 'loadClassProductionNoOmit';
79
80
    /**
81
     * Default constructor
82
     *
83
     * We will do all the necessary work to load without any further hassle.
84
     * Will check if there is content in the cache directory.
85
     * If not we will parse anew.
86
     *
87
     * @param \AppserverIo\Doppelgaenger\Config|null $config An already existing config instance
88
     */
89 1
    public function __construct(Config $config)
90
    {
91
92
        // set the configuration
93 1
        $this->config = $config;
94
95
        // initialize the variables
96 1
        $this->cacheIsReady = false;
97 1
        $this->autoloaderOmit = false;
98 1
    }
99
100
    /**
101
     * Initializes the autoloader with the values from the configuration
102
     * and creates and fills the structure map instance.
103
     *
104
     * @param \AppserverIo\Appserver\Core\StackableStructureMap $structureMap   Structure map to be used
105
     * @param \AppserverIo\Doppelgaenger\AspectRegister         $aspectRegister The register for all found aspects
106
     *
107
     * @return void
108
     */
109
    public function init(StackableStructureMap $structureMap, AspectRegister $aspectRegister)
110
    {
111
112
        // collect the passed instances
113
        $this->structureMap = $structureMap;
114
        $this->aspectRegister = $aspectRegister;
115
116
        // load the configuration
117
        $config = $this->getConfig();
118
119
        // pre-load the configuration values
120
        $this->cacheDir =  $config->getValue('cache/dir');
121
        $this->environment = $config->getValue('environment');
122
123
        // we want to know if there are namespaces to be omitted from autoloading
124
        if ($config->hasValue('autoloader/omit')) {
125
            $autoloadingOmitted = $config->getValue('autoloader/omit');
126
            $this->autoloaderOmit = !empty($autoloadingOmitted);
127
        }
128
129
        // we now have a structure map instance, so fill it initially
130
        $this->getStructureMap()->fill();
131
    }
132
133
134
135
    /**
136
     * Returns the cache directory.
137
     *
138
     * @return string The cache directory
139
     */
140
    public function getCacheDir()
141
    {
142
        return $this->cacheDir;
143
    }
144
145
    /**
146
     * Will start the generation of the cache based on the known structures
147
     *
148
     * @return null
149
     */
150
    public function createCache()
151
    {
152
        // Check if there are files in the cache.
153
        // If not we will fill the cache, if there are we will check if there have been any changes to the enforced dirs
154
        $fileIterator = new \FilesystemIterator($this->getConfig()->getValue('cache/dir'), \FilesystemIterator::SKIP_DOTS);
155
        if (iterator_count($fileIterator) <= 1 || $this->getConfig()->getValue('environment') === 'development') {
156
            // Fill the cache
157
            $this->fillCache();
158
159
        } else {
160
            if (!$this->structureMap->isRecent()) {
161
                $this->refillCache();
162
            }
163
        }
164
    }
165
166
    /**
167
     * Creates the definitions by given structure map
168
     *
169
     * @return bool
170
     */
171
    protected function createDefinitions()
172
    {
173
        // Get all the structures we found
174
        $structures = $this->structureMap->getEntries();
175
176
        // We will need a CacheMap instance which we can pass to the generator
177
        // We need the caching configuration
178
        $cacheMap = new CacheMap($this->getConfig()->getValue('cache/dir'), array(), $this->getConfig());
179
180
        // We need a generator so we can create our proxies initially
181
        $generator = new Generator($this->structureMap, $cacheMap, $this->getConfig(), $this->getAspectRegister());
182
183
        // Iterate over all found structures and generate their proxies, but ignore the ones with omitted
184
        // namespaces
185
        $omittedNamespaces = array();
186
        if ($this->getConfig()->hasValue('autoloader/omit')) {
187
            $omittedNamespaces = $this->getConfig()->getValue('autoloader/omit');
188
        }
189
190
        // Now check which structures we have to create and split them up for multi-threaded creation
191
        $generatorStack = array();
192
        /** @var \AppserverIo\Doppelgaenger\Entities\Definitions\Structure $structure */
193
        foreach ($structures as $identifier => $structure) {
194
            // Working on our own files has very weird side effects, so don't do it
195
            if (strpos($structure->getIdentifier(), 'AppserverIo\Doppelgaenger') !== false) {
196
                continue;
197
            }
198
199
            // Might this be an omitted structure after all?
200
            foreach ($omittedNamespaces as $omittedNamespace) {
201
                // If our class name begins with the omitted part e.g. it's namespace
202
                if (strpos($structure->getIdentifier(), $omittedNamespace) === 0) {
203
                    continue 2;
204
                }
205
            }
206
207
            // adding all structures to the generator stack as we cannot assure cross references from aspects here
208
            $generatorStack[$identifier] = $structure;
209
        }
210
211
        // Chuck the stack and start generating
212
        $generatorStack = array_chunk($generatorStack, self::GENERATOR_STACK_COUNT, true);
213
214
        // Generate all the structures!
215
        $generatorThreads = array();
216
        foreach ($generatorStack as $key => $generatorStackChunk) {
217
            $generatorThreads[$key] = new GeneratorThread($generator, $generatorStackChunk);
218
            $generatorThreads[$key]->start();
219
        }
220
221
        // Wait on the threads
222
        foreach ($generatorThreads as $generatorThread) {
223
            $generatorThread->join();
224
        }
225
226
        // mark this instance as production ready cachewise
227
        $this->cacheIsReady = true;
228
229
        // Still here? Sounds about right
230
        return true;
231
    }
232
233
    /**
234
     * Will initiate the creation of a structure map and the parsing process of all found structures
235
     *
236
     * @return bool
237
     */
238
    protected function fillCache()
239
    {
240
        // Lets create the definitions
241
        return $this->createDefinitions();
242
    }
243
244
    /**
245
     * Getter for the $aspectRegister property
246
     *
247
     * @return \AppserverIo\Doppelgaenger\AspectRegister
248
     */
249
    public function getAspectRegister()
250
    {
251
        return $this->aspectRegister;
252
    }
253
254
    /**
255
     * Getter for the config member
256
     *
257
     * @return \AppserverIo\Doppelgaenger\Config
258
     */
259
    public function getConfig()
260
    {
261
        return $this->config;
262
    }
263
264
    /**
265
     * Getter for the structureMap member
266
     *
267
     * @return \AppserverIo\Doppelgaenger\StructureMap
268
     */
269
    public function getStructureMap()
270
    {
271
        return $this->structureMap;
272
    }
273
274
    /**
275
     * Will inject an AspectRegister instance into the generator
276
     *
277
     * @param \AppserverIo\Doppelgaenger\AspectRegister $aspectRegister The AspectRegister instance to inject
278
     *
279
     * @return null
280
     */
281
    public function injectAspectRegister(AspectRegister $aspectRegister)
282
    {
283
        $this->aspectRegister = $aspectRegister;
284
    }
285
286
    /**
287
     * Will load any given structure based on it's availability in our structure map which depends on the configured
288
     * project directories.
289
     *
290
     * If the structure cannot be found we will redirect to the composer autoloader which we registered as a fallback
291
     *
292
     * @param string $className The name of the structure we will try to load
293
     *
294
     * @return null
295
     */
296
    public function loadClassProductionNoOmit($className)
297
    {
298
299
        // prepare the cache path
300
        $cachePath = $this->cacheDir . DIRECTORY_SEPARATOR . str_replace('\\', '_', $className) . '.php';
301
302
        // check if the file is readable
303
        if (is_readable($cachePath)) {
304
            require $cachePath;
305
            return;
306
        }
307
    }
308
309
    /**
310
     * Will load any given structure based on it's availability in our structure map which depends on the configured
311
     * project directories.
312
     *
313
     * If the structure cannot be found we will redirect to the composer autoloader which we registered as a fallback
314
     *
315
     * @param string $className The name of the structure we will try to load
316
     *
317
     * @return null
318
     */
319
    public function loadClass($className)
320
    {
321
322
        // might the class be a omitted one? If so we can require the original.
323
        if ($this->autoloaderOmit) {
324
            $omittedNamespaces = $this->config->getValue('autoloader/omit');
325
            foreach ($omittedNamespaces as $omitted) {
326
                // If our class name begins with the omitted part e.g. it's namespace
327
                if (strpos($className, str_replace('\\\\', '\\', $omitted)) === 0) {
328
                    return;
329
                }
330
            }
331
        }
332
333
        // do we have the file in our cache dir? If we are in development mode we have to ignore this.
334
        if ($this->environment !== 'development') {
335
            $this->loadClassProductionNoOmit($className);
336
            if (class_exists($className, false) || interface_exists($className, false) || trait_exists($className, false)) {
337
                return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type null.
Loading history...
338
            }
339
        }
340
341
        // if we are loading something of our own library we can skip to composer
342
        if (strpos($className, 'AppserverIo\Doppelgaenger') === 0 || strpos($className, 'AppserverIo\Appserver\Core\StackableStructureMap') !== false) {
343
            return;
344
        }
345
346
        // get the file from the map
347
        $file = $this->structureMap->getEntry($className);
348
349
        // did we get something? If so we have to load it
350
        if ($file instanceof Structure) {
351
            require $file->getPath();
352
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type null.
Loading history...
353
        }
354
    }
355
356
    /**
357
     * We will refill the cache dir by emptying it and filling it again
358
     *
359
     * @return bool
360
     */
361
    protected function refillCache()
362
    {
363
364
        // lets clear the cache so we can fill it anew
365
        foreach (new \DirectoryIterator($this->getConfig()->getValue('cache/dir')) as $fileInfo) {
366
            if (!$fileInfo->isDot() && !$fileInfo->isDir()) {
367
                // unlink the file
368
                unlink($fileInfo->getPathname());
369
            }
370
        }
371
372
        // lets create the definitions anew
373
        return $this->createDefinitions();
374
    }
375
376
    /**
377
     * Will register our autoloading method at the beginning of the spl autoloader stack
378
     *
379
     * @param boolean $throw   Should we throw an exception on error?
380
     * @param boolean $prepend If you want to NOT prepend you might, but you should not
381
     *
382
     * @return null
383
     */
384
    public function register($throw = true, $prepend = true)
385
    {
386
387
        // now we have a config no matter what, we can store any instance we might need
388
        $this->getConfig()->storeInstances();
389
390
        // query whether the cache is ready or if we aren't in production mode
391
        if (!$this->cacheIsReady || $this->environment !== 'production') {
392
            // If yes, load the appropriate autoloader method
393
            spl_autoload_register(array($this, self::OUR_LOADER), $throw, $prepend);
394
            return;
395
        }
396
397
        // the cache is ready, so register the simple class loader
398
        spl_autoload_register(array($this, self::OUR_LOADER_PROD_NO_OMIT), $throw, $prepend);
399
    }
400
401
    /**
402
     * Uninstalls this class loader from the SPL autoloader stack.
403
     *
404
     * @return void
405
     */
406
    public function unregister()
407
    {
408
409
        // query whether the cache is ready or if we aren't in production mode
410
        if (!$this->cacheIsReady || $this->environment !== 'production') {
411
            // If yes, unload the appropriate autoloader method
412
            spl_autoload_unregister(array($this, self::OUR_LOADER));
413
            return;
414
        }
415
416
        // the cache has been ready, so unregister the simple class loader
417
        spl_autoload_unregister(array($this, self::OUR_LOADER_PROD_NO_OMIT));
418
    }
419
}
420