Passed
Push — master ( 247a52...f18ab4 )
by Melech
07:21 queued 03:19
created

Valkyrja::addComponent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 51
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 30
dl 0
loc 51
rs 9.44
c 0
b 0
f 0
cc 2
nc 2
nop 1

How to fix   Long Method   

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
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Application;
15
16
use Valkyrja\Application\Config as ValkyrjaConfig;
17
use Valkyrja\Application\Config\Valkyrja as AppConfig;
18
use Valkyrja\Application\Constant\ComponentClass;
0 ignored issues
show
Bug introduced by
The type Valkyrja\Application\Constant\ComponentClass was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use Valkyrja\Application\Contract\Application;
0 ignored issues
show
Bug introduced by
The type Valkyrja\Application\Contract\Application was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
use Valkyrja\Application\Exception\InvalidArgumentException;
21
use Valkyrja\Cli\Component as CliComponent;
22
use Valkyrja\Cli\Config as CliConfig;
23
use Valkyrja\Config\Config;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Valkyrja\Application\Config. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
24
use Valkyrja\Container\Component as ContainerComponent;
25
use Valkyrja\Container\Config as ContainerConfig;
26
use Valkyrja\Container\Contract\Container;
27
use Valkyrja\Event\Component as EventComponent;
28
use Valkyrja\Event\Config as EventConfig;
29
use Valkyrja\Exception\RuntimeException;
30
use Valkyrja\Http\Component as HttpComponent;
31
use Valkyrja\Http\Config as HttpConfig;
32
use Valkyrja\Support\Directory;
0 ignored issues
show
Bug introduced by
The type Valkyrja\Support\Directory was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
33
use Valkyrja\Type\BuiltIn\Support\Obj;
34
35
use function constant;
36
use function defined;
37
use function is_file;
38
use function is_string;
39
40
/**
41
 * Class Valkyrja.
42
 *
43
 * @author Melech Mizrachi
44
 */
