Passed
Push — master ( 604c83...a44f99 )
by Ingo
06:41
created

CoreKernel::sessionEnvironment()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 30
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 7
nop 0
dl 0
loc 30
rs 6.7272
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A CoreKernel::bootConfigs() 0 10 3
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\PublicThemes;
28
use SilverStripe\View\SSViewer;
29
use SilverStripe\View\ThemeManifest;
30
use SilverStripe\View\ThemeResourceLoader;
31
use SilverStripe\Dev\Deprecation;
32
33
/**
34
 * Simple Kernel container
35
 */
36
class CoreKernel implements Kernel
37
{
38
    /**
39
     * @var Kernel
40
     */
41
    protected $nestedFrom = null;
42
43
    /**
44
     * @var Injector
45
     */
46
    protected $container = null;
47
48
    /**
49
     * @var string
50
     */
51
    protected $enviroment = null;
52
53
    /**
54
     * @var ClassLoader
55
     */
56
    protected $classLoader = null;
57
58
    /**
59
     * @var ModuleLoader
60
     */
61
    protected $moduleLoader = null;
62
63
    /**
64
     * @var ConfigLoader
65
     */
66
    protected $configLoader = null;
67
68
    /**
69
     * @var InjectorLoader
70
     */
71
    protected $injectorLoader = null;
72
73
    /**
74
     * @var ThemeResourceLoader
75
     */
76
    protected $themeResourceLoader = null;
77
78
    protected $basePath = null;
79
80
    /**
81
     * Create a new kernel for this application
82
     *
83
     * @param string $basePath Path to base dir for this application
84
     */
85
    public function __construct($basePath)
86
    {
87
        $this->basePath = $basePath;
88
89
        // Initialise the dependency injector as soon as possible, as it is
90
        // subsequently used by some of the following code
91
        $injectorLoader = InjectorLoader::inst();
92
        $injector = new Injector(array('locator' => SilverStripeServiceConfigurationLocator::class));
93
        $injectorLoader->pushManifest($injector);
94
        $this->setInjectorLoader($injectorLoader);
95
96
        // Manifest cache factory
97
        $manifestCacheFactory = $this->buildManifestCacheFactory();
98
99
        // Class loader
100
        $classLoader = ClassLoader::inst();
101
        $classLoader->pushManifest(new ClassManifest($basePath, $manifestCacheFactory));
102
        $this->setClassLoader($classLoader);
103
104
        // Module loader
105
        $moduleLoader = ModuleLoader::inst();
106
        $moduleManifest = new ModuleManifest($basePath, $manifestCacheFactory);
107
        $moduleLoader->pushManifest($moduleManifest);
108
        $this->setModuleLoader($moduleLoader);
109
110
        // Config loader
111
        // @todo refactor CoreConfigFactory
112
        $configFactory = new CoreConfigFactory($manifestCacheFactory);
113
        $configManifest = $configFactory->createRoot();
114
        $configLoader = ConfigLoader::inst();
115
        $configLoader->pushManifest($configManifest);
116
        $this->setConfigLoader($configLoader);
117
118
        // Load template manifest
119
        $themeResourceLoader = ThemeResourceLoader::inst();
120
        $themeResourceLoader->addSet(SSViewer::PUBLIC_THEME, new PublicThemes());
121
        $themeResourceLoader->addSet(SSViewer::DEFAULT_THEME, new ThemeManifest(
122
            $basePath,
123
            null, // project is defined in config, and this argument is deprecated
124
            $manifestCacheFactory
125
        ));
126
        $this->setThemeResourceLoader($themeResourceLoader);
127
    }
128
129
    public function getEnvironment()
130
    {
131
        // Check set
132
        if ($this->enviroment) {
133
            return $this->enviroment;
134
        }
135
136
        // Check getenv
137
        if ($env = Environment::getEnv('SS_ENVIRONMENT_TYPE')) {
138
            return $env;
139
        }
140
141
        return self::LIVE;
142
    }
143
144
    public function boot($flush = false)
145
    {
146
        $this->bootPHP();
147
        $this->bootManifests($flush);
148
        $this->bootErrorHandling();
149
        $this->bootDatabaseEnvVars();
150
        $this->bootConfigs();
151
        $this->bootDatabaseGlobals();
152
        $this->validateDatabase();
153
    }
154
155
    /**
156
     * Include all _config.php files
157
     */
158
    protected function bootConfigs()
159
    {
160
        global $project;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
161
        $projectBefore = $project;
162
        $config = ModuleManifest::config();
163
        // After loading all other app manifests, include _config.php files
164
        $this->getModuleLoader()->getManifest()->activateConfig();
165
        if ($project && $project !== $projectBefore) {
166
            Deprecation::notice('5.0', '$project global is deprecated');
167
            $config->set('project', $project);
168
        }
169
    }
170
171
    /**
172
     * Load default database configuration from the $database and $databaseConfig globals
173
     */
174
    protected function bootDatabaseGlobals()
175
    {
176
        // Now that configs have been loaded, we can check global for database config
177
        global $databaseConfig;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
178
        global $database;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
179
180
        // Case 1: $databaseConfig global exists. Merge $database in as needed
181
        if (!empty($databaseConfig)) {
182
            if (!empty($database)) {
183
                $databaseConfig['database'] =  $this->getDatabasePrefix() . $database;
184
            }
185
186
            // Only set it if its valid, otherwise ignore $databaseConfig entirely
187
            if (!empty($databaseConfig['database'])) {
188
                DB::setConfig($databaseConfig);
189
190
                return;
191
            }
192
        }
193
194
        // Case 2: $database merged into existing config
195
        if (!empty($database)) {
196
            $existing = DB::getConfig();
197
            $existing['database'] = $this->getDatabasePrefix() . $database;
198
199
            DB::setConfig($existing);
200
        }
201
    }
202
203
    /**
204
     * Load default database configuration from environment variable
205
     */
206
    protected function bootDatabaseEnvVars()
207
    {
208
        // Set default database config
209
        $databaseConfig = $this->getDatabaseConfig();
210
        $databaseConfig['database'] = $this->getDatabaseName();
211
        DB::setConfig($databaseConfig);
212
    }
213
214
    /**
215
     * Check that the database configuration is valid, throwing an HTTPResponse_Exception if it's not
216
     *
217
     * @throws HTTPResponse_Exception
218
     */
219
    protected function validateDatabase()
220
    {
221
        $databaseConfig = DB::getConfig();
222
        // Gracefully fail if no DB is configured
223
        if (empty($databaseConfig['database'])) {
224
            $this->detectLegacyEnvironment();
225
            $this->redirectToInstaller();
226
        }
227
    }
228
229
    /**
230
     * Check if there's a legacy _ss_environment.php file
231
     *
232
     * @throws HTTPResponse_Exception
233
     */
234
    protected function detectLegacyEnvironment()
235
    {
236
        // Is there an _ss_environment.php file?
237
        if (!file_exists($this->basePath . '/_ss_environment.php') &&
238
            !file_exists(dirname($this->basePath) . '/_ss_environment.php')
239
        ) {
240
            return;
241
        }
242
243
        // Build error response
244
        $dv = new DebugView();
245
        $body =
246
            $dv->renderHeader() .
247
            $dv->renderInfo(
248
                "Configuration Error",
249
                Director::absoluteBaseURL()
250
            ) .
251
            $dv->renderParagraph(
252
                'You need to replace your _ss_environment.php file with a .env file, or with environment '
253
                . 'variables.<br><br>'
254
                . 'See the <a href="https://docs.silverstripe.org/en/4/getting_started/environment_management/">'
255
                . 'Environment Management</a> docs for more information.'
256
            ) .
257
            $dv->renderFooter();
258
259
        // Raise error
260
        $response = new HTTPResponse($body, 500);
261
        throw new HTTPResponse_Exception($response);
262
    }
263
264
    /**
265
     * If missing configuration, redirect to install.php
266
     */
267
    protected function redirectToInstaller()
268
    {
269
        // Error if installer not available
270
        if (!file_exists(Director::publicFolder() . '/install.php')) {
271
            throw new HTTPResponse_Exception(
272
                'SilverStripe Framework requires database configuration defined via .env',
273
                500
274
            );
275
        }
276
277
        // Redirect to installer
278
        $response = new HTTPResponse();
279
        $response->redirect(Director::absoluteURL('install.php'));
280
        throw new HTTPResponse_Exception($response);
281
    }
282
283
    /**
284
     * Load database config from environment
285
     *
286
     * @return array
287
     */
288
    protected function getDatabaseConfig()
289
    {
290
        /** @skipUpgrade */
291
        $databaseConfig = [
292
            "type" => Environment::getEnv('SS_DATABASE_CLASS') ?: 'MySQLDatabase',
293
            "server" => Environment::getEnv('SS_DATABASE_SERVER') ?: 'localhost',
294
            "username" => Environment::getEnv('SS_DATABASE_USERNAME') ?: null,
295
            "password" => Environment::getEnv('SS_DATABASE_PASSWORD') ?: null,
296
        ];
297
298
        // Set the port if called for
299
        $dbPort = Environment::getEnv('SS_DATABASE_PORT');
300
        if ($dbPort) {
301
            $databaseConfig['port'] = $dbPort;
302
        }
303
304
        // Set the timezone if called for
305
        $dbTZ = Environment::getEnv('SS_DATABASE_TIMEZONE');
306
        if ($dbTZ) {
307
            $databaseConfig['timezone'] = $dbTZ;
308
        }
309
310
        // For schema enabled drivers:
311
        $dbSchema = Environment::getEnv('SS_DATABASE_SCHEMA');
312
        if ($dbSchema) {
313
            $databaseConfig["schema"] = $dbSchema;
314
        }
315
316
        // For SQlite3 memory databases (mainly for testing purposes)
317
        $dbMemory = Environment::getEnv('SS_DATABASE_MEMORY');
318
        if ($dbMemory) {
319
            $databaseConfig["memory"] = $dbMemory;
320
        }
321
322
        // Allow database adapters to handle their own configuration
323
        DatabaseAdapterRegistry::autoconfigure($databaseConfig);
324
        return $databaseConfig;
325
    }
326
327
    /**
328
     * @return string
329
     */
330
    protected function getDatabasePrefix()
331
    {
332
        return Environment::getEnv('SS_DATABASE_PREFIX') ?: '';
333
    }
334
335
    /**
336
     * Get name of database
337
     *
338
     * @return string
339
     */
340
    protected function getDatabaseName()
341
    {
342
        // Check globals
343
        global $database;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
344
345
        if (!empty($database)) {
346
            return $this->getDatabasePrefix() . $database;
347
        }
348
349
        global $databaseConfig;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
350
351
        if (!empty($databaseConfig['database'])) {
352
            return $databaseConfig['database']; // Note: Already includes prefix
353
        }
354
355
        // Check environment
356
        $database = Environment::getEnv('SS_DATABASE_NAME');
357
358
        if ($database) {
359
            return $this->getDatabasePrefix() . $database;
360
        }
361
362
        // Auto-detect name
363
        $chooseName = Environment::getEnv('SS_DATABASE_CHOOSE_NAME');
364
365
        if ($chooseName) {
366
            // Find directory to build name from
367
            $loopCount = (int)$chooseName;
368
            $databaseDir = $this->basePath;
369
            for ($i = 0; $i < $loopCount-1; $i++) {
370
                $databaseDir = dirname($databaseDir);
371
            }
372
373
            // Build name
374
            $database = str_replace('.', '', basename($databaseDir));
375
            $prefix = $this->getDatabasePrefix();
376
377
            if ($prefix) {
378
                $prefix = 'SS_';
379
            } else {
380
                // If no prefix, hard-code prefix into database global
381
                $prefix = '';
382
                $database = 'SS_' . $database;
383
            }
384
385
            return $prefix . $database;
386
        }
387
388
        // no DB name (may be optional for some connectors)
389
        return null;
390
    }
391
392
    /**
393
     * Initialise PHP with default variables
394
     */
395
    protected function bootPHP()
396
    {
397
        if ($this->getEnvironment() === self::LIVE) {
398
            // limited to fatal errors and warnings in live mode
399
            error_reporting(E_ALL & ~(E_DEPRECATED | E_STRICT | E_NOTICE));
400
        } else {
401
            // Report all errors in dev / test mode
402
            error_reporting(E_ALL | E_STRICT);
403
        }
404
405
        /**
406
         * Ensure we have enough memory
407
         */
408
        Environment::increaseMemoryLimitTo('64M');
409
410
        // Ensure we don't run into xdebug's fairly conservative infinite recursion protection limit
411
        if (function_exists('xdebug_enable')) {
412
            $current = ini_get('xdebug.max_nesting_level');
413
            if ((int)$current < 200) {
414
                ini_set('xdebug.max_nesting_level', 200);
415
            }
416
        }
417
418
        /**
419
         * Set default encoding
420
         */
421
        mb_http_output('UTF-8');
422
        mb_internal_encoding('UTF-8');
423
        mb_regex_encoding('UTF-8');
424
425
        /**
426
         * Enable better garbage collection
427
         */
428
        gc_enable();
429
    }
430
431
    /**
432
     * @return ManifestCacheFactory
433
     */
434
    protected function buildManifestCacheFactory()
435
    {
436
        return new ManifestCacheFactory([
437
            'namespace' => 'manifestcache',
438
            'directory' => TempFolder::getTempFolder($this->basePath),
439
        ]);
440
    }
441
442
    /**
443
     * @return bool
444
     */
445
    protected function getIncludeTests()
446
    {
447
        return false;
448
    }
449
450
    /**
451
     * Boot all manifests
452
     *
453
     * @param bool $flush
454
     */
455
    protected function bootManifests($flush)
456
    {
457
        // Setup autoloader
458
        $this->getClassLoader()->init($this->getIncludeTests(), $flush);
459
460
        // Find modules
461
        $this->getModuleLoader()->init($this->getIncludeTests(), $flush);
462
463
        // Flush config
464
        if ($flush) {
465
            $config = $this->getConfigLoader()->getManifest();
466
            if ($config instanceof CachedConfigCollection) {
467
                $config->setFlush(true);
468
            }
469
        }
470
        // tell modules to sort, now that config is available
471
        $this->getModuleLoader()->getManifest()->sort();
472
473
        // Find default templates
474
        $defaultSet = $this->getThemeResourceLoader()->getSet('$default');
475
        if ($defaultSet instanceof ThemeManifest) {
476
            $defaultSet->setProject(
477
                ModuleManifest::config()->get('project')
478
            );
479
            $defaultSet->init($this->getIncludeTests(), $flush);
480
        }
481
    }
482
483
    /**
484
     * Turn on error handling
485
     */
486
    protected function bootErrorHandling()
487
    {
488
        // Register error handler
489
        $errorHandler = Injector::inst()->get(ErrorHandler::class);
490
        $errorHandler->start();
491
492
        // Register error log file
493
        $errorLog = Environment::getEnv('SS_ERROR_LOG');
494
        if ($errorLog) {
495
            $logger = Injector::inst()->get(LoggerInterface::class);
496
            if ($logger instanceof Logger) {
497
                $logger->pushHandler(new StreamHandler($this->basePath . '/' . $errorLog, Logger::WARNING));
498
            } else {
499
                user_error(
500
                    "SS_ERROR_LOG setting only works with Monolog, you are using another logger",
501
                    E_USER_WARNING
502
                );
503
            }
504
        }
505
    }
506
507
    public function shutdown()
508
    {
509
    }
510
511
    public function nest()
512
    {
513
        // Clone this kernel, nesting config / injector manifest containers
514
        $kernel = clone $this;
515
        $kernel->setConfigLoader($this->configLoader->nest());
516
        $kernel->setInjectorLoader($this->injectorLoader->nest());
517
        $kernel->nestedFrom = $this;
518
        return $kernel;
519
    }
520
521
    public function activate()
522
    {
523
        $this->configLoader->activate();
524
        $this->injectorLoader->activate();
525
526
        // Self register
527
        $this->getInjectorLoader()
528
            ->getManifest()
529
            ->registerService($this, Kernel::class);
530
        return $this;
531
    }
532
533
    public function getNestedFrom()
534
    {
535
        return $this->nestedFrom;
536
    }
537
538
    public function getContainer()
539
    {
540
        return $this->getInjectorLoader()->getManifest();
541
    }
542
543
    public function setInjectorLoader(InjectorLoader $injectorLoader)
544
    {
545
        $this->injectorLoader = $injectorLoader;
546
        $injectorLoader
547
            ->getManifest()
548
            ->registerService($this, Kernel::class);
549
        return $this;
550
    }
551
552
    public function getInjectorLoader()
553
    {
554
        return $this->injectorLoader;
555
    }
556
557
    public function getClassLoader()
558
    {
559
        return $this->classLoader;
560
    }
561
562
    public function setClassLoader(ClassLoader $classLoader)
563
    {
564
        $this->classLoader = $classLoader;
565
        return $this;
566
    }
567
568
    public function getModuleLoader()
569
    {
570
        return $this->moduleLoader;
571
    }
572
573
    public function setModuleLoader(ModuleLoader $moduleLoader)
574
    {
575
        $this->moduleLoader = $moduleLoader;
576
        return $this;
577
    }
578
579
    public function setEnvironment($environment)
580
    {
581
        if (!in_array($environment, [self::DEV, self::TEST, self::LIVE, null])) {
582
            throw new InvalidArgumentException(
583
                "Director::set_environment_type passed '$environment'.  It should be passed dev, test, or live"
584
            );
585
        }
586
        $this->enviroment = $environment;
587
        return $this;
588
    }
589
590
    public function getConfigLoader()
591
    {
592
        return $this->configLoader;
593
    }
594
595
    public function setConfigLoader($configLoader)
596
    {
597
        $this->configLoader = $configLoader;
598
        return $this;
599
    }
600
601
    public function getThemeResourceLoader()
602
    {
603
        return $this->themeResourceLoader;
604
    }
605
606
    public function setThemeResourceLoader($themeResourceLoader)
607
    {
608
        $this->themeResourceLoader = $themeResourceLoader;
609
        return $this;
610
    }
611
}
612