Passed
Pull Request — master (#9)
by ANTHONIUS
03:11
created

ApplicationFactory::getConfiguration()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 35
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 8.439
c 0
b 0
f 0
cc 6
eloc 25
nc 3
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the dotfiles project.
7
 *
8
 *     (c) Anthonius Munthi <[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 Dotfiles\Core;
15
16
use Composer\Autoload\ClassLoader;
17
use Dotfiles\Core\DI\Compiler\DefaultPass;
18
use Dotfiles\Core\DI\Parameters;
19
use Dotfiles\Core\Util\Toolkit;
20
use Symfony\Component\Config\ConfigCache;
21
use Symfony\Component\Config\Definition\Processor;
22
use Symfony\Component\Config\FileLocator;
23
use Symfony\Component\Config\Resource\FileResource;
24
use Symfony\Component\DependencyInjection\Container;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Dotfiles\Core\Container. 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...
25
use Symfony\Component\DependencyInjection\ContainerBuilder;
26
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
27
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
28
use Symfony\Component\Dotenv\Dotenv;
29
use Symfony\Component\Finder\Finder;
30
use Symfony\Component\Finder\SplFileInfo;
31
use Symfony\Component\Yaml\Yaml;
32
33
class ApplicationFactory
34
{
35
    /**
36
     * @var Container
37
     */
38
    private $container;
39
40
    /**
41
     * @var bool
42
     */
43
    private $debug = false;
44
45
    /**
46
     * @var string
47
     */
48
    private $env;
49
50
    /**
51
     * @var array
52
     */
53
    private $envFiles = array();
54
55
    /**
56
     * @var Plugin[]
57
     */
58
    private $plugins = array();
59
60
    public function __construct()
61
    {
62
        $files = array(__DIR__.'/Resources/default.env');
63
64
        // $PWD/.env always win
65
        $cwd = getcwd();
66
        if (is_file($file = $cwd.'/.env.dist')) {
67
            $files[] = $file;
68
        }
69
        if (is_file($file = $cwd.'/.env')) {
70
            $files[] = $file;
71
        }
72
73
        $this->envFiles = $files;
74
    }
75
76
    /**
77
     * @return $this
78
     * @codeCoverageIgnore
79
     */
80
    public function boot(): self
81
    {
82
        $this->loadDotEnv();
83
        $this->addAutoload();
84
        $this->loadPlugins();
85
        $this->compileContainer();
86
        $this->ensureDir();
87
88
        return $this;
89
    }
90
91
    public function ensureDir(): void
92
    {
93
        /* @var Parameters $parameters */
94
        $parameters = $this->container->get('dotfiles.parameters');
95
        Toolkit::ensureDir($parameters->get('dotfiles.install_dir'));
96
        Toolkit::ensureDir($parameters->get('dotfiles.bin_dir'));
97
        Toolkit::ensureDir($parameters->get('dotfiles.vendor_dir'));
98
    }
99
100
    /**
101
     * @return Container
102
     */
103
    public function getContainer(): Container
104
    {
105
        return $this->container;
106
    }
107
108
    /**
109
     * @param string $name
110
     *
111
     * @return bool
112
     */
113
    public function hasPlugin(string $name): bool
114
    {
115
        return array_key_exists($name, $this->plugins);
116
    }
117
118
    protected function compileContainer(): void
119
    {
120
        $configs = $this->getConfiguration();
121
        $builder = new ContainerBuilder();
122
        $this->processCoreConfig($configs, $builder);
123
        // processing core configuration
124
125
        /* @var Plugin $plugin */
126
        foreach ($this->plugins as $name => $plugin) {
127
            $pluginConfig[$name] = array_key_exists($name, $configs) ? $configs[$name] : array();
128
129
            $plugin->load($pluginConfig, $builder);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pluginConfig seems to be defined later in this foreach loop on line 127. Are you sure it is defined here?
Loading history...
130
        }
131
132
        $cachePath = $this->getCachePathPrefix().'/container.php';
133
        $cache = new ConfigCache($cachePath, $this->debug);
134
        $className = 'CachedContainer'.$this->getContainerId();
135
136
        // always compile container in dev environment
137
        if (!$cache->isFresh() || 'prod' !== $this->env) {
138
            $builder->addCompilerPass(new DefaultPass());
139
            $builder->compile(true);
140
            $dumper = new PhpDumper($builder);
141
            $resources = $this->envFiles;
142
            array_walk($resources, function (&$item): void {
143
                $item = new FileResource($item);
144
            });
145
            $resources = array_merge($resources, $builder->getResources());
146
            $cache->write($dumper->dump(array('class' => $className)), $resources);
0 ignored issues
show
Bug introduced by
It seems like $dumper->dump(array('class' => $className)) can also be of type array; however, parameter $content of Symfony\Component\Config...kerConfigCache::write() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

146
            $cache->write(/** @scrutinizer ignore-type */ $dumper->dump(array('class' => $className)), $resources);
Loading history...
147
        }
148
        if (!class_exists($className)) {
149
            include $cachePath;
150
        }
151
        $container = new $className();
152
153
        $parameters = new Parameters();
154
        $parameters->setConfigs($container->getParameterBag()->all());
155
        $container->set('dotfiles.parameters', $parameters);
156
        $this->container = $container;
157
    }
158
159
    protected function getCachePathPrefix()
160
    {
161
        // using argv command to differ each dotfiles executable file
162
        global $argv;
163
        $command = $argv[0];
0 ignored issues
show
Unused Code introduced by
The assignment to $command is dead and can be removed.
Loading history...
164
        $cacheDir = getenv('DOTFILES_CACHE_DIR');
165
        $env = getenv('DOTFILES_ENV');
166
167
        return $cacheDir.DIRECTORY_SEPARATOR.$this->getContainerId().DIRECTORY_SEPARATOR.$env;
168
    }
169
170
    protected function getContainerId()
171
    {
172
        global $argv;
173
        $command = $argv[0];
174
175
        return crc32($command);
176
    }
177
178
    private function addAutoload(): void
179
    {
180
        $baseDir = getenv('DOTFILES_BACKUP_DIR');
181
        $autoloadFile = $baseDir.'/vendor/autoload.php';
182
183
        // ignore if files is already loaded in phar file
184
        if (
185
            is_file($autoloadFile)
186
        ) {
187
            include_once $autoloadFile;
188
        }
189
    }
190
191
    private function getConfiguration()
192
    {
193
        $configDir = getenv('DOTFILES_CONFIG_DIR');
194
        if (!is_dir($configDir)) {
195
            return array();
196
        }
197
        $cacheFile = $this->getCachePathPrefix().'/config.php';
198
        $cache = new ConfigCache($cacheFile, $this->debug);
199
        if (!$cache->isFresh() || 'dev' === $this->env) {
200
            $finder = Finder::create()
201
                ->name('*.yaml')
202
                ->name('*.yml')
203
                ->in($configDir)
204
            ;
205
            $configs = array();
206
            $configFiles = array();
207
            /* @var SplFileInfo $file */
208
            foreach ($finder->files() as $file) {
209
                $parsed = Yaml::parseFile($file->getRealPath());
210
                if (is_array($parsed)) {
211
                    $configs = array_merge_recursive($configs, $parsed);
212
                }
213
                $configFiles[] = new FileResource($file->getRealPath());
214
            }
215
            $template = "<?php\n/* ParameterBag Cache File Generated at %s */\nreturn %s;\n";
216
            $time = new \DateTime();
217
            $contents = sprintf(
218
                $template,
219
                $time->format('Y-m-d H:i:s'),
220
                var_export($configs, true)
221
            );
222
            $cache->write($contents, $configFiles);
223
        }
224
225
        return include $cacheFile;
226
    }
227
228
    /**
229
     * Load available plugins directory.
230
     *
231
     * @return array
232
     */
233
    private function loadDirectoryFromAutoloader()
234
    {
235
        $spl = spl_autoload_functions();
236
237
        $dirs = array();
238
        foreach ($spl as $item) {
239
            $object = $item[0];
240
            if (!$object instanceof ClassLoader) {
241
                continue;
242
            }
243
            $temp = array_merge($object->getPrefixes(), $object->getPrefixesPsr4());
244
            foreach ($temp as $name => $dir) {
245
                if (false === strpos($name, 'Dotfiles')) {
246
                    continue;
247
                }
248
                foreach ($dir as $num => $path) {
249
                    $path = realpath($path);
250
                    if ($path && is_dir($path) && !in_array($path, $dirs)) {
251
                        $dirs[] = $path;
252
                    }
253
                }
254
            }
255
        }
256
257
        return $dirs;
258
    }
259
260
    private function loadDotEnv(): void
261
    {
262
        global $argv;
263
        // set temp dir based on OS
264
        putenv('DOTFILES_TEMP_DIR='.sys_get_temp_dir().'/dotfiles');
265
        $dryRun = in_array('--dry-run', $argv) ? true : false;
266
        putenv('DOTFILES_DRY_RUN='.$dryRun);
267
268
        $files = $this->envFiles;
269
        $env = new Dotenv();
270
        if (count($files) > 0) {
271
            call_user_func_array(array($env, 'load'), $files);
272
        }
273
274
        $dev = getenv('DOTFILES_ENV');
275
        if (
276
            'dev' !== $dev && is_file($file = getenv('HOME').'/.dotfiles_profile')) {
277
            $env->load($file);
278
        }
279
280
        $this->debug = (bool) getenv('DOTFILES_DEBUG');
281
        $this->env = getenv('DOTFILES_ENV');
282
283
        $homeDir = getenv('DOTFILES_HOME_DIR');
284
        $backupDir = getenv('DOTFILES_BACKUP_DIR');
285
        if (!getenv('DOTFILES_INSTALL_DIR')) {
286
            putenv('DOTFILES_INSTALL_DIR='.$homeDir.'/.dotfiles');
287
        }
288
289
        if (!getenv('DOTFILES_CONFIG_DIR')) {
290
            putenv('DOTFILES_CONFIG_DIR='.getenv('DOTFILES_BACKUP_DIR').'/config');
291
        }
292
293
        if (!getenv('DOTFILES_CACHE_DIR')) {
294
            putenv('DOTFILES_CACHE_DIR='.$backupDir.'/var/cache');
295
        }
296
        if (!getenv('DOTFILES_LOG_DIR')) {
297
            putenv('DOTFILES_LOG_DIR='.$backupDir.'/var/log');
298
        }
299
    }
300
301
    /**
302
     * Load available plugins.
303
     */
304
    private function loadPlugins(): void
305
    {
306
        $finder = Finder::create();
307
        $finder
308
            ->name('*Plugin.php')
309
        ;
310
        if (is_dir($dir = __DIR__.'/../Plugins')) {
311
            $finder->in(__DIR__.'/../Plugins');
312
        }
313
        $dirs = $this->loadDirectoryFromAutoloader();
314
        $finder->in($dirs);
315
        foreach ($finder->files() as $file) {
316
            $namespace = 'Dotfiles\\Plugins\\'.str_replace('Plugin.php', '', $file->getFileName());
317
            $className = $namespace.'\\'.str_replace('.php', '', $file->getFileName());
318
            if (class_exists($className)) {
319
                /* @var \Dotfiles\Core\Plugin $plugin */
320
                $plugin = new $className();
321
                $this->registerPlugin($plugin);
322
            }
323
        }
324
    }
325
326
    private function processCoreConfig(array $configs, ContainerBuilder $builder): void
327
    {
328
        $dotfileConfig = array_key_exists('dotfiles', $configs) ? $configs['dotfiles'] : array();
329
        $processor = new Processor();
330
        $parameters = $processor->processConfiguration(new Configuration(), $dotfileConfig);
331
        $parameters = array('dotfiles' => $parameters);
332
        Toolkit::flattenArray($parameters);
333
334
        $builder->getParameterBag()->add($parameters);
335
336
        $locator = new FileLocator(__DIR__.'/Resources/config');
337
        $loader = new YamlFileLoader($builder, $locator);
338
        $loader->load('services.yaml');
339
    }
340
341
    /**
342
     * Register plugin.
343
     *
344
     * @param Plugin $plugin
345
     */
346
    private function registerPlugin(Plugin $plugin): void
347
    {
348
        if ($this->hasPlugin($plugin->getName())) {
349
            return;
350
        }
351
352
        $this->plugins[$plugin->getName()] = $plugin;
353
    }
354
}
355