45
class Valkyrja implements Application
46
{
47
    /**
48
     * Application env.
49
     *
50
     * @var class-string<Env>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Env> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Env>.
Loading history...
51
     */
52
    protected string $env;
53
54
    /**
55
     * Application config.
56
     */
57
    protected AppConfig $config;
58
59
    /**
60
     * Get the instance of the container.
61
     */
62
    protected Container $container;
63
64
    /**
65
     * Whether the application was setup.
66
     */
67
    protected bool $setup = false;
68
69
    /**
70
     * Application constructor.
71
     *
72
     * @param class-string<Env>       $env    The env file to use
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Env> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Env>.
Loading history...
73
     * @param class-string<AppConfig> $config The config class to use
74
     */
75
    public function __construct(string $env, string $config)
76
    {
77
        $this->setEnv(env: $env);
78
        $this->setup(config: $config);
79
    }
80
81
    /**
82
     * @inheritDoc
83
     */
84
    public function setup(string $config, bool $force = false): void
85
    {
86
        // If the application was already setup, no need to do it again
87
        if ($this->setup && ! $force) {
88
            return;
89
        }
90
91
        // Avoid re-setting up the app later
92
        $this->setup = true;
93
94
        // Bootstrap debug capabilities
95
        $this->bootstrapConfig(config: $config);
96
    }
97
98
    /**
99
     * @inheritDoc
100
     */
101
    public function addComponent(string $component): void
102
    {
103
        $componentConfig = $component::getConfig();
104
105
        if ($componentConfig !== null) {
106
            $name = strtolower($component::getName());
107
108
            $this->addConfig(
109
                name: $name,
110
                config: $componentConfig::fromEnv($this->env)
111
            );
112
        }
113
114
        $this->config->container->aliases = [
115
            ...$this->config->container->aliases,
116
            ...$component::getContainerAliases(),
117
        ];
118
119
        $this->config->container->services = [
120
            ...$this->config->container->services,
121
            ...$component::getContainerServices(),
122
        ];
123
124
        $this->config->container->contextAliases = [
125
            ...$this->config->container->contextAliases,
126
            ...$component::getContainerContextAliases(),
127
        ];
128
129
        $this->config->container->contextServices = [
130
            ...$this->config->container->contextServices,
131
            ...$component::getContainerContextServices(),
132
        ];
133
134
        $this->config->container->providers = [
135
            ...$this->config->container->providers,
136
            ...$component::getContainerProviders(),
137
        ];
138
139
        $this->config->event->listeners = [
140
            ...$this->config->event->listeners,
141
            ...$component::getEventListeners(),
142
        ];
143
144
        $this->config->cli->routing->controllers = [
145
            ...$this->config->cli->routing->controllers,
146
            ...$component::getCliControllers(),
147
        ];
148
149
        $this->config->http->routing->controllers = [
150
            ...$this->config->http->routing->controllers,
151
            ...$component::getHttpControllers(),
152
        ];
153
    }
154
155
    /**
156
     * @inheritDoc
157
     *
158
     * @return class-string<Env>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Env> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Env>.
Loading history...
159
     */
160
    public function getEnv(): string
161
    {
162
        return $this->env;
163
    }
164
165
    /**
166
     * @inheritDoc
167
     */
168
    public function setEnv(string $env): void
169
    {
170
        if (class_exists($env)) {
171
            // Set the env class to use
172
            $this->env = $env;
173
174
            return;
175
        }
176
177
        throw new InvalidArgumentException('Env must be a valid class');
178
    }
179
180
    /**
181
     * @inheritDoc
182
     */
183
    public function getEnvValue(string $name, mixed $default = null): mixed
184
    {
185
        $env = $this->env;
186
187
        // If the env has this variable defined and the variable isn't null
188
        if (defined($env . '::' . $name)) {
189
            // Return the variable
190
            return constant($env . '::' . $name)
191
                ?? $default;
192
        }
193
194
        // Otherwise return the default
195
        return $default;
196
    }
197
198
    /**
199
     * @inheritDoc
200
     */
201
    public function setConfig(AppConfig $config): static
202
    {
203
        $this->config = $config;
204
205
        return $this;
206
    }
207
208
    /**
209
     * @inheritDoc
210
     */
211
    public function getConfig(): AppConfig
212
    {
213
        return $this->config;
214
    }
215
216
    /**
217
     * @inheritDoc
218
     */
219
    public function getConfigValue(string $name, mixed $default = null): mixed
220
    {
221
        return Obj::getValueDotNotation(
222
            subject: $this->config,
223
            name: $name,
224
            default: $default
225
        );
226
    }
227
228
    /**
229
     * @inheritDoc
230
     */
231
    public function addConfig(string $name, Config $config): void
232
    {
233
        if (! isset($this->config->$name)) {
234
            // Set the config within the application
235
            $this->config->$name = $config;
236
        }
237
    }
238
239
    /**
240
     * @inheritDoc
241
     */
242
    public function getContainer(): Container
243
    {
244
        return $this->container;
245
    }
246
247
    /**
248
     * @inheritDoc
249
     */
250
    public function setContainer(Container $container): static
251
    {
252
        $this->container = $container;
253
254
        return $this;
255
    }
256
257
    /**
258
     * @inheritDoc
259
     */
260
    public function getDebugMode(): bool
261
    {
262
        return $this->config->app->debugMode;
263
    }
264
265
    /**
266
     * @inheritDoc
267
     */
268
    public function getEnvironment(): string
269
    {
270
        return $this->config->app->env;
271
    }
272
273
    /**
274
     * @inheritDoc
275
     */
276
    public function getVersion(): string
277
    {
278
        return static::VERSION;
279
    }
280
281
    /**
282
     * Bootstrap the config.
283
     *
284
     * @param class-string<AppConfig> $config The config class to use
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<AppConfig> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<AppConfig>.
Loading history...
285
     */
286
    protected function bootstrapConfig(string $config): void
287
    {
288
        // Get the cache file
289
        $cacheFilePath = $this->getConfigCacheFilePath();
290
291
        // If we should use the config cache file
292
        if (is_file($cacheFilePath)) {
293
            // Get the config from the cache file's contents
294
            $this->setupFromConfigCacheFile($cacheFilePath);
295
296
            return;
297
        }
298
299
        if (is_a($config, AppConfig::class, true)) {
300
            $this->config = $newConfig = new $config(env: $this->getEnv());
301
302
            $this->setConfig($newConfig);
303
304
            $this->bootstrapComponents();
305
306
            return;
307
        }
308
309
        throw new InvalidArgumentException('Config must be an instance of AppConfig');
310
    }
311
312
    /**
313
     * Get cache file path.
314
     */
315
    protected function getConfigCacheFilePath(): string
316
    {
317
        $cacheFilePath = $this->getEnvValue('CONFIG_CACHE_FILE_PATH', Directory::cachePath('config.php'));
318
319
        if (! is_string($cacheFilePath)) {
320
            throw new InvalidArgumentException('Config cache file path should be a string');
321
        }
322
323
        return $cacheFilePath;
324
    }
325
326
    /**
327
     * Setup the application from a cache file.
328
     */
329
    protected function setupFromConfigCacheFile(string $cacheFilePath): void
330
    {
331
        if (is_file($cacheFilePath)) {
332
            $cacheFileContents = file_get_contents($cacheFilePath);
333
334
            if ($cacheFileContents === '' || $cacheFileContents === false) {
335
                throw new RuntimeException('Invalid cache file contents');
336
            }
337
338
            $this->config = AppConfig::fromSerializedString(cached: $cacheFileContents);
339
340
            return;
341
        }
342
343
        throw new InvalidArgumentException("Invalid $cacheFilePath provided");
344
    }
345
346
    /**
347
     * Bootstrap all the components set in the config.
348
     */
349
    protected function bootstrapComponents(): void
350
    {
351
        $env = $this->env;
352
353
        // Bootstrap required configs
354
        $appConfig       = ValkyrjaConfig::fromEnv(env: $env);
355
        $containerConfig = ContainerConfig::fromEnv(env: $env);
356
        $cliConfig       = CliConfig::fromEnv(env: $env);
357
        $eventConfig     = EventConfig::fromEnv(env: $env);
358
        $httpConfig      = HttpConfig::fromEnv(env: $env);
359
360
        $this->addConfig(name: Component::getName(), config: $appConfig);
361
        $this->addConfig(name: ContainerComponent::getName(), config: $containerConfig);
362
        $this->addConfig(name: CliComponent::getName(), config: $cliConfig);
363
        $this->addConfig(name: EventComponent::getName(), config: $eventConfig);
364
        $this->addConfig(name: HttpComponent::getName(), config: $httpConfig);
365
366
        // All all the components
367
        $this->addComponent(component: ComponentClass::CONTAINER);
368
        $this->addComponent(component: ComponentClass::APPLICATION);
369
        $this->addComponent(component: ComponentClass::ATTRIBUTE);
370
        $this->addComponent(component: ComponentClass::CLI);
371
        $this->addComponent(component: ComponentClass::DISPATCHER);
372
        $this->addComponent(component: ComponentClass::EVENT);
373
        $this->addComponent(component: ComponentClass::HTTP);
374
        $this->addComponent(component: ComponentClass::REFLECTION);
375
376
        foreach ($this->config->app->components as $component) {
377
            $this->addComponent($component);
378
        }
379
380
        $this->config->setConfigFromEnv(env: $env);
381
    }
382
}
383