Completed
Branch 09branch (a008bb)
by Anton
03:16
created

Core::getEnvironment()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * spiral
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\Core;
8
9
use Interop\Container\ContainerInterface as InteropContainer;
10
use Spiral\Console\ConsoleDispatcher;
11
use Spiral\Core\Containers\SpiralContainer;
12
use Spiral\Core\Exceptions\CoreException;
13
use Spiral\Core\Exceptions\DirectoryException;
14
use Spiral\Core\Exceptions\FatalException;
15
use Spiral\Core\Exceptions\ScopeException;
16
use Spiral\Core\HMVC\CoreInterface;
17
use Spiral\Core\Traits\SharedTrait;
18
use Spiral\Debug\SnapshotInterface;
19
use Spiral\Files\FilesInterface;
20
use Spiral\Http\HttpDispatcher;
21
22
/**
23
 * Spiral core responsible for application timezone, memory, represents spiral container (can be
24
 * overwritten with custom instance).
25
 *
26
 * Btw, you can design your architecture any way you want: MVC, MMVC, HMVC, ADR, anything which can
27
 * be invoked and/or routed. Technically you can even invent your own, application specific,
28
 * architecture.
29
 */
30
abstract class Core extends AbstractCore implements DirectoriesInterface
31
{
32
    use SharedTrait;
33
34
    /**
35
     * I need this constant for Symfony Console. :/
36
     */
37
    const VERSION = '0.9.0-rc';
38
39
    /**
40
     * Memory section for bootloaders cache.
41
     */
42
    const BOOT_MEMORY = 'app';
43
44
    /**
45
     * Components to be autoloader while application initialization. This property can be redefined
46
     * on application level.
47
     */
48
    const LOAD = [];
49
50
    /**
51
     * Every application should have defined timezone.
52
     *
53
     * @see setTimezone()
54
     * @see getTimezone()
55
     * @var string
56
     */
57
    private $timezone = 'UTC';
58
59
    /**
60
     * Set of primary application directories.
61
     *
62
     * @see setDirectory()
63
     * @see directory()
64
     * @see getDirectories()
65
     * @var array
66
     */
67
    private $directories = [
68
        'root'        => null,
69
        'public'      => null,
70
        'libraries'   => null,
71
        'framework'   => null,
72
        'application' => null,
73
        'locales'     => null,
74
        'runtime'     => null,
75
        'config'      => null,
76
        'cache'       => null
77
    ];
78
79
    /**
80
     * @var BootloadManager
81
     */
82
    protected $bootloader;
83
84
    /**
85
     * @var EnvironmentInterface
86
     */
87
    protected $environment;
88
89
    /**
90
     * Not set until start method. Can be set manually in bootload.
91
     *
92
     * @var DispatcherInterface
93
     */
94
    protected $dispatcher;
95
96
    /**
97
     * Application memory.
98
     *
99
     * @invisible
100
     * @var MemoryInterface
101
     */
102
    protected $memory;
103
104
    /**
105
     * Components to be autoloader while application initialization. This property can be redefined
106
     * on application level.
107
     *
108
     * @deprecated use LOAD constant instead
109
     * @invisible
110
     */
111
    protected $load = [];
112
113
    /**
114
     * Core class will extend default spiral container and initiate set of directories. You must
115
     * provide application, libraries and root directories to constructor.
116
     *
117
     * @param array              $directories   Core directories list. Every directory must have /
118
     *                                          at the end.
119
     * @param ContainerInterface $container
120
     * @param MemoryInterface    $memory
121
     */
122
    public function __construct(
123
        array $directories,
124
        ContainerInterface $container,
125
        MemoryInterface $memory = null
126
    ) {
127
        $this->container = $container;
128
129
        /*
130
         * Default directories pattern, you can overwrite any directory you want in index file.
131
         */
132
        $this->directories = $directories + [
133
                'framework' => dirname(__DIR__) . '/',
134
                'public'    => $directories['root'] . 'webroot/',
135
                'config'    => $directories['application'] . 'config/',
136
                'views'     => $directories['application'] . 'views/',
137
                'runtime'   => $directories['application'] . 'runtime/',
138
                'cache'     => $directories['application'] . 'runtime/cache/',
139
                'resources' => $directories['application'] . 'resources/',
140
                'locales'   => $directories['application'] . 'resources/locales/'
141
            ];
142
143
        //Every application needs timezone to be set, by default we are using UTC
144
        date_default_timezone_set($this->timezone);
145
146
        //Default memory implementation as fallback
147
        $this->memory = $memory ?? new Memory(
148
                $this->directory('cache'),
149
                $container->get(FilesInterface::class)
150
            );
151
152
        $this->bootloader = new BootloadManager($this->container, $this->memory);
153
    }
154
155
    /**
156
     * Change application timezone.
157
     *
158
     * @param string $timezone
159
     *
160
     * @return $this|self
161
     * @throws CoreException
162
     */
163
    public function setTimezone(string $timezone): Core
164
    {
165
        try {
166
            date_default_timezone_set($timezone);
167
        } catch (\Exception $e) {
168
            throw new CoreException($e->getMessage(), $e->getCode(), $e);
169
        }
170
171
        $this->timezone = $timezone;
172
173
        return $this;
174
    }
175
176
    /**
177
     * Get active application timezone.
178
     *
179
     * @return \DateTimeZone
180
     */
181
    public function getTimezone(): \DateTimeZone
182
    {
183
        return new \DateTimeZone($this->timezone);
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189
    public function hasDirectory(string $alias): bool
190
    {
191
        return isset($this->directories[$alias]);
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197
    public function setDirectory(string $alias, string $path): DirectoriesInterface
198
    {
199
        $this->directories[$alias] = rtrim($path, '/\\') . '/';
200
201
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Spiral\Core\Core) is incompatible with the return type declared by the interface Spiral\Core\DirectoriesInterface::setDirectory of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function directory(string $alias): string
208
    {
209
        if (!$this->hasDirectory($alias)) {
210
            throw new DirectoryException("Undefined directory alias '{$alias}'");
211
        }
212
213
        return $this->directories[$alias];
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219
    public function getDirectories(): array
220
    {
221
        return $this->directories;
222
    }
223
224
    /**
225
     * Change application environment. Attention, already loaded configs would not be altered!
226
     *
227
     * @param EnvironmentInterface $environment
228
     */
229
    public function setEnvironment(EnvironmentInterface $environment)
230
    {
231
        $this->environment = $environment;
232
233
        //Making sure environment is available in container scope
234
        $this->container->bindSingleton(EnvironmentInterface::class, $this->environment);
0 ignored issues
show
Documentation introduced by
$this->environment is of type object<Spiral\Core\EnvironmentInterface>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
235
    }
236
237
    /**
238
     * @return EnvironmentInterface
239
     *
240
     * @throws CoreException
241
     */
242
    public function getEnvironment()
243
    {
244
        if (empty($this->environment)) {
245
            throw new CoreException("Application environment not set");
246
        }
247
248
        return $this->environment;
249
    }
250
251
    /**
252
     * BootloadManager responsible for initiation of your application.
253
     *
254
     * @return BootloadManager
255
     */
256
    public function getBootloader()
257
    {
258
        return $this->bootloader;
259
    }
260
261
    /**
262
     * Handle php shutdown and search for fatal errors.
263
     */
264
    public function handleShutdown()
265
    {
266
        if (!$this->container->has(SnapshotInterface::class)) {
267
            //We are unable to handle exception without proper snaphotter
268
            return;
269
        }
270
271
        if (!empty($error = error_get_last())) {
272
            $this->handleException(new FatalException(
273
                $error['message'], $error['type'], 0, $error['file'], $error['line']
274
            ));
275
        }
276
    }
277
278
    /**
279
     * Convert application error into exception.
280
     *
281
     * @param int    $code
282
     * @param string $message
283
     * @param string $filename
284
     * @param int    $line
285
     *
286
     * @throws \ErrorException
287
     */
288
    public function handleError($code, $message, $filename = '', $line = 0)
289
    {
290
        throw new \ErrorException($message, $code, 0, $filename, $line);
291
    }
292
293
    /**
294
     * Handle exception using associated application dispatcher and snapshot class.
295
     *
296
     * @param \Throwable $exception
297
     *
298
     * @throws \Throwable
299
     */
300
    public function handleException(\Throwable $exception)
301
    {
302
        restore_error_handler();
303
        restore_exception_handler();
304
305
        $snapshot = $this->makeSnapshot($exception);
306
307
        if (empty($snapshot)) {
308
            //No action is required
309
            throw $exception;
310
        }
311
312
        //Let's allow snapshot to report about itself
313
        $snapshot->report();
314
315
        if (!empty($this->dispatcher)) {
316
            //Now dispatcher can handle snapshot it's own way
317
            $this->dispatcher->handleSnapshot($snapshot);
318
        } else {
319
            echo $snapshot->render();
320
        }
321
    }
322
323
    /**
324
     * Create appropriate snapshot for given exception. By default SnapshotInterface binding will be
325
     * used.
326
     *
327
     * Method can return null, in this case exception will be ignored and handled default way.
328
     *
329
     * @param \Throwable $exception
330
     *
331
     * @return SnapshotInterface|null
332
     */
333
    public function makeSnapshot(\Throwable $exception)
334
    {
335
        if (!$this->container->has(SnapshotInterface::class)) {
336
            return null;
337
        }
338
339
        return $this->container->make(SnapshotInterface::class, compact('exception'));
340
    }
341
342
    /**
343
     * Start application using custom or default dispatcher.
344
     *
345
     * @param DispatcherInterface $dispatcher Custom dispatcher.
346
     */
347
    public function start(DispatcherInterface $dispatcher = null)
348
    {
349
        $this->dispatcher = $dispatcher ?? $this->createDispatcher();
350
        $this->dispatcher->start();
351
    }
352
353
    /**
354
     * Bootstrap application. Must be executed before start method.
355
     */
356
    abstract protected function bootstrap();
357
358
    /**
359
     * Create default application dispatcher based on environment value.
360
     *
361
     * @return DispatcherInterface|ConsoleDispatcher|HttpDispatcher
362
     */
363
    protected function createDispatcher()
364
    {
365
        if (php_sapi_name() === 'cli') {
366
            return $this->container->make(ConsoleDispatcher::class);
367
        }
368
369
        return $this->container->make(HttpDispatcher::class);
370
    }
371
372
    /**
373
     * Bootload all registered classes using BootloadManager.
374
     *
375
     * @return $this
376
     */
377
    private function bootload()
378
    {
379
        $this->bootloader->bootload(
380
            $this->load + static::LOAD,
0 ignored issues
show
Deprecated Code introduced by
The property Spiral\Core\Core::$load has been deprecated with message: use LOAD constant instead

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

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

Loading history...
381
            $this->environment->get('CACHE_BOOTLOADERS', false) ? static::BOOT_MEMORY : null
382
        );
383
384
        return $this;
385
    }
386
387
    /**
388
     * Shared container instance (needed for helpers and etc). Attention, method will fail if no
389
     * global container is set.
390
     *
391
     * @return InteropContainer
392
     *
393
     * @throws ScopeException
394
     */
395
    public static function sharedContainer()
396
    {
397
        $container = self::staticContainer();
398
        if (empty($container)) {
399
            throw new ScopeException("No shared/global container scope are set");
400
        }
401
402
        return $container;
403
    }
404
405
    /**
406
     * Initiate application core. Method will set global container if none exists.
407
     *
408
     * @param array                $directories Spiral directories should include root, libraries
409
     *                                          and application directories.
410
     * @param EnvironmentInterface $environment Application specific environment if any.
411
     * @param ContainerInterface   $container   Initial container instance.
412
     * @param bool                 $handleErrors
413
     *
414
     * @return self
415
     */
416
    public static function init(
417
        array $directories,
418
        EnvironmentInterface $environment = null,
419
        ContainerInterface $container = null,
420
        bool $handleErrors = true
421
    ): self {
422
        //Default spiral container
423
        $container = $container ?? new SpiralContainer();
424
425
        //Spiral core interface, @see SpiralContainer
426
        $container->bindSingleton(ContainerInterface::class, $container);
0 ignored issues
show
Documentation introduced by
$container is of type object<Spiral\Core\ContainerInterface>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
427
428
        /**
429
         * @var Core $core
430
         */
431
        $core = new static($directories, $container);
432
433
        //Core binding
434
        $container->bindSingleton(self::class, $core);
0 ignored issues
show
Documentation introduced by
$core is of type object<Spiral\Core\Core>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
435
        $container->bindSingleton(static::class, $core);
0 ignored issues
show
Documentation introduced by
$core is of type object<Spiral\Core\Core>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
436
437
        //Core shared interfaces
438
        $container->bindSingleton(CoreInterface::class, $core);
0 ignored issues
show
Documentation introduced by
$core is of type object<Spiral\Core\Core>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
439
        $container->bindSingleton(DirectoriesInterface::class, $core);
0 ignored issues
show
Documentation introduced by
$core is of type object<Spiral\Core\Core>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
440
441
        //Core shared components
442
        $container->bindSingleton(BootloadManager::class, $core->bootloader);
0 ignored issues
show
Documentation introduced by
$core->bootloader is of type object<Spiral\Core\BootloadManager>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
443
        $container->bindSingleton(MemoryInterface::class, $core->memory);
0 ignored issues
show
Documentation introduced by
$core->memory is of type object<Spiral\Core\MemoryInterface>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
444
445
        //Setting environment (by default - dotenv extension)
446
        if (empty($environment)) {
447
            /*
448
             * Default spiral environment is based on .env file.
449
             */
450
            $environment = new Environment(
451
                $core->directory('root') . '.env',
452
                $container->get(FilesInterface::class),
453
                $core->memory
454
            );
455
        }
456
457
        //Mounting environment to be available for other components
458
        $core->setEnvironment($environment);
459
460
        //Initiating config loader
461
        $container->bindSingleton(
462
            ConfiguratorInterface::class,
463
            $container->make(ConfigFactory::class, ['directory' => $core->directory('config')])
0 ignored issues
show
Documentation introduced by
$container->make(\Spiral...->directory('config'))) is of type *, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
464
        );
465
466
        if ($handleErrors) {
467
            //Error and exception handlers
468
            register_shutdown_function([$core, 'handleShutdown']);
469
            set_error_handler([$core, 'handleError']);
470
            set_exception_handler([$core, 'handleException']);
471
        }
472
473
        $scope = self::staticContainer($container);
474
        try {
475
            //Bootloading our application in a defined GLOBAL container scope
476
            $core->bootload()->bootstrap();
477
        } finally {
478
            self::staticContainer($scope);
479
        }
480
481
        return $core;
482
    }
483
}