Completed
Push — master ( 528415...2bdb72 )
by Damian
03:25 queued 02:49
created

src/Core/CoreKernel.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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