Completed
Pull Request — master (#9)
by ANTHONIUS
02:56
created

ApplicationFactory::__construct()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 8
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\CommandPass;
18
use Dotfiles\Core\DI\Compiler\ListenerPass;
19
use Dotfiles\Core\DI\Parameters;
20
use Dotfiles\Core\Util\Toolkit;
21
use Symfony\Component\Config\ConfigCache;
22
use Symfony\Component\Config\Definition\Processor;
23
use Symfony\Component\Config\FileLocator;
24
use Symfony\Component\Config\Resource\FileResource;
25
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...
26
use Symfony\Component\DependencyInjection\ContainerBuilder;
27
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
28
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
29
use Symfony\Component\Dotenv\Dotenv;
30
use Symfony\Component\Finder\Finder;
31
use Symfony\Component\Finder\SplFileInfo;
32
use Symfony\Component\Yaml\Yaml;
33
34
class ApplicationFactory
35
{
36
    /**
37
     * @var Container
38
     */
39
    private $container;
40
41
    /**
42
     * @var bool
43
     */
44
    private $debug = false;
45
46
    /**
47
     * @var string
48
     */
49
    private $env;
50
51
    /**
52
     * @var array
53
     */
54
    private $envFiles = array();
55
56
    /**
57
     * @var Plugin[]
58
     */
59
    private $plugins = array();
60
61
    public function __construct()
62
    {
63
        $files = array(__DIR__.'/Resources/default.env');
64
65
        if (is_file($file = getenv('HOME').'/.dotfiles_profile')) {
66
            $files[] = $file;
67
        }
68
69
        // $PWD/.env always win
70
        $cwd = getcwd();
71
        if (is_file($file = $cwd.'/.env.dist')) {
72
            $files[] = $file;
73
        }
74
        if (is_file($file = $cwd.'/.env')) {
75
            $files[] = $file;
76
        }
77
78
        $this->envFiles = $files;
79
    }
80
81
    /**
82
     * @return $this
83
     */
84
    public function boot(): self
85
    {
86
        $this->loadDotEnv();
87
        $this->addAutoload();
88
        $this->loadPlugins();
89
        $this->compileContainer();
90
91
        return $this;
92
    }
93
94
    /**
95
     * @return Container
96
     */
97
    public function getContainer(): Container
98
    {
99
        return $this->container;
100
    }
101
102
    /**
103
     * @param string $name
104
     *
105
     * @return bool
106
     */
107
    public function hasPlugin(string $name): bool
108
    {
109
        return array_key_exists($name, $this->plugins);
110
    }
111
112
    private function addAutoload(): void
113
    {
114
        $baseDir = Toolkit::getBaseDir();
115
        $autoloadFile = $baseDir.'/vendor/autoload.php';
116
117
        // ignore if files is already loaded in phar file
118
        if (
119
            is_file($autoloadFile) &&
120
            (false === strpos($autoloadFile, 'phar:///'))
121
        ) {
122
            include_once $autoloadFile;
123
        }
124
    }
125
126
    private function compileContainer(): void
127
    {
128
        $configs = $this->getConfiguration();
129
        //$paramaterBag = new ParameterBag();
130
        $builder = new ContainerBuilder();
131
        $this->processCoreConfig($configs, $builder);
132
        // processing core configuration
133
134
        /* @var Plugin $plugin */
135
        foreach ($this->plugins as $name => $plugin) {
136
            $pluginConfig = array_key_exists($name, $configs) ? $configs[$name] : array();
137
            $plugin->load($pluginConfig, $builder);
138
        }
139
140
        $cachePath = $this->getCachePathPrefix().'/container.php';
141
        $cache = new ConfigCache($cachePath, $this->debug);
142
        if (!$cache->isFresh() || 'dev' == $this->env) {
143
            $builder->addCompilerPass(new CommandPass());
144
            $builder->addCompilerPass(new ListenerPass());
145
            $builder->compile(true);
146
            $dumper = new PhpDumper($builder);
147
            $resources = $this->envFiles;
148
            array_walk($resources, function (&$item): void {
149
                $item = new FileResource($item);
150
            });
151
            $resources = array_merge($resources, $builder->getResources());
152
            $cache->write($dumper->dump(array('class' => 'CachedContainer')), $resources);
0 ignored issues
show
Bug introduced by
It seems like $dumper->dump(array('cla... => 'CachedContainer')) 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

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