Completed
Pull Request — master (#154)
by Alexey
03:25 queued 01:23
created

Bowerphp::isNeedUpdate()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 20
rs 9.2
cc 4
eloc 14
nc 4
nop 1
1
<?php
2
3
/*
4
 * This file is part of Bowerphp.
5
 *
6
 * (c) Massimiliano Arione <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Bowerphp;
13
14
use Bowerphp\Config\ConfigInterface;
15
use Bowerphp\Installer\InstallerInterface;
16
use Bowerphp\Output\BowerphpConsoleOutput;
17
use Bowerphp\Package\Package;
18
use Bowerphp\Package\PackageInterface;
19
use Bowerphp\Repository\RepositoryInterface;
20
use Bowerphp\Util\Filesystem;
21
use Github\Client;
22
use Guzzle\Http\Exception\RequestException;
23
use InvalidArgumentException;
24
use RuntimeException;
25
use Symfony\Component\Finder\Finder;
26
27
/**
28
 * Main class
29
 */
30
class Bowerphp
0 ignored issues
show
Complexity introduced by
The class Bowerphp has a coupling between objects value of 13. Consider to reduce the number of dependencies under 13.
Loading history...
Complexity introduced by
This class has a complexity of 73 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

You can also find more detailed suggestions for refactoring in the “Code” section of your repository.

Loading history...
31
{
32
    protected $config;
33
    protected $filesystem;
34
    protected $githubClient;
35
    protected $repository;
36
    protected $output;
37
38
    /**
39
     * @param ConfigInterface       $config
40
     * @param Filesystem            $filesystem
41
     * @param Client                $githubClient
42
     * @param RepositoryInterface   $repository
43
     * @param BowerphpConsoleOutput $output
44
     */
45
    public function __construct(
46
        ConfigInterface $config,
47
        Filesystem $filesystem,
48
        Client $githubClient,
49
        RepositoryInterface $repository,
50
        BowerphpConsoleOutput $output
51
    ) {
52
        $this->config = $config;
53
        $this->filesystem = $filesystem;
54
        $this->githubClient = $githubClient;
55
        $this->repository = $repository;
56
        $this->output = $output;
57
    }
58
59
    /**
60
     * Init bower.json
61
     *
62
     * @param array $params
63
     */
64
    public function init(array $params)
65
    {
66
        if ($this->config->bowerFileExists()) {
67
            $bowerJson = $this->config->getBowerFileContent();
68
            $this->config->setSaveToBowerJsonFile(true);
69
            $this->config->updateBowerJsonFile2($bowerJson, $params);
70
        } else {
71
            $this->config->initBowerJsonFile($params);
72
        }
73
    }
74
75
    /**
76
     * Install a single package
77
     *
78
     * @param PackageInterface   $package
79
     * @param InstallerInterface $installer
80
     * @param bool               $isDependency
81
     */
82
    public function installPackage(PackageInterface $package, InstallerInterface $installer, $isDependency = false)
83
    {
84
        if (strpos($package->getName(), 'github') !== false) {
85
            // install from a github endpoint
86
            $name = basename($package->getName(), '.git');
87
            $repoUrl = $package->getName();
88
            $package = new Package($name, $package->getRequiredVersion());
89
            $this->repository->setUrl($repoUrl)->setHttpClient($this->githubClient);
90
            $package->setRepository($this->repository);
91
            $packageTag = $this->repository->findPackage($package->getRequiredVersion());
92
            if (is_null($packageTag)) {
93
                throw new RuntimeException(sprintf('Cannot find package %s version %s.', $package->getName(), $package->getRequiredVersion()));
94
            }
95
        } else {
96
            $packageTag = $this->getPackageTag($package, true);
97
            $package->setRepository($this->repository);
98
        }
99
100
        $package->setVersion($packageTag);
101
102
        $this->updateBowerFile($package, $isDependency);
103
104
        // if package is already installed, match current version with latest available version
105
        if ($this->isPackageInstalled($package)) {
106
            $packageBower = $this->config->getPackageBowerFileContent($package);
107
            if ($packageTag == $packageBower['version']) {
108
                // if version is fully matching, there's no need to install
109
                return;
110
            }
111
        }
112
113
        $this->output->writelnInfoPackage($package);
114
115
        $this->output->writelnInstalledPackage($package);
116
117
        $this->cachePackage($package);
118
119
        $installer->install($package);
120
121
        $overrides = $this->config->getOverrideFor($package->getName());
122
        if (array_key_exists('dependencies', $overrides)) {
123
            $dependencies = $overrides['dependencies'];
124
        } else {
125
            $dependencies = $package->getRequires();
126
        }
127
        if (!empty($dependencies)) {
128
            foreach ($dependencies as $name => $version) {
129
                $depPackage = new Package($name, $version);
130
                if (!$this->isPackageInstalled($depPackage)) {
131
                    $this->installPackage($depPackage, $installer, true);
132
                } elseif ($this->isNeedUpdate($depPackage)) {
133
                    $this->updatePackage($depPackage, $installer);
134
                }
135
            }
136
        }
137
    }
138
139
    /**
140
     * Install all dependencies
141
     *
142
     * @param InstallerInterface $installer
143
     */
144
    public function installDependencies(InstallerInterface $installer)
145
    {
146
        $decode = $this->config->getBowerFileContent();
147
        if (!empty($decode['dependencies'])) {
148
            foreach ($decode['dependencies'] as $name => $requiredVersion) {
149
                if (strpos($requiredVersion, 'github') !== false) {
150
                    list($name, $requiredVersion) = explode('#', $requiredVersion);
151
                }
152
                $package = new Package($name, $requiredVersion);
153
                $this->installPackage($package, $installer, true);
154
            }
155
        }
156
    }
157
158
    /**
159
     * Update a single package
160
     *
161
     * @param PackageInterface   $package
162
     * @param InstallerInterface $installer
163
     */
164
    public function updatePackage(PackageInterface $package, InstallerInterface $installer)
0 ignored issues
show
Complexity introduced by
This operation has 800 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
165
    {
166
        if (!$this->isPackageInstalled($package)) {
167
            throw new RuntimeException(sprintf('Package %s is not installed.', $package->getName()));
168
        }
169
        if (is_null($package->getRequiredVersion())) {
170
            $decode = $this->config->getBowerFileContent();
171
            if (empty($decode['dependencies']) || empty($decode['dependencies'][$package->getName()])) {
172
                throw new InvalidArgumentException(sprintf('Package %s not found in bower.json', $package->getName()));
173
            }
174
            $package->setRequiredVersion($decode['dependencies'][$package->getName()]);
175
        }
176
177
        $bower = $this->config->getPackageBowerFileContent($package);
178
        $package->setInfo($bower);
179
        $package->setVersion($bower['version']);
180
        $package->setRequires(isset($bower['dependencies']) ? $bower['dependencies'] : null);
181
182
        $packageTag = $this->getPackageTag($package);
183
        $package->setRepository($this->repository);
184
        if ($packageTag == $package->getVersion()) {
185
            // if version is fully matching, there's no need to update
186
            return;
187
        }
188
        $package->setVersion($packageTag);
189
190
        $this->output->writelnUpdatingPackage($package);
191
192
        $this->cachePackage($package);
193
194
        $installer->update($package);
195
196
        $overrides = $this->config->getOverrideFor($package->getName());
197
        if (array_key_exists('dependencies', $overrides)) {
198
            $dependencies = $overrides['dependencies'];
199
        } else {
200
            $dependencies = $package->getRequires();
201
        }
202
        if (!empty($dependencies)) {
203
            foreach ($dependencies as $name => $requiredVersion) {
204
                $depPackage = new Package($name, $requiredVersion);
205
                if (!$this->isPackageInstalled($depPackage)) {
206
                    $this->installPackage($depPackage, $installer, true);
207
                } elseif ($this->isNeedUpdate($depPackage)) {
208
                    $this->updatePackage($depPackage, $installer);
209
                }
210
            }
211
        }
212
    }
213
214
    /**
215
     * Update all dependencies
216
     *
217
     * @param InstallerInterface $installer
218
     */
219
    public function updatePackages(InstallerInterface $installer)
220
    {
221
        $decode = $this->config->getBowerFileContent();
222
        if (!empty($decode['dependencies'])) {
223
            foreach ($decode['dependencies'] as $packageName => $requiredVersion) {
224
                $this->updatePackage(new Package($packageName, $requiredVersion), $installer);
225
            }
226
        }
227
    }
228
229
    /**
230
     * @param  PackageInterface $package
231
     * @param  string           $info
232
     * @return mixed
233
     */
234
    public function getPackageInfo(PackageInterface $package, $info = 'url')
235
    {
236
        $decode = $this->lookupPackage($package->getName());
237
238
        $this->repository->setHttpClient($this->githubClient);
239
240
        if ($info == 'url') {
241
            $this->repository->setUrl($decode['url'], false);
242
243
            return $this->repository->getUrl();
244
        }
245
246
        if ($info == 'versions') {
247
            $tags = $this->repository->getTags();
248
            usort($tags, function ($a, $b) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $a. Configured minimum length is 2.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $b. Configured minimum length is 2.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
249
                return version_compare($b, $a);
250
            });
251
252
            return $tags;
253
        }
254
255
        throw new RuntimeException(sprintf('Unsupported info option "%s".', $info));
256
    }
257
258
    /**
259
     * @param  string $name
260
     * @return array
261
     */
262
    public function lookupPackage($name)
263
    {
264
        return $this->findPackage($name);
265
    }
266
267
    /**
268
     * @param  PackageInterface $package
269
     * @return string
270
     */
271
    public function getPackageBowerFile(PackageInterface $package)
272
    {
273
        $this->repository->setHttpClient($this->githubClient);
274
        $lookupPackage = $this->lookupPackage($package->getName());
275
        $this->repository->setUrl($lookupPackage['url'], false);
276
        $tag = $this->repository->findPackage($package->getRequiredVersion());
277
278
        return $this->repository->getBower($tag, true, $lookupPackage['url']);
279
    }
280
281
    /**
282
     * Uninstall a single package
283
     *
284
     * @param PackageInterface   $package
285
     * @param InstallerInterface $installer
286
     */
287
    public function uninstallPackage(PackageInterface $package, InstallerInterface $installer)
288
    {
289
        if (!$this->isPackageInstalled($package)) {
290
            throw new RuntimeException(sprintf('Package %s is not installed.', $package->getName()));
291
        }
292
        $installer->uninstall($package);
293
    }
294
295
    /**
296
     * Search packages by name
297
     *
298
     * @param  string $name
299
     * @return array
300
     */
301
    public function searchPackages($name)
302
    {
303
        try {
304
            $url = $this->config->getBasePackagesUrl() . 'search/' . $name;
305
            $response = $this->githubClient->getHttpClient()->get($url);
306
307
            return json_decode($response->getBody(true), true);
308
        } catch (RequestException $e) {
309
            throw new RuntimeException(sprintf('Cannot get package list from %s.', str_replace('/packages/', '', $this->config->getBasePackagesUrl())));
310
        }
311
    }
312
313
    /**
314
     * Get a list of installed packages
315
     *
316
     * @param  InstallerInterface $installer
317
     * @param  Finder             $finder
318
     * @return array
319
     */
320
    public function getInstalledPackages(InstallerInterface $installer, Finder $finder)
321
    {
322
        return $installer->getInstalled($finder);
323
    }
324
325
    /**
326
     * Check if package is installed
327
     *
328
     * @param  PackageInterface $package
329
     * @return bool
330
     */
331
    public function isPackageInstalled(PackageInterface $package)
332
    {
333
        return $this->filesystem->exists($this->config->getInstallDir() . '/' . $package->getName() . '/.bower.json');
334
    }
335
336
    /**
337
     * {@inheritdoc}
338
     */
339
    public function isPackageExtraneous(PackageInterface $package, $checkInstall = false)
0 ignored issues
show
Complexity introduced by
This operation has 216 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
340
    {
341
        if ($checkInstall && !$this->isPackageInstalled($package)) {
342
            return false;
343
        }
344
        try {
345
            $bower = $this->config->getBowerFileContent();
346
        } catch (RuntimeException $e) { // no bower.json file, package is extraneous
347
            return true;
348
        }
349
        if (!isset($bower['dependencies'])) {
350
            return true;
351
        }
352
        // package is a direct dependencies
353
        if (isset($bower['dependencies'][$package->getName()])) {
354
            return false;
355
        }
356
        // look for dependencies of dependencies
357
        foreach ($bower['dependencies'] as $name => $version) {
358
            $dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name . '/.bower.json');
359
            $depBower = json_decode($dotBowerJson, true);
360
            if (isset($depBower['dependencies'][$package->getName()])) {
361
                return false;
362
            }
363
            // look for dependencies of dependencies of dependencies
364
            if (isset($depBower['dependencies'])) {
365
                foreach ($depBower['dependencies'] as $name1 => $version1) {
366
                    $dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name1 . '/.bower.json');
367
                    $depDepBower = json_decode($dotBowerJson, true);
368
                    if (isset($depDepBower['dependencies'][$package->getName()])) {
369
                        return false;
370
                    }
371
                }
372
            }
373
        }
