Passed
Push — fix-8832 ( 2eb5fa )
by Sam
07:48
created

CoreKernel::setInjectorLoader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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