Completed
Branch 09branch (0a5c88)
by Anton
05:50
created

Core::getTimezone()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
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\ControllerException;
13
use Spiral\Core\Exceptions\CoreException;
14
use Spiral\Core\Exceptions\DirectoryException;
15
use Spiral\Core\Exceptions\FatalException;
16
use Spiral\Core\Exceptions\ScopeException;
17
use Spiral\Core\HMVC\ControllerInterface;
18
use Spiral\Core\HMVC\CoreInterface;
19
use Spiral\Core\Traits\SharedTrait;
20
use Spiral\Debug\SnapshotInterface;
21
use Spiral\Debug\Traits\BenchmarkTrait;
22
use Spiral\Files\FilesInterface;
23
use Spiral\Http\HttpDispatcher;
24
25
/**
26
 * Spiral core responsible for application timezone, memory, represents spiral container (can be
27
 * overwritten with custom instance).
28
 *
29
 * Btw, you can design your architecture any way you want: MVC, MMVC, HMVC, ADR, anything which can
30
 * be invoked and/or routed. Technically you can even invent your own, application specific,
31
 * architecture.
32
 */
33
abstract class Core extends Component implements CoreInterface, DirectoriesInterface
34
{
35
    use SharedTrait, BenchmarkTrait;
36
37
    /**
38
     * I need this constant for Symfony Console. :/
39
     */
40
    const VERSION = '0.9.0-rc';
41
42
    /**
43
     * Memory section for bootloaders cache.
44
     */
45
    const BOOT_MEMORY = 'app';
46
47
    /**
48
     * Components to be autoloader while application initialization. This property can be redefined
49
     * on application level.
50
     */
51
    const LOAD = [];
52
53
    /**
54
     * Every application should have defined timezone.
55
     *
56
     * @see setTimezone()
57
     * @see getTimezone()
58
     * @var string
59
     */
60
    private $timezone = 'UTC';
61
62
    /**
63
     * Set of primary application directories.
64
     *
65
     * @see setDirectory()
66
     * @see directory()
67
     * @see getDirectories()
68
     * @var array
69
     */
70
    private $directories = [
71
        'root'        => null,
72
        'public'      => null,
73
        'libraries'   => null,
74
        'framework'   => null,
75
        'application' => null,
76
        'locales'     => null,
77
        'runtime'     => null,
78
        'config'      => null,
79
        'cache'       => null
80
    ];
81
82
    /**
83
     * @var BootloadManager
84
     */
85
    protected $bootloader;
86
87
    /**
88
     * Not set until start method. Can be set manually in bootload.
89
     *
90
     * @var DispatcherInterface
91
     */
92
    protected $dispatcher;
93
94
    /**
95
     * @var EnvironmentInterface
96
     */
97
    protected $environment;
98
99
    /**
100
     * @invisible
101
     *
102
     * @var ContainerInterface
103
     */
104
    protected $container;
105
106
    /**
107
     * Application memory.
108
     *
109
     * @invisible
110
     * @var MemoryInterface
111
     */
112
    protected $memory;
113
114
    /**
115
     * Components to be autoloader while application initialization. This property can be redefined
116
     * on application level.
117
     *
118
     * @invisible
119
     */
120
    protected $load = [];
121
122
    /**
123
     * Core class will extend default spiral container and initiate set of directories. You must
124
     * provide application, libraries and root directories to constructor.
125
     *
126
     * @param array              $directories   Core directories list. Every directory must have /
127
     *                                          at the end.
128
     * @param ContainerInterface $container
129
     * @param MemoryInterface    $memory
130
     */
131
    public function __construct(
132
        array $directories,
133
        ContainerInterface $container,
134
        MemoryInterface $memory = null
135
    ) {
136
        $this->container = $container;
137
138
        /*
139
         * Default directories pattern, you can overwrite any directory you want in index file.
140
         */
141
        $this->directories = $directories + [
142
                'framework' => dirname(__DIR__) . '/',
143
                'public'    => $directories['root'] . 'webroot/',
144
                'config'    => $directories['application'] . 'config/',
145
                'views'     => $directories['application'] . 'views/',
146
                'runtime'   => $directories['application'] . 'runtime/',
147
                'cache'     => $directories['application'] . 'runtime/cache/',
148
                'resources' => $directories['application'] . 'resources/',
149
                'locales'   => $directories['application'] . 'resources/locales/'
150
            ];
151
152
        //Every application needs timezone to be set, by default we are using UTC
153
        date_default_timezone_set($this->timezone);
154
155
        //Default memory implementation as fallback
156
        $this->memory = $memory ?? new Memory(
157
                $this->directory('cache'),
158
                $container->get(FilesInterface::class)
159
            );
160
161
        $this->bootloader = new BootloadManager($this->container, $this->memory);
162
    }
163
164
    /**
165
     * Change application timezone.
166
     *
167
     * @param string $timezone
168
     *
169
     * @return $this|self
170
     * @throws CoreException
171
     */
172
    public function setTimezone(string $timezone): Core
173
    {
174
        try {
175
            date_default_timezone_set($timezone);
176
        } catch (\Exception $e) {
177
            throw new CoreException($e->getMessage(), $e->getCode(), $e);
178
        }
179
180
        $this->timezone = $timezone;
181
182
        return $this;
183
    }
184
185
    /**
186
     * Get active application timezone.
187
     *
188
     * @return \DateTimeZone
189
     */
190
    public function getTimezone(): \DateTimeZone
191
    {
192
        return new \DateTimeZone($this->timezone);
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198
    public function hasDirectory(string $alias): bool
199
    {
200
        return isset($this->directories[$alias]);
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function setDirectory(string $alias, string $path): DirectoriesInterface
207
    {
208
        $this->directories[$alias] = rtrim($path, '/\\') . '/';
209
210
        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...
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     */
216
    public function directory(string $alias): string
217
    {
218
        if (!$this->hasDirectory($alias)) {
219
            throw new DirectoryException("Undefined directory alias '{$alias}'");
220
        }
221
222
        return $this->directories[$alias];
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function getDirectories(): array
229
    {
230
        return $this->directories;
231
    }
232
233
    /**
234
     * Change application environment. Attention, already loaded configs would not be altered!
235
     *
236
     * @param EnvironmentInterface $environment
237
     */
238
    public function setEnvironment(EnvironmentInterface $environment)
239
    {
240
        $this->environment = $environment;
241
242
        //Making sure environment is available in container scope
243
        $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...
244
    }
245
246
    /**
247
     * @return EnvironmentInterface
248
     *
249
     * @throws CoreException
250
     */
251
    public function environment()
252
    {
253
        if (empty($this->environment)) {
254
            throw new CoreException("Application environment not set");
255
        }
256
257
        return $this->environment;
258
    }
259
260
    /**
261
     * BootloadManager responsible for initiation of your application.
262
     *
263
     * @return BootloadManager
264
     */
265
    public function bootloader()
266
    {
267
        return $this->bootloader;
268
    }
269
270
    /**
271
     * {@inheritdoc}
272
     *
273
     * @todo Move to AbstractCore?
274
     */
275
    public function callAction(
276
        string $controller,
277
        string $action = null,
278
        array $parameters = [],
279
        array $scope = []
280
    ) {
281
        if (!class_exists($controller)) {
282
            throw new ControllerException(
283
                "No such controller '{$controller}' found",
284
                ControllerException::NOT_FOUND
285
            );
286
        }
287
288
        $benchmark = $this->benchmark('callAction', $controller . '::' . ($action ?? '~default~'));
289
290
        //Making sure that all static functionality works well
291
        $iocScope = self::staticContainer($this->container);
292
293
        //Working with container scope
294
        foreach ($scope as $alias => &$target) {
295
            $target = $this->container->replace($alias, $target);
296
            unset($target);
297
        }
298
299
        try {
300
            //Getting instance of controller
301
            $controller = $this->container->get($controller);
302
303
            if (!$controller instanceof ControllerInterface) {
304
                throw new ControllerException(
305
                    "No such controller '{$controller}' found",
306
                    ControllerException::NOT_FOUND
307
                );
308
            }
309
310
            return $controller->callAction($action, $parameters);
311
        } finally {
312
            $this->benchmark($benchmark);
313
314
            //Restoring container scope
315
            foreach (array_reverse($scope) as $payload) {
316
                $this->container->restore($payload);
317
            }
318
319
            //Restoring shared container to it's original state
320
            self::staticContainer($iocScope);
321
        }
322
    }
323
324
    /**
325
     * Handle php shutdown and search for fatal errors.
326
     */
327
    public function handleShutdown()
328
    {
329
        if (!$this->container->has(SnapshotInterface::class)) {
330
            //We are unable to handle exception without proper snaphotter
331
            return;
332
        }
333
334
        if (!empty($error = error_get_last())) {
335
            $this->handleException(new FatalException(
336
                $error['message'], $error['type'], 0, $error['file'], $error['line']
337
            ));
338
        }
339
    }
340
341
    /**
342
     * Convert application error into exception.
343
     *
344
     * @param int    $code
345
     * @param string $message
346
     * @param string $filename
347
     * @param int    $line
348
     *
349
     * @throws \ErrorException
350
     */
351
    public function handleError($code, $message, $filename = '', $line = 0)
352
    {
353
        throw new \ErrorException($message, $code, 0, $filename, $line);
354
    }
355
356
    /**
357
     * Handle exception using associated application dispatcher and snapshot class.
358
     *
359
     * @param \Throwable $exception
360
     *
361
     * @throws \Throwable
362
     */
363
    public function handleException(\Throwable $exception)
364
    {
365
        restore_error_handler();
366
        restore_exception_handler();
367
368
        $snapshot = $this->makeSnapshot($exception);
369
370
        if (empty($snapshot)) {
371
            //No action is required
372
            throw $exception;
373
        }
374
375
        //Let's allow snapshot to report about itself
376
        $snapshot->report();
377
378
        if (!empty($this->dispatcher)) {
379
            //Now dispatcher can handle snapshot it's own way
380
            $this->dispatcher->handleSnapshot($snapshot);
381
        } else {
382
            echo $snapshot->render();
383
        }
384
    }
385
386
    /**
387
     * Create appropriate snapshot for given exception. By default SnapshotInterface binding will be
388
     * used.
389
     *
390
     * Method can return null, in this case exception will be ignored and handled default way.
391
     *
392
     * @param \Throwable $exception
393
     *
394
     * @return SnapshotInterface|null
395
     */
396
    public function makeSnapshot(\Throwable $exception)
397
    {
398
        if (!$this->container->has(SnapshotInterface::class)) {
399
            return null;
400
        }
401
402
        return $this->container->make(SnapshotInterface::class, compact('exception'));
403
    }
404
405
    /**
406
     * Start application using custom or default dispatcher.
407
     *
408
     * @param DispatcherInterface $dispatcher Custom dispatcher.
409
     */
410
    public function start(DispatcherInterface $dispatcher = null)
411
    {
412
        $this->dispatcher = $dispatcher ?? $this->createDispatcher();
413
        $this->dispatcher->start();
414
    }
415
416
    /**
417
     * Bootstrap application. Must be executed before start method.
418
     */
419
    abstract protected function bootstrap();
420
421
    /**
422
     * Create default application dispatcher based on environment value.
423
     *
424
     * @return DispatcherInterface|ConsoleDispatcher|HttpDispatcher
425
     */
426
    protected function createDispatcher()
427
    {
428
        if (php_sapi_name() === 'cli') {
429
            return $this->container->make(ConsoleDispatcher::class);
430
        }
431
432
        return $this->container->make(HttpDispatcher::class);
433
    }
434
435
    /**
436
     * Bootload all registered classes using BootloadManager.
437
     *
438
     * @return $this
439
     */
440
    private function bootload()
441
    {
442
        $this->bootloader->bootload(
443
            $this->load + static::LOAD,
444
            $this->environment->get('CACHE_BOOTLOADERS', false) ? static::BOOT_MEMORY : null
445
        );
446
447
        return $this;
448
    }
449
450
    /**
451
     * Shared container instance (needed for helpers and etc). Attention, method will fail if no
452
     * global container is set.
453
     *
454
     * @return InteropContainer
455
     *
456
     * @throws ScopeException
457
     */
458
    public static function sharedContainer()
459
    {
460
        $container = self::staticContainer();
461
        if (empty($container)) {
462
            throw new ScopeException("No shared/global container scope are set");
463
        }
464
465
        return $container;
466
    }
467
468
    /**
469
     * Initiate application core. Method will set global container if none exists.
470
     *
471
     * @param array                $directories Spiral directories should include root, libraries
472
     *                                          and application directories.
473
     * @param EnvironmentInterface $environment Application specific environment if any.
474
     * @param ContainerInterface   $container   Initial container instance.
475
     * @param bool                 $handleErrors
476
     *
477
     * @return self
478
     */
479
    public static function init(
480
        array $directories,
481
        EnvironmentInterface $environment = null,
482
        ContainerInterface $container = null,
483
        bool $handleErrors = true
484
    ): self {
485
        //Default spiral container
486
        $container = $container ?? new SpiralContainer();
487
488
        //Spiral core interface, @see SpiralContainer
489
        $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...
490
491
        /**
492
         * @var Core $core
493
         */
494
        $core = new static($directories, $container);
495
496
        //Core binding
497
        $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...
498
        $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...
499
500
        //Core shared interfaces
501
        $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...
502
        $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...
503
504
        //Core shared components
505
        $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...
506
        $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...
507
508
        //Setting environment (by default - dotenv extension)
509
        if (empty($environment)) {
510
            /*
511
             * Default spiral environment is based on .env file.
512
             */
513
            $environment = new Environment(
514
                $core->directory('root') . '.env',
515
                $container->get(FilesInterface::class),
516
                $core->memory
517
            );
518
        }
519
520
        //Mounting environment to be available for other components
521
        $core->setEnvironment($environment);
522
523
        //Initiating config loader
524
        $container->bindSingleton(
525
            ConfiguratorInterface::class,
526
            $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...
527
        );
528
529
        if ($handleErrors) {
530
            //Error and exception handlers
531
            register_shutdown_function([$core, 'handleShutdown']);
532
            set_error_handler([$core, 'handleError']);
533
            set_exception_handler([$core, 'handleException']);
534
        }
535
536
        $scope = self::staticContainer($container);
537
        try {
538
            //Bootloading our application in a defined GLOBAL container scope
539
            $core->bootload()->bootstrap();
540
        } finally {
541
            self::staticContainer($scope);
542
        }
543
544
        return $core;
545
    }
546
}