374
375
        return true;
376
    }
377
378
    /**
379
     * @param  array $params
380
     * @return array
381
     */
382
    protected function createAClearBowerFile(array $params)
383
    {
384
        $authors = ['Beelab <[email protected]>'];
385
        if (!empty($params['author'])) {
386
            $authors[] = $params['author'];
387
        }
388
        $structure = [
389
            'name'         => $params['name'],
390
            'authors'      => $authors,
391
            'private'      => true,
392
            'dependencies' => new \StdClass(),
393
        ];
394
395
        return $structure;
396
    }
397
398
    /**
399
     * @param  PackageInterface $package
400
     * @param  bool             $setInfo
401
     * @return string
402
     */
403
    protected function getPackageTag(PackageInterface $package, $setInfo = false)
404
    {
405
        $decode = $this->findPackage($package->getName());
406
        // open package repository
407
        $repoUrl = $decode['url'];
408
        $this->repository->setUrl($repoUrl)->setHttpClient($this->githubClient);
409
        $packageTag = $this->repository->findPackage($package->getRequiredVersion());
410
        if (is_null($packageTag)) {
411
            throw new RuntimeException(sprintf('Cannot find package %s version %s.', $package->getName(), $package->getRequiredVersion()));
412
        }
413
        $bowerJson = $this->repository->getBower($packageTag);
414
        $bower = json_decode($bowerJson, true);
415
        if (!is_array($bower)) {
416
            throw new RuntimeException(sprintf('Invalid bower.json found in package %s: %s.', $package->getName(), $bowerJson));
417
        }
418
        if ($setInfo) {
419
            $package->setInfo($bower);
420
        }
421
422
        return $packageTag;
423
    }
