Bowerphp   F
last analyzed

Complexity

Total Complexity 70

Size/Duplication

Total Lines 464
Duplicated Lines 0 %

Coupling/Cohesion

Dependencies 12

Importance

Changes 0
Metric Value
wmc 70
cbo 12
dl 0
loc 464
rs 2.8
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A init() 0 10 2
B installPackage() 0 54 10
A installDependencies() 0 13 4
C updatePackage() 0 50 12
A updatePackages() 0 9 3
A getPackageInfo() 0 23 3
A lookupPackage() 0 4 1
A getPackageBowerFile() 0 9 1
A uninstallPackage() 0 7 2
A searchPackages() 0 11 2
A getInstalledPackages() 0 4 1
A isPackageInstalled() 0 4 1
B isPackageExtraneous() 0 39 11
A createAClearBowerFile() 0 15 2
A getPackageTag() 0 21 4
A findPackage() 0 14 4
A cachePackage() 0 10 1
A updateBowerFile() 0 10 4
A isNeedUpdate() 0 7 1

How to fix   Complexity   

Complex Class

Complex classes like Bowerphp 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 Bowerphp, and based on these observations, apply Extract Interface, too.

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
use vierbergenlars\SemVer\expression;
27
use vierbergenlars\SemVer\version;
28
29
/**
30
 * Main class
31
 */
32
class Bowerphp
0 ignored issues
show
Complexity introduced by
This class has a complexity of 70 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...
Complexity introduced by
The class Bowerphp has a coupling between objects value of 15. Consider to reduce the number of dependencies under 13.
Loading history...
33
{
34
    protected $config;
35
36
    protected $filesystem;
37
38
    protected $githubClient;
39
40
    protected $repository;
41
42
    protected $output;
43
44
    /**
45
     * @param ConfigInterface       $config
46
     * @param Filesystem            $filesystem
47
     * @param Client                $githubClient
48
     * @param RepositoryInterface   $repository
49
     * @param BowerphpConsoleOutput $output
50
     */
51
    public function __construct(
52
        ConfigInterface $config,
53
        Filesystem $filesystem,
54
        Client $githubClient,
55
        RepositoryInterface $repository,
56
        BowerphpConsoleOutput $output
57
    ) {
58
        $this->config = $config;
59
        $this->filesystem = $filesystem;
60
        $this->githubClient = $githubClient;
61
        $this->repository = $repository;
62
        $this->output = $output;
63
    }
64
65
    /**
66
     * Init bower.json
67
     *
68
     * @param array $params
69
     */
70
    public function init(array $params)
71
    {
72
        if ($this->config->bowerFileExists()) {
73
            $bowerJson = $this->config->getBowerFileContent();
74
            $this->config->setSaveToBowerJsonFile();
75
            $this->config->updateBowerJsonFile2($bowerJson, $params);
76
        } else {
77
            $this->config->initBowerJsonFile($params);
78
        }
79
    }
80
81
    /**
82
     * Install a single package
83
     *
84
     * @param PackageInterface   $package
85
     * @param InstallerInterface $installer
86
     * @param bool               $isDependency
87
     */
88
    public function installPackage(PackageInterface $package, InstallerInterface $installer, $isDependency = false)
89
    {
90
        if (false !== strpos($package->getName(), 'github')) {
91
            // install from a github endpoint
92
            $name = basename($package->getName(), '.git');
93
            $repoUrl = $package->getName();
94
            $package = new Package($name, $package->getRequiredVersion());
95
            $this->repository->setUrl($repoUrl)->setHttpClient($this->githubClient);
96
            $package->setRepository($this->repository);
97
            $packageTag = $this->repository->findPackage($package->getRequiredVersion());
98
            if (is_null($packageTag)) {
99
                throw new RuntimeException(sprintf('Cannot find package %s version %s.', $package->getName(), $package->getRequiredVersion()));
100
            }
101
        } else {
102
            $packageTag = $this->getPackageTag($package, true);
103
            $package->setRepository($this->repository);
104
        }
105
106
        $package->setVersion($packageTag);
107
108
        $this->updateBowerFile($package, $isDependency);
109
110
        // if package is already installed, match current version with latest available version
111
        if ($this->isPackageInstalled($package)) {
112
            $this->output->writelnInfoPackage($package, 'validate', sprintf('%s against %s#%s', $packageTag, $package->getName(), $package->getRequiredVersion()));
113
            $packageBower = $this->config->getPackageBowerFileContent($package);
114
            if ($packageTag == $packageBower['version']) {
115
                // if version is fully matching, there's no need to install
116
                return;
117
            }
118
        }
119
120
        $this->cachePackage($package);
121
122
        $this->output->writelnInstalledPackage($package);
123
        $installer->install($package);
124
125
        $overrides = $this->config->getOverrideFor($package->getName());
126
        if (array_key_exists('dependencies', $overrides)) {
127
            $dependencies = $overrides['dependencies'];
128
        } else {
129
            $dependencies = $package->getRequires();
130
        }
131
        if (!empty($dependencies)) {
132
            foreach ($dependencies as $name => $version) {
133
                $depPackage = new Package($name, $version);
134
                if (!$this->isPackageInstalled($depPackage)) {
135
                    $this->installPackage($depPackage, $installer, true);
136
                } elseif ($this->isNeedUpdate($depPackage)) {
137
                    $this->updatePackage($depPackage, $installer);
138
                }
139
            }
140
        }
141
    }
142
143
    /**
144
     * Install all dependencies
145
     *
146
     * @param InstallerInterface $installer
147
     */
148
    public function installDependencies(InstallerInterface $installer)
149
    {
150
        $decode = $this->config->getBowerFileContent();
151
        if (!empty($decode['dependencies'])) {
152
            foreach ($decode['dependencies'] as $name => $requiredVersion) {
153
                if (false !== strpos($requiredVersion, 'github')) {
154
                    list($name, $requiredVersion) = explode('#', $requiredVersion);
155
                }
156
                $package = new Package($name, $requiredVersion);
157
                $this->installPackage($package, $installer, true);
158
            }
159
        }
160
    }
161
162
    /**
163
     * Update a single package
164
     *
165
     * @param PackageInterface   $package
166
     * @param InstallerInterface $installer
167
     */
168
    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...
169
    {
170
        if (!$this->isPackageInstalled($package)) {
171
            throw new RuntimeException(sprintf('Package %s is not installed.', $package->getName()));
172
        }
173
        if (is_null($package->getRequiredVersion())) {
174
            $decode = $this->config->getBowerFileContent();
175
            if (empty($decode['dependencies']) || empty($decode['dependencies'][$package->getName()])) {
176
                throw new InvalidArgumentException(sprintf('Package %s not found in bower.json', $package->getName()));
177
            }
178
            $package->setRequiredVersion($decode['dependencies'][$package->getName()]);
179
        }
180
181
        $bower = $this->config->getPackageBowerFileContent($package);
182
        $package->setInfo($bower);
183
        $package->setVersion($bower['version']);
184
        $package->setRequires(isset($bower['dependencies']) ? $bower['dependencies'] : null);
185
186
        $packageTag = $this->getPackageTag($package);
187
        $this->output->writelnInfoPackage($package, 'validate', sprintf('%s against %s#%s', $packageTag, $package->getName(), $package->getRequiredVersion()));
188
189
        $package->setRepository($this->repository);
190
        if ($packageTag == $package->getVersion()) {
191
            // if version is fully matching, there's no need to update
192
            return;
193
        }
194
        $package->setVersion($packageTag);
195
196
        $this->cachePackage($package);
197
198
        $this->output->writelnUpdatingPackage($package);
199
        $installer->update($package);
200
201
        $overrides = $this->config->getOverrideFor($package->getName());
202
        if (array_key_exists('dependencies', $overrides)) {
203
            $dependencies = $overrides['dependencies'];
204
        } else {
205
            $dependencies = $package->getRequires();
206
        }
207
        if (!empty($dependencies)) {
208
            foreach ($dependencies as $name => $requiredVersion) {
209
                $depPackage = new Package($name, $requiredVersion);
210
                if (!$this->isPackageInstalled($depPackage)) {
211
                    $this->installPackage($depPackage, $installer, true);
212
                } elseif ($this->isNeedUpdate($depPackage)) {
213
                    $this->updatePackage($depPackage, $installer);
214
                }
215
            }
216
        }
217
    }
218
219
    /**
220
     * Update all dependencies
221
     *
222
     * @param InstallerInterface $installer
223
     */
224
    public function updatePackages(InstallerInterface $installer)
225
    {
226
        $decode = $this->config->getBowerFileContent();
227
        if (!empty($decode['dependencies'])) {
228
            foreach ($decode['dependencies'] as $packageName => $requiredVersion) {
229
                $this->updatePackage(new Package($packageName, $requiredVersion), $installer);
230
            }
231
        }
232
    }
233
234
    /**
235
     * @param  PackageInterface $package
236
     * @param  string           $info
237
     * @return mixed
238
     */
239
    public function getPackageInfo(PackageInterface $package, $info = 'url')
240
    {
241
        $decode = $this->lookupPackage($package->getName());
242
243
        $this->repository->setHttpClient($this->githubClient);
244
245
        if ('url' == $info) {
246
            $this->repository->setUrl($decode['url'], false);
247
248
            return $this->repository->getUrl();
249
        }
250
251
        if ('versions' == $info) {
252
            $tags = $this->repository->getTags();
253
            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...
254
                return version_compare($b, $a);
255
            });
256
257
            return $tags;
258
        }
259
260
        throw new RuntimeException(sprintf('Unsupported info option "%s".', $info));
261
    }
262
263
    /**
264
     * @param  string $name
265
     * @return array
266
     */
267
    public function lookupPackage($name)
268
    {
269
        return $this->findPackage($name);
270
    }
271
272
    /**
273
     * @param  PackageInterface $package
274
     * @return string
275
     */
276
    public function getPackageBowerFile(PackageInterface $package)
277
    {
278
        $this->repository->setHttpClient($this->githubClient);
279
        $lookupPackage = $this->lookupPackage($package->getName());
280
        $this->repository->setUrl($lookupPackage['url'], false);
281
        $tag = $this->repository->findPackage($package->getRequiredVersion());
282
283
        return $this->repository->getBower($tag, true, $lookupPackage['url']);
284
    }
285
286
    /**
287
     * Uninstall a single package
288
     *
289
     * @param PackageInterface   $package
290
     * @param InstallerInterface $installer
291
     */
292
    public function uninstallPackage(PackageInterface $package, InstallerInterface $installer)
293
    {
294
        if (!$this->isPackageInstalled($package)) {
295
            throw new RuntimeException(sprintf('Package %s is not installed.', $package->getName()));
296
        }
297
        $installer->uninstall($package);
298
    }
299
300
    /**
301
     * Search packages by name
302
     *
303
     * @param  string $name
304
     * @return array
305
     */
306
    public function searchPackages($name)
307
    {
308
        try {
309
            $url = $this->config->getBasePackagesUrl() . 'search/' . $name;
310
            $response = $this->githubClient->getHttpClient()->get($url);
311
312
            return json_decode($response->getBody(true), true);
313
        } catch (RequestException $e) {
314
            throw new RuntimeException(sprintf('Cannot get package list from %s.', str_replace('/packages/', '', $this->config->getBasePackagesUrl())));
315
        }
316
    }
317
318
    /**
319
     * Get a list of installed packages
320
     *
321
     * @param  InstallerInterface $installer
322
     * @param  Finder             $finder
323
     * @return array
324
     */
325
    public function getInstalledPackages(InstallerInterface $installer, Finder $finder)
326
    {
327
        return $installer->getInstalled($finder);
328
    }
329
330
    /**
331
     * Check if package is installed
332
     *
333
     * @param  PackageInterface $package
334
     * @return bool
335
     */
336
    public function isPackageInstalled(PackageInterface $package)
337
    {
338
        return $this->filesystem->exists($this->config->getInstallDir() . '/' . $package->getName() . '/.bower.json');
339
    }
340
341
    /**
342
     * @param PackageInterface $package
343
     * @param bool             $checkInstall
344
     *
345
     * @return bool
346
     */
347
    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...
348
    {
349
        if ($checkInstall && !$this->isPackageInstalled($package)) {
350
            return false;
351
        }
352
353
        try {
354
            $bower = $this->config->getBowerFileContent();
355
        } catch (RuntimeException $e) { // no bower.json file, package is extraneous
356
            return true;
357
        }
358
        if (!isset($bower['dependencies'])) {
359
            return true;
360
        }
361
        // package is a direct dependencies
362
        if (isset($bower['dependencies'][$package->getName()])) {
363
            return false;
364
        }
365
        // look for dependencies of dependencies
366
        foreach ($bower['dependencies'] as $name => $version) {
367
            $dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name . '/.bower.json');
368
            $depBower = json_decode($dotBowerJson, true);
369
            if (isset($depBower['dependencies'][$package->getName()])) {
370
                return false;
371
            }
372
            // look for dependencies of dependencies of dependencies
373
            if (isset($depBower['dependencies'])) {
374
                foreach ($depBower['dependencies'] as $name1 => $version1) {
375
                    $dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name1 . '/.bower.json');
376
                    $depDepBower = json_decode($dotBowerJson, true);
377
                    if (isset($depDepBower['dependencies'][$package->getName()])) {
378
                        return false;
379
                    }
380
                }
381
            }
