Passed
Push — 4.2 ( 7f69cc...ca56e8 )
by
unknown
10:42
created

CoreKernel::sessionEnvironment()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
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
     * Indicates whether the Kernel has been booted already
82
     *
83
     * @var bool
84
     */
85
    private $booted = false;
86
87
    /**
88
     * Indicates whether the Kernel has been flushed on boot
89
     * Unitialized before boot
90
     *
91
     * @var bool
92
     */
93
    private $flush;
94
95
    /**
96
     * Create a new kernel for this application
97
     *
98
     * @param string $basePath Path to base dir for this application
99
     */
100
    public function __construct($basePath)
101
    {
102
        $this->basePath = $basePath;
103
104
        // Initialise the dependency injector as soon as possible, as it is
105
        // subsequently used by some of the following code
106
        $injectorLoader = InjectorLoader::inst();
107
        $injector = new Injector(array('locator' => SilverStripeServiceConfigurationLocator::class));
108
        $injectorLoader->pushManifest($injector);
109
        $this->setInjectorLoader($injectorLoader);
110
111
        // Manifest cache factory
112
        $manifestCacheFactory = $this->buildManifestCacheFactory();
113
114
        // Class loader
115
        $classLoader = ClassLoader::inst();
116
        $classLoader->pushManifest(new ClassManifest($basePath, $manifestCacheFactory));
117
        $this->setClassLoader($classLoader);
118
119
        // Module loader
120
        $moduleLoader = ModuleLoader::inst();
121
        $moduleManifest = new ModuleManifest($basePath, $manifestCacheFactory);
122
        $moduleLoader->pushManifest($moduleManifest);
123
        $this->setModuleLoader($moduleLoader);
124
125
        // Config loader
126
        // @todo refactor CoreConfigFactory
127
        $configFactory = new CoreConfigFactory($manifestCacheFactory);
128
        $configManifest = $configFactory->createRoot();
129
        $configLoader = ConfigLoader::inst();
130
        $configLoader->pushManifest($configManifest);
131
        $this->setConfigLoader($configLoader);
132
133
        // Load template manifest
134
        $themeResourceLoader = ThemeResourceLoader::inst();
135
        $themeResourceLoader->addSet(SSViewer::PUBLIC_THEME, new PublicThemes());
136
        $themeResourceLoader->addSet(SSViewer::DEFAULT_THEME, new ThemeManifest(
137
            $basePath,
138
            null, // project is defined in config, and this argument is deprecated
139
            $manifestCacheFactory
140
        ));
141
        $this->setThemeResourceLoader($themeResourceLoader);
142
    }
143
144
    /**
145
     * Get the environment type
146
     *
147
     * @return string
148
     *
149
     * @deprecated 5.0 use Director::get_environment_type() instead. Since 5.0 it should return only if kernel overrides. No checking SESSION or Environment.
150
     */
151
    public function getEnvironment()
152
    {
153
        // Check set
154
        if ($this->enviroment) {
155
            return $this->enviroment;
156
        }
157
158
        // Check saved session
159
        $env = $this->sessionEnvironment();
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Core\CoreKernel::sessionEnvironment() has been deprecated: 5.0 Use Director::get_session_environment_type() instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

159
        $env = /** @scrutinizer ignore-deprecated */ $this->sessionEnvironment();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
160
        if ($env) {
161
            return $env;
162
        }
163
164
        // Check getenv
165
        if ($env = Environment::getEnv('SS_ENVIRONMENT_TYPE')) {
166
            return $env;
167
        }
168
169
        return self::LIVE;
170
    }
171
172
    /**
173
     * Check or update any temporary environment specified in the session.
174
     *
175
     * @return null|string
176
     *
177
     * @deprecated 5.0 Use Director::get_session_environment_type() instead
178
     */
179
    protected function sessionEnvironment()
180
    {
181
        if (!$this->booted) {
182
            // session is not initialyzed yet, neither is manifest
183
            return null;
184
        }
185
186
        return Director::get_session_environment_type();
187
    }
188
189
    public function boot($flush = false)
190
    {
191
        $this->flush = $flush;
192
193
        $this->bootPHP();
194
        $this->bootManifests($flush);
195
        $this->bootErrorHandling();
196
        $this->bootDatabaseEnvVars();
197
        $this->bootConfigs();
198
        $this->bootDatabaseGlobals();
199
        $this->validateDatabase();
200
201
        $this->booted = true;
202
    }
203
204
    /**
205
     * Include all _config.php files
206
     */
207
    protected function bootConfigs()
208
    {
209
        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...
210
        $projectBefore = $project;
211
        $config = ModuleManifest::config();
212
        // After loading all other app manifests, include _config.php files
213
        $this->getModuleLoader()->getManifest()->activateConfig();
214
        if ($project && $project !== $projectBefore) {
215
            Deprecation::notice('5.0', '$project global is deprecated');
216
            $config->set('project', $project);
217
        }
218
    }
219
220
    /**
221
     * Load default database configuration from the $database and $databaseConfig globals
222
     */
223
    protected function bootDatabaseGlobals()
224
    {
225
        // Now that configs have been loaded, we can check global for database config
226
        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...
227
        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...
228
229
        // Case 1: $databaseConfig global exists. Merge $database in as needed
230
        if (!empty($databaseConfig)) {
231
            if (!empty($database)) {
232
                $databaseConfig['database'] =  $this->getDatabasePrefix() . $database;
233
            }
234
235
            // Only set it if its valid, otherwise ignore $databaseConfig entirely
236
            if (!empty($databaseConfig['database'])) {
237
                DB::setConfig($databaseConfig);
238
239
                return;
240
            }
241
        }
242
243
        // Case 2: $database merged into existing config
244
        if (!empty($database)) {
245
            $existing = DB::getConfig();
246
            $existing['database'] = $this->getDatabasePrefix() . $database;
247
248
            DB::setConfig($existing);
249
        }
250
    }
251
252
    /**
253
     * Load default database configuration from environment variable
254
     */
255
    protected function bootDatabaseEnvVars()
256
    {
257
        // Set default database config
258
        $databaseConfig = $this->getDatabaseConfig();
259
        $databaseConfig['database'] = $this->getDatabaseName();
260
        DB::setConfig($databaseConfig);
261
    }
262
263
    /**
264
     * Check that the database configuration is valid, throwing an HTTPResponse_Exception if it's not
265
     *
266
     * @throws HTTPResponse_Exception
267
     */
268
    protected function validateDatabase()
269
    {
270
        $databaseConfig = DB::getConfig();
271
        // Gracefully fail if no DB is configured
272
        if (empty($databaseConfig['database'])) {
273
            $this->detectLegacyEnvironment();
274
            $this->redirectToInstaller();
275
        }
276
    }
277
278
    /**
279
     * Check if there's a legacy _ss_environment.php file
280
     *
281
     * @throws HTTPResponse_Exception
282
     */
283
    protected function detectLegacyEnvironment()
284
    {
285
        // Is there an _ss_environment.php file?
286
        if (!file_exists($this->basePath . '/_ss_environment.php') &&
287
            !file_exists(dirname($this->basePath) . '/_ss_environment.php')
288
        ) {
289
            return;
290
        }
291
292
        // Build error response
293
        $dv = new DebugView();
294
        $body =
295
            $dv->renderHeader() .
296
            $dv->renderInfo(
297
                "Configuration Error",
298
                Director::absoluteBaseURL()
299
            ) .
300
            $dv->renderParagraph(
301
                'You need to replace your _ss_environment.php file with a .env file, or with environment variables.<br><br>'
302
                . 'See the <a href="https://docs.silverstripe.org/en/4/getting_started/environment_management/">'
303
                . 'Environment Management</a> docs for more information.'
304
            ) .
305
            $dv->renderFooter();
306
307
        // Raise error
308
        $response = new HTTPResponse($body, 500);
309
        throw new HTTPResponse_Exception($response);
310
    }
311
312
    /**
313
     * If missing configuration, redirect to install.php
314
     */
315
    protected function redirectToInstaller()
316
    {
317
        // Error if installer not available
318
        if (!file_exists(Director::publicFolder() . '/install.php')) {
319
            throw new HTTPResponse_Exception(
320
                'SilverStripe Framework requires database configuration defined via .env',
321
                500
322
            );
323
        }
324
325
        // Redirect to installer
326
        $response = new HTTPResponse();
327
        $response->redirect(Director::absoluteURL('install.php'));
328
        throw new HTTPResponse_Exception($response);
329
    }
330
331
    /**
332
     * Load database config from environment
333
     *
334
     * @return array
335
     */
336
    protected function getDatabaseConfig()
337
    {
338
        /** @skipUpgrade */
339
        $databaseConfig = [
340
            "type" => Environment::getEnv('SS_DATABASE_CLASS') ?: 'MySQLDatabase',
341
            "server" => Environment::getEnv('SS_DATABASE_SERVER') ?: 'localhost',
342
            "username" => Environment::getEnv('SS_DATABASE_USERNAME') ?: null,
343
            "password" => Environment::getEnv('SS_DATABASE_PASSWORD') ?: null,
344
        ];
345
346
        // Set the port if called for
347
        $dbPort = Environment::getEnv('SS_DATABASE_PORT');
348
        if ($dbPort) {
349
            $databaseConfig['port'] = $dbPort;
350
        }
351
352
        // Set the timezone if called for
353
        $dbTZ = Environment::getEnv('SS_DATABASE_TIMEZONE');
354
        if ($dbTZ) {
355
            $databaseConfig['timezone'] = $dbTZ;
356
        }
357
358
        // For schema enabled drivers:
359
        $dbSchema = Environment::getEnv('SS_DATABASE_SCHEMA');
360
        if ($dbSchema) {
361
            $databaseConfig["schema"] = $dbSchema;
362
        }
363
364
        // For SQlite3 memory databases (mainly for testing purposes)
365
        $dbMemory = Environment::getEnv('SS_DATABASE_MEMORY');
366
        if ($dbMemory) {
367
            $databaseConfig["memory"] = $dbMemory;
368
        }
369
370
        // Allow database adapters to handle their own configuration
371
        DatabaseAdapterRegistry::autoconfigure($databaseConfig);
372
        return $databaseConfig;
373
    }
374
375
    /**
376
     * @return string
377
     */
378
    protected function getDatabasePrefix()
379
    {
380
        return Environment::getEnv('SS_DATABASE_PREFIX') ?: '';
381
    }
382
383
    /**
384
     * Get name of database
385
     *
386
     * @return string
387
     */
388
    protected function getDatabaseName()
389
    {
390
        // Check globals
391
        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...
392
393
        if (!empty($database)) {
394
            return $this->getDatabasePrefix() . $database;
395
        }
396
397
        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...
398
399
        if (!empty($databaseConfig['database'])) {
400
            return $databaseConfig['database']; // Note: Already includes prefix
401
        }
402
403
        // Check environment
404
        $database = Environment::getEnv('SS_DATABASE_NAME');
405
406
        if ($database) {
407
            return $this->getDatabasePrefix() . $database;
408
        }
409
410
        // Auto-detect name
411
        $chooseName = Environment::getEnv('SS_DATABASE_CHOOSE_NAME');
412
413
        if ($chooseName) {
414
            // Find directory to build name from
415
            $loopCount = (int)$chooseName;
416
            $databaseDir = $this->basePath;
417
            for ($i = 0; $i < $loopCount-1; $i++) {
418
                $databaseDir = dirname($databaseDir);
419
            }
420
421
            // Build name
422
            $database = str_replace('.', '', basename($databaseDir));
423
            $prefix = $this->getDatabasePrefix();
424
425
            if ($prefix) {
426
                $prefix = 'SS_';
427
            } else {
428
                // If no prefix, hard-code prefix into database global
429
                $prefix = '';
430
                $database = 'SS_' . $database;
431
            }
432
433
            return $prefix . $database;
434
        }
435
436
        // no DB name (may be optional for some connectors)
437
        return null;
438
    }
439
440
    /**
441
     * Initialise PHP with default variables
442
     */
443
    protected function bootPHP()
444
    {
445
        if ($this->getEnvironment() === self::LIVE) {
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Core\CoreKernel::getEnvironment() has been deprecated: 5.0 use Director::get_environment_type() instead. Since 5.0 it should return only if kernel overrides. No checking SESSION or Environment. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

445
        if (/** @scrutinizer ignore-deprecated */ $this->getEnvironment() === self::LIVE) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
446
            // limited to fatal errors and warnings in live mode
447
            error_reporting(E_ALL & ~(E_DEPRECATED | E_STRICT | E_NOTICE));
448
        } else {
449
            // Report all errors in dev / test mode
450
            error_reporting(E_ALL | E_STRICT);
451
        }
452
453
        /**
454
         * Ensure we have enough memory
455
         */
456
        Environment::increaseMemoryLimitTo('64M');
457
458
        // Ensure we don't run into xdebug's fairly conservative infinite recursion protection limit
459
        if (function_exists('xdebug_enable')) {
460
            $current = ini_get('xdebug.max_nesting_level');
461
            if ((int)$current < 200) {
462
                ini_set('xdebug.max_nesting_level', 200);
463
            }
464
        }
465
466
        /**
467
         * Set default encoding
468
         */
469
        mb_http_output('UTF-8');
470
        mb_internal_encoding('UTF-8');
471
        mb_regex_encoding('UTF-8');
472
473
        /**
474
         * Enable better garbage collection
475
         */
476
        gc_enable();
477
    }
478
479
    /**
480
     * @return ManifestCacheFactory
481
     */
482
    protected function buildManifestCacheFactory()
483
    {
484
        return new ManifestCacheFactory([
485
            'namespace' => 'manifestcache',
486
            'directory' => TempFolder::getTempFolder($this->basePath),
487
        ]);
488
    }
489
490
    /**
491
     * @return bool
492
     */
493
    protected function getIncludeTests()
494
    {
495
        return false;
496
    }
497
498
    /**
499
     * Boot all manifests
500
     *
501
     * @param bool $flush
502
     */
