Completed
Push — resource-url-generator ( 53dbe7...4e3b5a )
by Sam
09:57
created

CoreKernel   F

Complexity

Total Complexity 74

Size/Duplication

Total Lines 598
Duplicated Lines 2.34 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 0
Metric Value
dl 14
loc 598
rs 1.7098
c 0
b 0
f 0
wmc 74
lcom 1
cbo 22

34 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 42 1
A getEnvironment() 0 20 4
C sessionEnvironment() 14 31 7
A boot() 0 10 1
A bootConfigs() 0 5 1
A bootDatabaseEnvVars() 0 7 1
A validateDatabase() 0 9 2
B detectLegacyEnvironment() 0 28 3
A redirectToInstaller() 0 15 2
F getDatabaseConfig() 0 39 9
B bootDatabaseGlobals() 0 28 5
A getDatabasePrefix() 0 4 1
C getDatabaseName() 0 47 7
B bootPHP() 0 35 4
A buildManifestCacheFactory() 0 7 1
A getIncludeTests() 0 4 1
B bootManifests() 0 22 4
A bootErrorHandling() 0 17 3
A shutdown() 0 3 1
A nest() 0 8 1
A activate() 0 11 1
A getNestedFrom() 0 4 1
A getContainer() 0 4 1
A setInjectorLoader() 0 8 1
A getInjectorLoader() 0 4 1
A getClassLoader() 0 4 1
A setClassLoader() 0 5 1
A getModuleLoader() 0 4 1
A setModuleLoader() 0 5 1
A setEnvironment() 0 10 2
A getConfigLoader() 0 4 1
A setConfigLoader() 0 5 1
A getThemeResourceLoader() 0 4 1
A setThemeResourceLoader() 0 5 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CoreKernel 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 CoreKernel, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\Core;
4
5
use InvalidArgumentException;
6
use Monolog\Handler\StreamHandler;
7
use Monolog\Logger;
8
use Psr\Log\LoggerInterface;
9
use SilverStripe\Config\Collections\CachedConfigCollection;
10
use SilverStripe\Control\Director;
11
use SilverStripe\Control\HTTPResponse;
12
use SilverStripe\Control\HTTPResponse_Exception;
13
use SilverStripe\Core\Cache\ManifestCacheFactory;
14
use SilverStripe\Core\Config\ConfigLoader;
15
use SilverStripe\Core\Config\CoreConfigFactory;
16
use SilverStripe\Core\Injector\Injector;
17
use SilverStripe\Core\Injector\InjectorLoader;
18
use SilverStripe\Core\Injector\SilverStripeServiceConfigurationLocator;
19
use SilverStripe\Core\Manifest\ClassLoader;
20
use SilverStripe\Core\Manifest\ClassManifest;
21
use SilverStripe\Core\Manifest\ModuleLoader;
22
use SilverStripe\Core\Manifest\ModuleManifest;
23
use SilverStripe\Dev\DebugView;
24
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
25
use SilverStripe\Logging\ErrorHandler;
26
use SilverStripe\ORM\DB;
27
use SilverStripe\View\ThemeManifest;
28
use SilverStripe\View\ThemeResourceLoader;
29
30
/**
31
 * Simple Kernel container
32
 */
