These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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; |
||
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
|
|||
56 | $route = reset($request->resolve()); |
||
0 ignored issues
–
show
|
|||
57 | $id = reset(explode('/', $route, 2)); |
||
0 ignored issues
–
show
|
|||
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'])) { |
||
95 | unset($config['components'][$id]); |
||
96 | $controllers[$id] = $def; |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
$controllers was never initialized. Although not strictly required by PHP, it is generally a good practice to add $controllers = array(); before regardless.
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code. Let’s take a look at an example: foreach ($collection as $item) {
$myArray['foo'] = $item->getFoo();
if ($item->hasBar()) {
$myArray['bar'] = $item->getBar();
}
// do something with $myArray
}
As you can see in this example, the array This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.
Loading history...
|
|||
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'])) { |
||
108 | $def['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); |
||
0 ignored issues
–
show
It seems like
$srcdir defined by \Yii::getAlias('@root/' ...ckage['src'] ?: 'src')) on line 195 can also be of type boolean ; however, yii\BaseYii::setAlias() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
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
|
|||
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'])) { |
||
0 ignored issues
–
show
The expression
$this->passthru('compose...y('install', '--ansi')) of type integer|null is loosely compared to true ; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
Loading history...
|
|||
234 | throw new InvalidParamException('Failed initialize project with composer install'); |
||
235 | } |
||
236 | } |
||
237 | $vendors[] = $this->buildRootPath('vendor'); |
||
238 | |||
239 | foreach ($vendors as $vendor) { |
||
240 | foreach (['common', '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."); |
||
358 | } |
||
359 | |||
360 | public function buildRootPath($subpath) |
||
361 | { |
||
362 | return $this->getRootDir() . DIRECTORY_SEPARATOR . $subpath; |
||
363 | } |
||
364 | } |
||
365 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: