Completed
Push — master ( 09d6a5...ab8255 )
by Andrii
02:27
created

PackageManager::buildPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
/*
4
 * Composer plugin for bower/npm assets
5
 *
6
 * @link      https://github.com/hiqdev/composer-asset-plugin
7
 * @package   composer-asset-plugin
8
 * @license   BSD-3-Clause
9
 * @copyright Copyright (c) 2015-2016, HiQDev (http://hiqdev.com/)
10
 */
11
12
namespace hiqdev\composerassetplugin;
13
14
use Composer\Json\JsonFile;
15
use Composer\Package\PackageInterface;
16
17
/**
18
 * Abstract package manager class.
19
 *
20
 * @author Andrii Vasyliev <[email protected]>
21
 */
22
abstract class PackageManager
23
{
24
    /**
25
     * @var Plugin the plugin instance
26
     */
27
    protected $plugin;
28
29
    /**
30
     * @var string Package manager name: `bower` or `npm`
31
     */
32
    protected $name;
33
34
    /**
35
     * @var string Package config file name: `bower.json` or `package.json`
36
     */
37
    public $file;
38
39
    /**
40
     * @var string Package RC config file name: `.bowerrc` or `.npmrc`
41
     */
42
    public $rcfile;
43
44
    /**
45
     * @var string Path to package manager binary
46
     */
47
    public $bin;
48
49
    /**
50
     * @var string Package name of the PHP version of the package manager
51
     */
52
    public $phpPackage;
53
54
    /**
55
     * @var string Binary name of PHP version of package manager
56
     */
57
    protected $phpBin;
58
59
    /**
60
     * @var array Package config. Initially holds default config
61
     */
62
    protected $config = [];
63
64
    /**
65
     * @var array RC config: .bowerrc or .npmrc
66
     */
67
    protected $rc = [];
68
69
    /**
70
     * @var array List of keys holding dependencies
71
     */
72
    protected $dependencies = ['dependencies', 'devDependencies'];
73
74
    /**
75
     * array known deps collected from requirements.
76
     */
77
    protected $knownDeps = [];
78
79
    /**
80
     * Reads config file or dist config if exists, merges with default config.
81
     * @param Plugin $plugin
82
     * @void
83
     */
84 3
    public function __construct(Plugin $plugin)
85
    {
86 3
        $this->plugin = $plugin;
87
        //$dist = $this->file . '.dist';
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% 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...
88 3
        $this->config = array_merge(
89 3
            $this->config,
90 3
            $this->readConfig($this->file)
91
            //$this->readConfig(file_exists($dist) ? $dist : $this->file)
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% 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...
92 3
        );
93 3
    }
94
95
    public function getName()
96
    {
97
        return $this->name;
98
    }
99
100
    public function packageFullName($package)
101
    {
102
        return $package->getName() . ':' . $package->getVersion();
103
    }
104
105
    public function setKnownDeps(PackageInterface $package, $type, $name, $constraint)
106
    {
107
        $res = $this->getKnownDeps($package);
108
        if (!isset($res[$type])) {
109
            $res[$type] = [];
110
        }
111
        $res[$type][$name] = $constraint;
112
        $this->knownDeps[$this->packageFullName($package)] = $res;
113
    }
114
115
    public function getKnownDeps(PackageInterface $package)
116
    {
117
        $full = $this->packageFullName($package);
118
        return isset($this->knownDeps[$full]) ? $this->knownDeps[$full] : [];
119
    }
120
121
    public function getConfig()
122
    {
123
        return $this->config;
124
    }
125
126
    abstract public function setDestination($dir);
127
    /**
128
     * Reads the JSON config from the $path.
129
     *
130
     * @param string $path path to the Json file
131
     * @return array|mixed
132
     */
133 3
    public function readConfig($path)
134
    {
135 3
        $jsonFile = new JsonFile($path);
136 3
        $config = $jsonFile->exists() ? $jsonFile->read() : [];
137 3
        foreach ($this->dependencies as $key) {
138 3
            if (!isset($config[$key])) {
139 3
                $config[$key] = [];
140 3
            }
141 3
        }
142 3
        return $config;
143
    }
144
145
    /**
146
     * Saves JSON config to the given path.
147
     *
148
     * @param string $path
149
     * @param array $config
150
     * @throws \Exception
151
     */
152
    public function writeJson($path, array $config)
153
    {
154
        $jsonFile = new JsonFile($path);
155
        $jsonFile->write($this->prepareConfig($config));
156
    }
157
158
    public function prepareConfig(array $config)
159
    {
160
        foreach ($this->dependencies as $key) {
161
            if (!isset($config[$key])) {
162
                continue;
163
            }
164
            if (!$config[$key]) {
165
                unset($config[$key]);
166
                continue;
167
            }
168
            foreach ($config[$key] as $name => &$constraint) {
169
                $constraint = $this->fixConstraint($constraint);
170
            }
171
        }
172
173
        return $config;
174
    }
175
176
    /**
177
     * Fixes constraint for the package manager.
178
     * Does nothing for NPM. Redefined in Bower.
179
     * @param string $constraint
180
     * @return string
181
     */
182
    public function fixConstraint($constraint)
183
    {
184
        return $constraint;
185
    }
186
187
    /**
188
     * Scans the $package and extracts dependencies to the [[config]].
189
     * @param PackageInterface $package
190
     */
191
    public function scanPackage(PackageInterface $package)
192
    {
193
        $extra = $package->getExtra();
194
        $extra_deps = [];
195
        foreach ($this->dependencies as $key) {
196
            $name = $this->name . '-' . $key;
197
            if (isset($extra[$name])) {
198
                $extra_deps[$key] = $extra[$name];
199
            }
200
        }
201
        $known_deps = $this->getKnownDeps($package);
202
        foreach ([$known_deps, $extra_deps] as $deps) {
203
            if (!empty($deps)) {
204
                $this->mergeConfig($deps);
205
            }
206
        }
207
    }
208
209
    /**
210
     * Merges the $config over the [[config]].
211
     * @param array $config
212
     */
213
    protected function mergeConfig(array $config)
214
    {
215
        foreach ($config as $type => $packages) {
216
            foreach ($packages as $name => $constraint) {
217
                $this->addDependency($type, $name, $constraint);
218
            }
219
        }
220
    }
221
222
    public function addDependency($type, $name, $constraint)
223
    {
224
        if (isset($this->config[$type][$name])) {
225
            $this->config[$type][$name] = Constraint::merge($this->config[$type][$name], $constraint);
226
        } else {
227
            $this->config[$type][$name] = $constraint;
228
        }
229
    }
230
231
    /**
232
     * Set config.
233
     * @param array $config
234
     */
235
    public function setConfig(array $config)
236
    {
237
        $this->config = $config;
238
    }
239
240
    /**
241
     * Returns if the package manager has nonempty dependency list.
242
     * @return bool
243
     */
244 1
    public function hasDependencies()
245
    {
246 1
        foreach ($this->dependencies as $key) {
247 1
            if (isset($this->config[$key]) && $this->config[$key]) {
248
                return true;
249
            }
250 1
        }
251
252 1
        return false;
253
    }
254
255
    /**
256
     * Run the given action: show notice, write config and run `perform`.
257
     * @param string $action the action name
258
     */
259
    public function runAction($action)
260
    {
261
        $doing = ucfirst(trim($action, 'e')) . 'ing';
262
        $this->plugin->io->writeError('<info>' . $doing . ' ' . $this->name . ' dependencies...</info>');
263
        $this->saveConfigs();
264
        $this->perform($action);
265
    }
266
267
    public function saveConfigs()
268
    {
269
        if ($this->rc) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rc of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
270
            $this->writeRc($this->rcfile, $this->rc);
271
        } else {
272
            unlink($this->rcfile);
273
        }
274
        $this->writeJson($this->file, $this->config);
275
    }