33
class CoreKernel implements Kernel
34
{
35
    /**
36
     * @var Kernel
37
     */
38
    protected $nestedFrom = null;
39
40
    /**
41
     * @var Injector
42
     */
43
    protected $container = null;
44
45
    /**
46
     * @var string
47
     */
48
    protected $enviroment = null;
49
50
    /**
51
     * @var ClassLoader
52
     */
53
    protected $classLoader = null;
54
55
    /**
56
     * @var ModuleLoader
57
     */
58
    protected $moduleLoader = null;
59
60
    /**
61
     * @var ConfigLoader
62
     */
63
    protected $configLoader = null;
64
65
    /**
66
     * @var InjectorLoader
67
     */
68
    protected $injectorLoader = null;
69
70
    /**
71
     * @var ThemeResourceLoader
72
     */
73
    protected $themeResourceLoader = null;
74
75
    protected $basePath = null;
76
77
    /**
78
     * Create a new kernel for this application
79
     *
80
     * @param string $basePath Path to base dir for this application
81
     */
82
    public function __construct($basePath)
83
    {
84
        $this->basePath = $basePath;
85
86
        // Initialise the dependency injector as soon as possible, as it is
87
        // subsequently used by some of the following code
88
        $injectorLoader = InjectorLoader::inst();
89
        $injector = new Injector(array('locator' => SilverStripeServiceConfigurationLocator::class));
90
        $injectorLoader->pushManifest($injector);
91
        $this->setInjectorLoader($injectorLoader);
92
93
        // Manifest cache factory
94
        $manifestCacheFactory = $this->buildManifestCacheFactory();
95
96
        // Class loader
97
        $classLoader = ClassLoader::inst();
98
        $classLoader->pushManifest(new ClassManifest($basePath, $manifestCacheFactory));
99
        $this->setClassLoader($classLoader);
100
101
        // Module loader
102
        $moduleLoader = ModuleLoader::inst();
103
        $moduleManifest = new ModuleManifest($basePath, $manifestCacheFactory);
104
        $moduleLoader->pushManifest($moduleManifest);
105
        $this->setModuleLoader($moduleLoader);
106
107
        // Config loader
108
        // @todo refactor CoreConfigFactory
109
        $configFactory = new CoreConfigFactory($manifestCacheFactory);
110
        $configManifest = $configFactory->createRoot();
111
        $configLoader = ConfigLoader::inst();
112
        $configLoader->pushManifest($configManifest);
113
        $this->setConfigLoader($configLoader);
114
115
        // Load template manifest
116
        $themeResourceLoader = ThemeResourceLoader::inst();
117
        $themeResourceLoader->addSet('$default', new ThemeManifest(
118
            $basePath,
119
            project(),
120
            $manifestCacheFactory
121
        ));
122
        $this->setThemeResourceLoader($themeResourceLoader);
123
    }
124
125
    public function getEnvironment()
126
    {
127
        // Check set
128
        if ($this->enviroment) {
129
            return $this->enviroment;
130
        }
131
132
        // Check saved session
133
        $env = $this->sessionEnvironment();
134
        if ($env) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $env of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
135
            return $env;
136
        }
137
138
        // Check getenv
139
        if ($env = getenv('SS_ENVIRONMENT_TYPE')) {
140
            return $env;
141
        }
142
143
        return self::LIVE;
144
    }
145
146
    /**
147
     * Check or update any temporary environment specified in the session.
148
     *
149
     * @return null|string
150
     */
151
    protected function sessionEnvironment()
