Completed
Pull Request — master (#148)
by
unknown
03:16
created

Bowerphp   F

Complexity

Total Complexity 74

Size/Duplication

Total Lines 469
Duplicated Lines 0 %

Coupling/Cohesion

Dependencies 12

Importance

Changes 41
Bugs 24 Features 4
Metric Value
wmc 74
c 41
b 24
f 4
cbo 12
dl 0
loc 469
rs 2.3809

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A init() 0 10 2
C installPackage() 0 56 10
B installDependencies() 0 18 5
C updatePackage() 0 65 15
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
C isPackageExtraneous() 0 39 11
A createAClearBowerFile() 0 15 2
A getPackageTag() 0 21 4
B findPackage() 0 20 5
A cachePackage() 0 8 1
A updateBowerFile() 0 10 4

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