276
277
    abstract public function writeRc($path, $data);
278
279
    /**
280
     * Run installation. Specific for every package manager.
281
     * @param string $action the action name
282
     * @void
283
     */
284
    protected function perform($action)
285
    {
286
        $this->plugin->io->writeError('running ' . $this->getBin());
287
        if ($this->passthru([$action])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->passthru(array($action)) 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 ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

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...
288
            $this->plugin->io->writeError('<error>failed ' . $this->name . ' ' . $action . '</error>');
289
        }
290
    }
291
292
    /**
293
     * Prepares arguments and runs the command with [[passthru()]].
294
     * @param array $arguments
295
     * @return integer the exit code
296
     */
297
    public function passthru(array $arguments = [])
298
    {
299
        passthru($this->getBin() . $this->prepareCommand($arguments), $exitCode);
300
        return $exitCode;
301
    }
302
303
    /**
304
     * Prepares given command arguments.
305
     * @param array $arguments
306
     * @return string
307
     */
308
    public function prepareCommand(array $arguments = [])
309
    {
310
        $result = '';
311
        foreach ($arguments as $a) {
312
            $result .= ' ' . escapeshellarg($a);
313
        }
314
315
        return $result;
316
    }
317
318
    /**
319
     * Set path to binary executable file.
320
     * @param $bin
321
     * @internal param string $value
322
     */
323
    public function setBin($bin)
324
    {
325
        $this->bin = $bin;
326
    }
327
328
    /**
329
     * Get path to the binary executable file.
330
     * @return string
331
     */
332
    public function getBin()
333
    {
334
        if ($this->bin === null) {
335
            $this->bin = $this->detectBin();
336
        }
337
338
        return $this->bin;
339
    }
340
341
    /**
342
     * Find path to the binary.
343
     * @return string
344
     */
345
    public function detectBin()
346
    {
347
        $pathes = [
348
            static::buildPath([$this->plugin->getVendorDir(), 'bin', $this->phpBin]),
349
            static::buildPath([$this->plugin->getComposer()->getConfig()->get('home'), 'vendor', 'bin', $this->phpBin]),
350
        ];
351
        foreach ($pathes as $path) {
352
            if (file_exists($path)) {
353
                return $path;
354
            }
355
        }
356
357
        return $this->name;
358
    }
359
360
    public static function buildPath($parts)
361
    {
362
        return implode(DIRECTORY_SEPARATOR, array_filter($parts));
363
    }
364
}
365