Passed
Push — master ( 434341...b16aa1 )
by Andrii
03:18
created

Starter::requireAll()   C

Complexity

Conditions 9
Paths 27

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
cc 9
eloc 17
nc 27
nop 0
dl 0
loc 24
ccs 0
cts 23
cp 0
crap 90
rs 5.3563
c 0
b 0
f 0
1
<?php
2
/**
3
 * Automation tool mixed with code generator for easier continuous development
4
 *
5
 * @link      https://github.com/hiqdev/hidev
6
 * @package   hidev
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2015-2018, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hidev\base;
12
13
use Dotenv\Dotenv;
0 ignored issues
show
Bug introduced by
The type Dotenv\Dotenv 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...
14
use hidev\components\Request;
15
use hidev\helpers\ConfigPlugin;
16
use hidev\helpers\FileHelper;
17
use hidev\helpers\Helper;
18
use Symfony\Component\Yaml\Yaml;
19
use Yii;
20
use yii\base\InvalidParamException;
21
use yii\helpers\ArrayHelper;
22
23
/**
24
 * Application starter.
25
 * Chdirs to the project's root directory and loads dependencies and configs.
26
 *
27
 * XXX it's important to distinguish:
28
 * - goals definitions (hidev config) - YAML files
29
 * - application config - PHP files
30
 * @author Andrii Vasyliev <[email protected]>
31
 */
32
class Starter
33
{
34
    /**
35
     * @var string absolute path to the project root directory
36
     */
37
    private $_rootDir;
38
39
    /**
40
     * @var array goals definitions
41
     */
42
    private $goals = [];
43
44
    /**
45
     * @var array application config files
46
     */
47
    private $appFiles = ['@hidev/config/basis.php'];
48
49
    /**
50
     * Make action.
51
     */
52
    public function __construct()
53
    {
54
        $request = new Request();
55
        $this->scriptFile = $request->getScriptFile();
0 ignored issues
show
Bug Best Practice introduced by
The property scriptFile does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
56
        $route = reset($request->resolve());
57
        $id = reset(explode('/', $route, 2));
58
        if (in_array($id, ['init'], true)) {
59
            $this->noProject();
60
        } else {
61
            $this->startProject();
62
        }
63
    }
64
65
    public function noProject()
66
    {
67
        $this->setRootDir(getcwd());
68
        $this->addAliases();
69
    }
70
71
    public function startProject()
72
    {
73
        $this->getRootDir();
74
        $this->addAutoloader();
75
        $this->loadEnv();
76
        $this->loadGoals();
77
        $this->addAliases();
78
        $this->requireAll();
79
        $this->includeAll();
80
        $this->moreConfig();
81
    }
82
83
    public function getConfig()
84
    {
85
        $config = ArrayHelper::merge($this->readConfig(), [
86
            'components' => $this->goals,
87
        ]);
88
89
        $config['components']['request']['scriptFile'] = $this->scriptFile;
90
        unset($config['components']['include']);
91
        unset($config['components']['plugins']);
92
93
        foreach ($config['components'] as $id => $def) {
94
            if (empty($def['class']) && empty($def['__class'])) {
95
                unset($config['components'][$id]);
96
                $controllers[$id] = $def;
97
            }
98
        }
99
        if (!empty($controllers)) {
100
            $config = ArrayHelper::merge($config, [
101
                'controllerMap' => $controllers,
102
            ]);
103
        }
104
105
        if (!empty($config['controllerMap'])) {
106
            foreach ($config['controllerMap'] as &$def) {
107
                if (is_array($def) && empty($def['class']) && empty($def['__class'])) {
108
                    $def[Helper::isYii20() ? 'class' : '__class'] = \hidev\console\CommonController::class;
109
                }
110
            }
111
        }
112
113
        $interpolator = new Interpolator();
114
        $interpolator->interpolate($config);
115
116
        return $config;
117
    }
118
119
    public function readConfig()
120
    {
121
        $config = [];
122
        foreach ($this->appFiles as $file) {
123
            $path = Yii::getAlias($file);
124
            $config = ArrayHelper::merge($config, require $path);
125
        }
126
127
        return $config;
128
    }
129
130
    public function getGoals()
131
    {
132
        return $this->goals;
133
    }
134
135
    public function addAutoloader()
136
    {
137
        $autoloader = './vendor/autoload.php';
138
        if (file_exists($autoloader)) {
139
            if (Helper::isYii20()) {
140
                spl_autoload_unregister(['Yii', 'autoload']);
141
            }
142
            require $autoloader;
143
            if (Helper::isYii20()) {
144
                spl_autoload_register(['Yii', 'autoload'], true, true);
145
            }
146
        }
147
    }
148
149
    private function loadEnv()
150
    {
151
        if (file_exists('.env') && class_exists(Dotenv::class)) {
152
            $dotenv = new Dotenv('.');
153
            $dotenv->load();
154
        }
155
    }
156
157
    private function loadGoals()
158
    {
159
        $this->includeGoals('hidev.yml');
160
        if (file_exists('hidev-local.yml')) {
161
            $this->includeGoals('hidev-local.yml');
162
        }
163
    }
164
165
    private function includeGoals($paths)
166
    {
167
        foreach ((array) $paths as $path) {
168
            $this->goals = ArrayHelper::merge(
169
                $this->goals,
170
                $this->readYaml($path)
171
            );
172
        }
173
    }
174
175
    private function readYaml($path)
176
    {
177
        return Yaml::parse(FileHelper::read($path));
178
    }
179
180
    /**
181
     * Adds aliases:
182
     * - @root alias to current project root dir
183
     * - @hidev own alias
184
     * - current package namespace for it could be used from hidev
185
     * - aliases listed in config.
186
     */
187
    private function addAliases()
188
    {
189
        Yii::setAlias('@root', $this->getRootDir());
190
        Yii::setAlias('@hidev', dirname(__DIR__));
191
192
        $package = $this->goals['package'];
193
        $alias  = isset($package['namespace']) ? strtr($package['namespace'], '\\', '/') : '';
194
        if ($alias && !Yii::getAlias('@' . $alias, false)) {
195
            $srcdir = Yii::getAlias('@root/' . ($package['src'] ?: 'src'));
196
            Yii::setAlias($alias, $srcdir);
197
        }
198
        $aliases = $this->goals['aliases'];
199
        if (!empty($aliases) && is_array($aliases)) {
200
            foreach ($aliases as $alias => $path) {
201
                if (!$this->hasAlias($alias)) {
202
                    Yii::setAlias($alias, $path);
203
                }
204
            }
205
        }
206
    }
207
208
    private function hasAlias($alias, $exact = true)
0 ignored issues
show
Unused Code introduced by
The parameter $exact is not used and could be removed. ( Ignorable by Annotation )

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

208
    private function hasAlias($alias, /** @scrutinizer ignore-unused */ $exact = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
209
    {
210
        $pos = strpos($alias, '/');
211
212
        return $pos === false ? isset(Yii::$aliases[$alias]) : isset(Yii::$aliases[substr($alias, 0, $pos)][$alias]);
213
    }
214
215
    /**
216
     * - install configured plugins and register their app config
217
     * - install project dependencies and register
218
     * - register application config files.
219
     */
220
    private function requireAll()
221
    {
222
        $vendors = [];
223
        $plugins = $this->goals['plugins'];
224
        if ($plugins) {
225
            $file = File::create('.hidev/composer.json');
226
            $data = ArrayHelper::merge($file->load(), ['require' => $plugins]);
227
            if ($file->save($data) || !is_dir('.hidev/vendor')) {
228
                $this->updateDotHidev();
229
            }
230
            $vendors[] = $this->buildRootPath('.hidev/vendor');
231
        }
232
        if ($this->needsComposerInstall()) {
233
            if ($this->passthru('composer', ['install', '--ansi'])) {
234
                throw new InvalidParamException('Failed initialize project with composer install');
0 ignored issues
show
Deprecated Code introduced by
The class yii\base\InvalidParamException has been deprecated: since 2.0.14. Use [[InvalidArgumentException]] instead. ( Ignorable by Annotation )

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

234
                throw /** @scrutinizer ignore-deprecated */ new InvalidParamException('Failed initialize project with composer install');
Loading history...
235
            }
236
        }
237
        $vendors[] = $this->buildRootPath('vendor');
238
239
        foreach ($vendors as $vendor) {
240
            foreach (['console', 'hidev'] as $name) {
241
                $path = ConfigPlugin::path($name, $vendor);
242
                if (file_exists($path)) {
243
                    $this->appFiles[] = $path;
244
                }
245
            }
246
        }
247
    }
248
249
    /**
250
     * Update action.
251
     * @return int exit code
252
     */
253
    public function updateDotHidev()
254
    {
255
        if (file_exists('.hidev/composer.json')) {
256
            return $this->passthru('composer', ['update', '-d', '.hidev', '--prefer-source', '--ansi']);
257
        }
258
    }
259
260
    /**
261
     * Passthru command.
262
     * @param string $command
263
     * @param array $args
264
     * @return int exit code
265
     */
266
    private function passthru($command, $args)
267
    {
268
        $binary = new BinaryPhp([
269
            'name' => $command,
270
        ]);
271
272
        return $binary->passthru($args);
273
    }
274
275
    private function needsComposerInstall()
276
    {
277
        if (file_exists('vendor')) {
278
            return false;
279
        }
280
        if (!file_exists('composer.json')) {
281
            return false;
282
        }
283
284
        return true;
285
286
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
287
            $data = File::create('composer.json')->load();
288
            foreach (['require', 'require-dev'] as $key) {
289
                if (isset($data[$key])) {
290
                    foreach ($data[$key] as $package => $version) {
291
                        list(, $name) = explode('/', $package);
292
                        if (strncmp($name, 'hidev-', 6) === 0) {
293
                            return true;
294
                        }
295
                    }
296
                }
297
            }
298
299
            return false;
300
        */
301
    }
302
303
    /**
304
     * Include all configured includes.
305
     */
306
    private function includeAll()
307
    {
308
        $config = $this->readConfig();
309
        $files = array_merge(
310
            (array) $this->goals['include'],
311
            (array) $config['components']['include']
312
        );
313
        $this->includeGoals($files);
314
    }
315
316
    /**
317
     * Registers more application config to load.
318
     */
319
    private function moreConfig()
320
    {
321
        $paths = $this->goals['config'];
322
        foreach ((array) $paths as $path) {
323
            if ($path) {
324
                $this->appFiles[] = $path;
325
            }
326
        }
327
    }
328
329
    public function setRootDir($value)
330
    {
331
        $this->_rootDir = $value;
332
    }
333
334
    public function getRootDir()
335
    {
336
        if ($this->_rootDir === null) {
337
            $this->_rootDir = $this->findRootDir();
338
        }
339
340
        return $this->_rootDir;
341
    }
342
343
    /**
344
     * Chdirs to project's root by looking for config file in the current directory and up.
345
     * @throws InvalidParamException when failed to find
346
     * @return string path to the root directory of hidev project
347
     */
348
    private function findRootDir()
349
    {
350
        $configFile = 'hidev.yml';
351
        for ($i = 0; $i < 9; ++$i) {
352
            if (file_exists($configFile)) {
353
                return getcwd();
354
            }
355
            chdir('..');
356
        }
357
        throw new InvalidParamException("Not a hidev project (or any of the parent directories).\nUse `hidev init` to initialize hidev project.");
0 ignored issues
show
Deprecated Code introduced by
The class yii\base\InvalidParamException has been deprecated: since 2.0.14. Use [[InvalidArgumentException]] instead. ( Ignorable by Annotation )

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

357
        throw /** @scrutinizer ignore-deprecated */ new InvalidParamException("Not a hidev project (or any of the parent directories).\nUse `hidev init` to initialize hidev project.");
Loading history...
358
    }
359
360
    public function buildRootPath($subpath)
361
    {
362
        return $this->getRootDir() . DIRECTORY_SEPARATOR . $subpath;
363
    }
364
}
365