503
    protected function bootManifests($flush)
504
    {
505
        // Setup autoloader
506
        $this->getClassLoader()->init($this->getIncludeTests(), $flush);
507
508
        // Find modules
509
        $this->getModuleLoader()->init($this->getIncludeTests(), $flush);
510
511
        // Flush config
512
        if ($flush) {
513
            $config = $this->getConfigLoader()->getManifest();
514
            if ($config instanceof CachedConfigCollection) {
515
                $config->setFlush(true);
516
            }
517
        }
518
        // tell modules to sort, now that config is available
519
        $this->getModuleLoader()->getManifest()->sort();
520
521
        // Find default templates
522
        $defaultSet = $this->getThemeResourceLoader()->getSet('$default');
523
        if ($defaultSet instanceof ThemeManifest) {
524
            $defaultSet->setProject(
525
                ModuleManifest::config()->get('project')
526
            );
527
            $defaultSet->init($this->getIncludeTests(), $flush);
528
        }
529
    }
530
531
    /**
532
     * Turn on error handling
533
     */
534
    protected function bootErrorHandling()
535
    {
536
        // Register error handler
537
        $errorHandler = Injector::inst()->get(ErrorHandler::class);
538
        $errorHandler->start();
539
540
        // Register error log file
541
        $errorLog = Environment::getEnv('SS_ERROR_LOG');
542
        if ($errorLog) {
543
            $logger = Injector::inst()->get(LoggerInterface::class);
544
            if ($logger instanceof Logger) {
545
                $logger->pushHandler(new StreamHandler($this->basePath . '/' . $errorLog, Logger::WARNING));
546
            } else {
547
                user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING);
548
            }
549
        }
550
    }
551
552
    public function shutdown()
553
    {
554
    }
555
556
    public function nest()
557
    {
558
        // Clone this kernel, nesting config / injector manifest containers
559
        $kernel = clone $this;
560
        $kernel->setConfigLoader($this->configLoader->nest());
561
        $kernel->setInjectorLoader($this->injectorLoader->nest());
562
        $kernel->nestedFrom = $this;
563
        return $kernel;
564
    }
565
566
    public function activate()
567
    {
568
        $this->configLoader->activate();
569
        $this->injectorLoader->activate();
570
571
        // Self register
572
        $this->getInjectorLoader()
573
            ->getManifest()
574
            ->registerService($this, Kernel::class);
575
        return $this;
576
    }
577
578
    public function getNestedFrom()
579
    {
580
        return $this->nestedFrom;
581
    }
582
583
    public function getContainer()
584
    {
585
        return $this->getInjectorLoader()->getManifest();
586
    }
587
588
    public function setInjectorLoader(InjectorLoader $injectorLoader)
589
    {
590
        $this->injectorLoader = $injectorLoader;
591
        $injectorLoader
592
            ->getManifest()
593
            ->registerService($this, Kernel::class);
594
        return $this;
595
    }
596
597
    public function getInjectorLoader()
598
    {
599
        return $this->injectorLoader;
600
    }
601
602
    public function getClassLoader()
603
    {
604
        return $this->classLoader;
605
    }
606
607
    public function setClassLoader(ClassLoader $classLoader)
608
    {
609
        $this->classLoader = $classLoader;
610
        return $this;
611
    }
612
613
    public function getModuleLoader()
614
    {
615
        return $this->moduleLoader;
616
    }
617
618
    public function setModuleLoader(ModuleLoader $moduleLoader)
619
    {
620
        $this->moduleLoader = $moduleLoader;
621
        return $this;
622
    }
623
624
    public function setEnvironment($environment)
625
    {
626
        if (!in_array($environment, [self::DEV, self::TEST, self::LIVE, null])) {
627
            throw new InvalidArgumentException(
628
                "Director::set_environment_type passed '$environment'.  It should be passed dev, test, or live"
629
            );
630
        }
631
        $this->enviroment = $environment;
632
        return $this;
633
    }
634
635
    public function getConfigLoader()
636
    {
637
        return $this->configLoader;
638
    }
639
640
    public function setConfigLoader($configLoader)
641
    {
642
        $this->configLoader = $configLoader;
643
        return $this;
644
    }
645
646
    public function getThemeResourceLoader()
647
    {
648
        return $this->themeResourceLoader;
649
    }
650
651
    public function setThemeResourceLoader($themeResourceLoader)
652
    {
653
        $this->themeResourceLoader = $themeResourceLoader;
654
        return $this;
655
    }
656
657
    /**
658
     * Returns whether the Kernel has been flushed on boot
659
     *
660
     * @return bool|null null if the kernel hasn't been booted yet
661
     */
662
    public function isFlushed()
663
    {
664
        return $this->flush;
665
    }
666
}
667