Completed
Push — master ( 4ad6bd...3873e4 )
by Ingo
11:53
created

CoreKernel::getDatabaseConfig()   F

Complexity

Conditions 10
Paths 257

Size

Total Lines 44
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 23
nc 257
nop 0
dl 0
loc 44
rs 3.1304
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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()
0 ignored issues
show
Coding Style introduced by
sessionEnvironment uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
sessionEnvironment uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

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