424
425
    /**
426
     * @param  string $name
427
     * @return array
428
     */
429
    protected function findPackage($name)
430
    {
431
        try {
432
            $response = $this->githubClient->getHttpClient()->get($this->config->getBasePackagesUrl() . urlencode($name));
433
        } catch (RuntimeException $e) {
434
            throw new RuntimeException(sprintf('Cannot fetch registry info for package %s from search registry (%s).', $name, $e->getMessage()));
435
        }
436
        $packageInfo = json_decode($response->getBody(true), true);
437
        if (!is_array($packageInfo) || empty($packageInfo['url'])) {
438
            throw new RuntimeException(sprintf('Registry info for package %s has malformed json or is missing "url".', $name));
439
        }
440
441
        return $packageInfo;
442
    }
443
444
    /**
445
     * @param PackageInterface $package
446
     */
447
    private function cachePackage(PackageInterface $package)
448
    {
449
        // get release archive from repository
450
        $file = $this->repository->getRelease();
451
452
        $tmpFileName = $this->config->getCacheDir() . '/tmp/' . $package->getName();
453
        $this->filesystem->write($tmpFileName, $file);
454
    }
455
456
    /**
457
     * @param PackageInterface $package
458
     * @param bool             $isDependency
459
     */
460
    private function updateBowerFile(PackageInterface $package, $isDependency = false)
461
    {
462
        if ($this->config->isSaveToBowerJsonFile() && !$isDependency) {
463
            try {
464
                $this->config->updateBowerJsonFile($package);
465
            } catch (RuntimeException $e) {
466
                $this->output->writelnNoBowerJsonFile();
467
            }
468
        }
469
    }
470
471
    /**
472
     * Update only if needed is greater version
473
     *
474
     * @param  PackageInterface $package
475
     * @return bool
476
     */
477
    public function isNeedUpdate($package)
478
    {
479
        if (!$package->getRequiredVersion()) {
480
            return false;
481
        }
482
        $packageBower = $this->config->getPackageBowerFileContent($package);
483
        $requireVersion = $package->getRequiredVersion();
484
        $compare = '=';
485
        preg_match('!^[^\\d]+!', $requireVersion, $matches);
486
        if (!empty($matches[0])) {
487
            $compare = $matches[0];
488
            $requireVersion = substr($requireVersion, strlen($compare));
489
            if ($compare == '~') {
490
                $compare = '>';
491
                $requireVersion = substr($requireVersion, 0, strrpos($requireVersion, '.'));
492
            }
493
        }
494
495
        return version_compare($requireVersion, $packageBower['version'], $compare);
496
    }
497
}
498