382
        }
383
384
        return true;
385
    }
386
387
    /**
388
     * @param  array $params
389
     * @return array
390
     */
391
    protected function createAClearBowerFile(array $params)
392
    {
393
        $authors = ['Beelab <[email protected]>'];
394
        if (!empty($params['author'])) {
395
            $authors[] = $params['author'];
396
        }
397
        $structure = [
398
            'name'         => $params['name'],
399
            'authors'      => $authors,
400
            'private'      => true,
401
            'dependencies' => new \StdClass(),
402
        ];
403
404
        return $structure;
405
    }
406
407
    /**
408
     * @param  PackageInterface $package
409
     * @param  bool             $setInfo
410
     * @return string
411
     */
412
    protected function getPackageTag(PackageInterface $package, $setInfo = false)
413
    {
414
        $decode = $this->findPackage($package->getName());
415
        // open package repository
416
        $repoUrl = $decode['url'];
417
        $this->repository->setUrl($repoUrl)->setHttpClient($this->githubClient);
418
        $packageTag = $this->repository->findPackage($package->getRequiredVersion());
419
        if (is_null($packageTag)) {
420
            throw new RuntimeException(sprintf('Cannot find package %s version %s.', $package->getName(), $package->getRequiredVersion()));
421
        }
422
        $bowerJson = $this->repository->getBower($packageTag);
423
        $bower = json_decode($bowerJson, true);
424
        if (!is_array($bower)) {
425
            throw new RuntimeException(sprintf('Invalid bower.json found in package %s: %s.', $package->getName(), $bowerJson));
426
        }
427
        if ($setInfo) {
428
            $package->setInfo($bower);
429
        }
430
431
        return $packageTag;
432
    }
433
434
    /**
435
     * @param  string $name
436
     * @return array
437
     */
438
    protected function findPackage($name)
439
    {
440
        try {
441
            $response = $this->githubClient->getHttpClient()->get($this->config->getBasePackagesUrl() . urlencode($name));
442
        } catch (RuntimeException $e) {
443
            throw new RuntimeException(sprintf('Cannot fetch registry info for package %s from search registry (%s).', $name, $e->getMessage()));
444
        }
445
        $packageInfo = json_decode($response->getBody(true), true);
446
        if (!is_array($packageInfo) || empty($packageInfo['url'])) {
447
            throw new RuntimeException(sprintf('Registry info for package %s has malformed json or is missing "url".', $name));
448
        }
449
450
        return $packageInfo;
451
    }
452
453
    /**
454
     * @param PackageInterface $package
455
     */
456
    private function cachePackage(PackageInterface $package)
457
    {
458
        $this->output->writelnInfoPackage($package, 'download');
459
460
        // get release archive from repository
461
        $file = $this->repository->getRelease();
462
463
        $tmpFileName = $this->config->getCacheDir() . '/tmp/' . $package->getName();
464
        $this->filesystem->write($tmpFileName, $file);
465
    }
466
467
    /**
468
     * @param PackageInterface $package
469
     * @param bool             $isDependency
470
     */
471
    private function updateBowerFile(PackageInterface $package, $isDependency = false)
472
    {
473
        if ($this->config->isSaveToBowerJsonFile() && !$isDependency) {
474
            try {
475
                $this->config->updateBowerJsonFile($package);
476
            } catch (RuntimeException $e) {
477
                $this->output->writelnNoBowerJsonFile();
478
            }
479
        }
480
    }
481
482
    /**
483
     * Update only if needed is greater version
484
     *
485
     * @param  PackageInterface $package
486
     * @return bool
487
     */
488
    public function isNeedUpdate($package)
489
    {
490
        $packageBower = $this->config->getPackageBowerFileContent($package);
491
        $semver = new version($packageBower['version']);
492
493
        return !$semver->satisfies(new expression($package->getRequiredVersion()));
494
    }
495
}
496