152
    {
153
        // Check isDev in querystring
154 View Code Duplication
        if (isset($_GET['isDev'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
155
            if (isset($_SESSION)) {
156
                unset($_SESSION['isTest']); // In case we are changing from test mode
157
                $_SESSION['isDev'] = $_GET['isDev'];
158
            }
159
            return self::DEV;
160
        }
161
162
        // Check isTest in querystring
163 View Code Duplication
        if (isset($_GET['isTest'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
164
            if (isset($_SESSION)) {
165
                unset($_SESSION['isDev']); // In case we are changing from dev mode
166
                $_SESSION['isTest'] = $_GET['isTest'];
167
            }
168
            return self::TEST;
169
        }
170
171
        // Check session
172
        if (!empty($_SESSION['isDev'])) {
173
            return self::DEV;
174
        }
175
        if (!empty($_SESSION['isTest'])) {
176
            return self::TEST;
177
        }
178
179
        // no session environment
180
        return null;
181
    }
182
183
    public function boot($flush = false)
184
    {
185
        $this->bootPHP();
186
        $this->bootManifests($flush);
187
        $this->bootErrorHandling();
188
        $this->bootDatabaseEnvVars();
189
        $this->bootConfigs();
190
        $this->bootDatabaseGlobals();
191
        $this->validateDatabase();
192
    }
193
194
    /**
195
     * Include all _config.php files
196
     */
197
    protected function bootConfigs()
198
    {
199
        // After loading all other app manifests, include _config.php files
200
        $this->getModuleLoader()->getManifest()->activateConfig();
201
    }
202
203
    /**
204
     * Load default database configuration from the $database and $databaseConfig globals
205
     */
206
    protected function bootDatabaseGlobals()
207
    {
208
        // Now that configs have been loaded, we can check global for database config
209
        global $databaseConfig;
210
        global $database;
211
212
        // Case 1: $databaseConfig global exists. Merge $database in as needed
213
        if (!empty($databaseConfig)) {
214
            if (!empty($database)) {
215
                $databaseConfig['database'] =  $this->getDatabasePrefix() . $database;
216
            }
217
218
            // Only set it if its valid, otherwise ignore $databaseConfig entirely
219
            if (!empty($databaseConfig['database'])) {
220
                DB::setConfig($databaseConfig);
221
222
                return;
223
            }
224
        }
225
226
        // Case 2: $database merged into existing config
227
        if (!empty($database)) {
228
            $existing = DB::getConfig();
229
            $existing['database'] = $this->getDatabasePrefix() . $database;
230
231
            DB::setConfig($existing);
232
        }
233
    }
234
235
    /**
236
     * Load default database configuration from environment variable
237
     */
238
    protected function bootDatabaseEnvVars()
239
    {
240
        // Set default database config
241
        $databaseConfig = $this->getDatabaseConfig();
242
        $databaseConfig['database'] = $this->getDatabaseName();
243
        DB::setConfig($databaseConfig);
244
    }
245
246
    /**
247
     * Check that the database configuration is valid, throwing an HTTPResponse_Exception if it's not
248
     *
249
     * @throws HTTPResponse_Exception
250
     */
251
    protected function validateDatabase()
252
    {
253
        $databaseConfig = DB::getConfig();
254
        // Gracefully fail if no DB is configured
255
        if (empty($databaseConfig['database'])) {
256
            $this->detectLegacyEnvironment();
257
            $this->redirectToInstaller();
258
        }
259
    }
260
261
    /**
262
     * Check if there's a legacy _ss_environment.php file
263
     *
264
     * @throws HTTPResponse_Exception
265
     */
266
    protected function detectLegacyEnvironment()
267
    {
268
        // Is there an _ss_environment.php file?
269
        if (!file_exists($this->basePath . '/_ss_environment.php') &&
270
            !file_exists(dirname($this->basePath) . '/_ss_environment.php')
271
        ) {
272
            return;
273
        }
274
275
        // Build error response
276
        $dv = new DebugView();
277
        $body =
278
            $dv->renderHeader() .
279
            $dv->renderInfo(
280
                "Configuraton Error",
281
                Director::absoluteBaseURL()
0 ignored issues
show
Security Bug introduced by
It seems like \SilverStripe\Control\Director::absoluteBaseURL() targeting SilverStripe\Control\Director::absoluteBaseURL() can also be of type false; however, SilverStripe\Dev\DebugView::renderInfo() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
282
            ) .
283
            $dv->renderParagraph(
284
                'You need to replace your _ss_environment.php file with a .env file, or with environment variables.<br><br>'
285
                . 'See the <a href="https://docs.silverstripe.org/en/4/getting_started/environment_management/">'
286
                . 'Environment Management</a> docs for more information.'
287
            ) .
288
            $dv->renderFooter();
289
290
        // Raise error
291
        $response = new HTTPResponse($body, 500);
292
        throw new HTTPResponse_Exception($response);
293
    }
294
295
    /**
296
     * If missing configuration, redirect to install.php
297
     */
298
    protected function redirectToInstaller()
299
    {
300
        // Error if installer not available
301
        if (!file_exists($this->basePath . '/install.php')) {
302
            throw new HTTPResponse_Exception(
303
                'SilverStripe Framework requires a $databaseConfig defined.',
304
                500
305
            );
306
        }
307
308
        // Redirect to installer
309
        $response = new HTTPResponse();
310
        $response->redirect(Director::absoluteURL('install.php'));
0 ignored issues
show
Security Bug introduced by
It seems like \SilverStripe\Control\Di...oluteURL('install.php') targeting SilverStripe\Control\Director::absoluteURL() can also be of type false; however, SilverStripe\Control\HTTPResponse::redirect() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
311
        throw new HTTPResponse_Exception($response);
312
    }
313
314
    /**
315
     * Load database config from environment
316
     *
317
     * @return array
318
     */
319
    protected function getDatabaseConfig()
320
    {
321
322
        /** @skipUpgrade */
323
        $databaseConfig = [
324
            "type" => getenv('SS_DATABASE_CLASS') ?: 'MySQLDatabase',
325
            "server" => getenv('SS_DATABASE_SERVER') ?: 'localhost',
326
            "username" => getenv('SS_DATABASE_USERNAME') ?: null,
327
            "password" => getenv('SS_DATABASE_PASSWORD') ?: null,
328
        ];
329
330
        // Set the port if called for
331
        $dbPort = getenv('SS_DATABASE_PORT');
332
        if ($dbPort) {
333
            $databaseConfig['port'] = $dbPort;
334
        }
335
336
        // Set the timezone if called for
337
        $dbTZ = getenv('SS_DATABASE_TIMEZONE');
338
        if ($dbTZ) {
339
            $databaseConfig['timezone'] = $dbTZ;
340
        }
341
342
        // For schema enabled drivers:
343
        $dbSchema = getenv('SS_DATABASE_SCHEMA');
344
        if ($dbSchema) {
345
            $databaseConfig["schema"] = $dbSchema;
346
        }
347
348
        // For SQlite3 memory databases (mainly for testing purposes)
349
        $dbMemory = getenv('SS_DATABASE_MEMORY');
350
        if ($dbMemory) {
351
            $databaseConfig["memory"] = $dbMemory;
352
        }
353
354
        // Allow database adapters to handle their own configuration
355
        DatabaseAdapterRegistry::autoconfigure();
356
        return $databaseConfig;
357
    }
358
359
    /**
360
     * @return string
361
     */
362
    protected function getDatabasePrefix()
363
    {
364
        return getenv('SS_DATABASE_PREFIX');
365
    }
366
367
    /**
368
     * Get name of database
369
     *
370
     * @return string
371
     */
372
    protected function getDatabaseName()
373
    {
374
        // Check globals
375
        global $database;
376
377
        if (!empty($database)) {
378
            return $this->getDatabasePrefix() . $database;
379
        }
380
381
        global $databaseConfig;
382
383
        if (!empty($databaseConfig['database'])) {
384
            return $databaseConfig['database']; // Note: Already includes prefix
385
        }
386
387
        // Check environment
388
        $database = getenv('SS_DATABASE_NAME');
389
390
        if ($database) {
391
            return $this->getDatabasePrefix() . $database;
392
        }
393
394
        // Auto-detect name
395
        $chooseName = getenv('SS_DATABASE_CHOOSE_NAME');
396
397
        if ($chooseName) {
398
            // Find directory to build name from
399
            $loopCount = (int)$chooseName;
400
            $databaseDir = $this->basePath;
401
            for ($i = 0; $i < $loopCount-1; $i++) {
402
                $databaseDir = dirname($databaseDir);
403
            }
404
405
            // Build name
406
            $database = str_replace('.', '', basename($databaseDir));
407
            $prefix = $this->getDatabasePrefix();
408
409
            if ($prefix === false) {
410
                $prefix = 'SS_';
411
            }
412
413
            return $prefix . $database;
414
        }
415
416
        // no DB name (may be optional for some connectors)
417
        return null;
418
    }
419
420
    /**
421
     * Initialise PHP with default variables
422
     */
423
    protected function bootPHP()
424
    {
425
        if ($this->getEnvironment() === self::LIVE) {
426
            // limited to fatal errors and warnings in live mode
427
            error_reporting(E_ALL & ~(E_DEPRECATED | E_STRICT | E_NOTICE));
428
        } else {
429
            // Report all errors in dev / test mode
430
            error_reporting(E_ALL | E_STRICT);
431
        }
432
433
        /**
434
         * Ensure we have enough memory
435
         */
436
        Environment::increaseMemoryLimitTo('64M');
437
438
        // Ensure we don't run into xdebug's fairly conservative infinite recursion protection limit
439
        if (function_exists('xdebug_enable')) {
440
            $current = ini_get('xdebug.max_nesting_level');
441
            if ((int)$current < 200) {
442
                ini_set('xdebug.max_nesting_level', 200);
443
            }
444
        }
445
446
        /**
447
         * Set default encoding
448
         */
449
        mb_http_output('UTF-8');
450
        mb_internal_encoding('UTF-8');
451
        mb_regex_encoding('UTF-8');
452
453
        /**
454
         * Enable better garbage collection
455
         */
456
        gc_enable();
457
    }
458
459
    /**
460
     * @return ManifestCacheFactory
461
     */
462
    protected function buildManifestCacheFactory()
463
    {
464
        return new ManifestCacheFactory([
465
            'namespace' => 'manifestcache',
466
            'directory' => TempFolder::getTempFolder($this->basePath),
467
        ]);
468
    }
469
470
    /**
471
     * @return bool
472
     */
473
    protected function getIncludeTests()
474
    {
475
        return false;
476
    }
477
478
    /**
479
     * Boot all manifests
480
     *
481
     * @param bool $flush
482
     */
483
    protected function bootManifests($flush)
484
    {
485
        // Setup autoloader
486
        $this->getClassLoader()->init($this->getIncludeTests(), $flush);
487
488
        // Find modules
489
        $this->getModuleLoader()->init($this->getIncludeTests(), $flush);
490
491
        // Flush config
492
        if ($flush) {
493
            $config = $this->getConfigLoader()->getManifest();
494
            if ($config instanceof CachedConfigCollection) {
495
                $config->setFlush(true);
496
            }
497
        }
498
499
        // Find default templates
500
        $defaultSet = $this->getThemeResourceLoader()->getSet('$default');
501
        if ($defaultSet instanceof ThemeManifest) {
502
            $defaultSet->init($this->getIncludeTests(), $flush);
503
        }
504
    }
505
506
    /**
507
     * Turn on error handling
508
     */
509
    protected function bootErrorHandling()
510
    {
511
        // Register error handler
512
        $errorHandler = Injector::inst()->get(ErrorHandler::class);
513
        $errorHandler->start();
514
515
        // Register error log file
516
        $errorLog = getenv('SS_ERROR_LOG');
517
        if ($errorLog) {
518
            $logger = Injector::inst()->get(LoggerInterface::class);
519
            if ($logger instanceof Logger) {
520
                $logger->pushHandler(new StreamHandler($this->basePath . '/' . $errorLog, Logger::WARNING));
521
            } else {
522
                user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING);
523
            }
524
        }
525
    }
526
527
    public function shutdown()
528
    {
529
    }
530
531
    public function nest()
532
    {
533
        // Clone this kernel, nesting config / injector manifest containers
534
        $kernel = clone $this;
535
        $kernel->setConfigLoader($this->configLoader->nest());
536
        $kernel->setInjectorLoader($this->injectorLoader->nest());
537
        return $kernel;
538
    }
539
540
    public function activate()
541
    {
542
        $this->configLoader->activate();
543
        $this->injectorLoader->activate();
544
545
        // Self register
546
        $this->getInjectorLoader()
547
            ->getManifest()
548
            ->registerService($this, Kernel::class);
549
        return $this;
550
    }
551
552
    public function getNestedFrom()
553
    {
554
        return $this->nestedFrom;
555
    }
556
557
    public function getContainer()
558
    {
559
        return $this->getInjectorLoader()->getManifest();
560
    }
561
562
    public function setInjectorLoader(InjectorLoader $injectorLoader)
563
    {
564
        $this->injectorLoader = $injectorLoader;
565
        $injectorLoader
566
            ->getManifest()
567
            ->registerService($this, Kernel::class);
568
        return $this;
569
    }
570
571
    public function getInjectorLoader()
572
    {
573
        return $this->injectorLoader;
574
    }
575
576
    public function getClassLoader()
577
    {
578
        return $this->classLoader;
579
    }
580
581
    public function setClassLoader(ClassLoader $classLoader)
582
    {
583
        $this->classLoader = $classLoader;
584
        return $this;
585
    }
586
587
    public function getModuleLoader()
588
    {
589
        return $this->moduleLoader;
590
    }
591
592
    public function setModuleLoader(ModuleLoader $moduleLoader)
593
    {
594
        $this->moduleLoader = $moduleLoader;
595
        return $this;
596
    }
597
598
    public function setEnvironment($environment)
599
    {
600
        if (!in_array($environment, [self::DEV, self::TEST, self::LIVE, null])) {
601
            throw new InvalidArgumentException(
602
                "Director::set_environment_type passed '$environment'.  It should be passed dev, test, or live"
603
            );
604
        }
605
        $this->enviroment = $environment;
606
        return $this;
607
    }
608
609
    public function getConfigLoader()
610
    {
611
        return $this->configLoader;
612
    }
613
614
    public function setConfigLoader($configLoader)
615
    {
616
        $this->configLoader = $configLoader;
617
        return $this;
618
    }
619
620
    public function getThemeResourceLoader()
621
    {
622
        return $this->themeResourceLoader;
623
    }
624
625
    public function setThemeResourceLoader($themeResourceLoader)
626
    {
627
        $this->themeResourceLoader = $themeResourceLoader;
628
        return $this;
629
    }
630
}
631