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

ApplicationFactory   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 47
dl 0
loc 301
rs 8.439
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
C loadDotEnv() 0 32 7
A getCachePathPrefix() 0 8 1
A addAutoload() 0 11 3
B getConfiguration() 0 35 6
B compileContainer() 0 41 6
A loadPlugins() 0 18 4
A hasPlugin() 0 3 1
A getContainer() 0 3 1
A __construct() 0 18 4
D loadDirectoryFromAutoloader() 0 25 9
A processCoreConfig() 0 13 2
A registerPlugin() 0 7 2
A boot() 0 8 1

How to fix   Complexity   

Complex Class

Complex classes like ApplicationFactory often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ApplicationFactory, and based on these observations, apply Extract Interface, too.

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

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