Completed
Pull Request — master (#148)
by
unknown
02:59
created

Bowerphp::cachePackage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 1
eloc 4
nc 1
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
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
The class Bowerphp has a coupling between objects value of 15. Consider to reduce the number of dependencies under 13.
Loading history...
Complexity introduced by
This class has a complexity of 76 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...
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
        $package->setVersion($packageTag);
103
        $this->updateBowerFile($package, $isDependency);
104
105
        
106
        // if package is already installed, match current version with latest available version
107
        if ($this->isPackageInstalled($package)) {
108
            $packageBower = $this->config->getPackageBowerFileContent($package);
109
            if(isset($packageBower['version']) && $packageTag == $packageBower['version']) {
110
                // if version is fully matching, there's no need to install
111
                return;
112
            }
113
        }
114
115
        $this->output->writelnInfoPackage($package);
116
117
        $this->output->writelnInstalledPackage($package);
118
119
        $this->cachePackage($package);
120
121
        $installer->install($package);
122
123
        $overrides = $this->config->getOverrideFor($package->getName());
124
        if (array_key_exists('dependencies', $overrides)) {
125
            $dependencies = $overrides['dependencies'];
126
        } else {
127
            $dependencies = $package->getRequires();
128
        }
129
        if (!empty($dependencies)) {
130
            foreach ($dependencies as $name => $version) {
131
                $depPackage = new Package($name, $version);
132
                if (!$this->isPackageInstalled($depPackage)) {
133
                    $this->installPackage($depPackage, $installer, true);
134
                } else {
135
                    $this->updatePackage($depPackage, $installer, false);
136
                }
137
            }
138
        }
139
    }
140
141
    /**
142
     * Install all dependencies
143
     *
144
     * @param InstallerInterface $installer
145
     */
146
    public function installDependencies(InstallerInterface $installer)
147
    {
148
        $decode = $this->config->getBowerFileContent();
149
        if (!empty($decode['dependencies'])) {
150
            foreach ($decode['dependencies'] as $name => $requiredVersion) {
151
                if (strpos($requiredVersion, 'github') !== false) {
152
                    list($name, $requiredVersion) = explode('#', $requiredVersion);
153
                }
154
                $package = new Package($name, $requiredVersion);
155
                try{
156
					$this->installPackage($package, $installer, true);
157
				}
158
				catch(\RuntimeException $e){
159
					throw new \RuntimeException($name.' '.$requiredVersion.' '.$e->getMessage());
160
				}
161
            }
162
        }
163
    }
164
165
    /**
166
     * Update a single package
167
     *
168
     * @param PackageInterface   $package
169
     * @param InstallerInterface $installer
170
     */
171
    public function updatePackage(PackageInterface $package, InstallerInterface $installer, $force=true)
0 ignored issues
show
Complexity introduced by
This operation has 10240 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...
172
    {
173
        if (!$this->isPackageInstalled($package)) {
174
            throw new RuntimeException(sprintf('Package %s is not installed.', $package->getName()));
175
        }
176
		
177
		if(!$force){
178
			$packageTag = $this->getPackageTag($package);
179
            $decode = $this->config->getBowerFileContent();
180
            if(isset($decode['dependencies'][$package->getName()])){
181
				$requiredVersion = $decode['dependencies'][$package->getName()];
182
				$semver = new version($packageTag);
183
				if($requiredVersion=='latest'||$requiredVersion=='master'){
184
					$requiredVersionExpression = '*';
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $requiredVersionExpression exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
185
				}
186
				else{
187
					$requiredVersionExpression = $requiredVersion;
188
				}
189
				if(!$semver->satisfies(new expression($requiredVersionExpression))){
190
					$package->setRequiredVersion($requiredVersion);
191
				}
192
			}
193
		}
194
		
195
        if (is_null($package->getRequiredVersion())) {
196
            $decode = $this->config->getBowerFileContent();
197
            if (empty($decode['dependencies']) || empty($decode['dependencies'][$package->getName()])) {
198
                throw new InvalidArgumentException(sprintf('Package %s not found in bower.json', $package->getName()));
199
            }
200
            $package->setRequiredVersion($decode['dependencies'][$package->getName()]);
201
        }
202
203
        $bower = $this->config->getPackageBowerFileContent($package);
204
        $package->setInfo($bower);
205
        if(isset($bower['version'])){
206
            $package->setVersion($bower['version']);
207
        }
208
        $package->setRequires(isset($bower['dependencies']) ? $bower['dependencies'] : null);
209
210
        $packageTag = $this->getPackageTag($package);
211
        $package->setRepository($this->repository);        
212
        
213
        if ($packageTag == $package->getVersion()) {
214
            // if version is fully matching, there's no need to update
215
            return;
216
        }
217
        $package->setVersion($packageTag);
218
219
        $this->output->writelnUpdatingPackage($package);
220
221
        $this->cachePackage($package);
222
223
        $installer->update($package);
224
225
        $overrides = $this->config->getOverrideFor($package->getName());
226
        if (array_key_exists('dependencies', $overrides)) {
227
            $dependencies = $overrides['dependencies'];
228
        } else {
229
            $dependencies = $package->getRequires();
230
        }
231
        if (!empty($dependencies)) {
232
            foreach ($dependencies as $name => $requiredVersion) {
233
                $depPackage = new Package($name, $requiredVersion);
234
                if (!$this->isPackageInstalled($depPackage)) {
235
                    $this->installPackage($depPackage, $installer, true);
236
                } else {
237
                    $this->updatePackage($depPackage, $installer);
238
                }
239
            }
240
        }
241
    }
242
243
    /**
244
     * Update all dependencies
245
     *
246
     * @param InstallerInterface $installer
247
     */
248
    public function updatePackages(InstallerInterface $installer)
249
    {
250
        $decode = $this->config->getBowerFileContent();
251
        if (!empty($decode['dependencies'])) {
252
            foreach ($decode['dependencies'] as $packageName => $requiredVersion) {
253
                $this->updatePackage(new Package($packageName, $requiredVersion), $installer);
254
            }
255
        }
256
    }
257
258
    /**
259
     * @param  PackageInterface $package
260
     * @param  string           $info
261
     * @return mixed
262
     */
263
    public function getPackageInfo(PackageInterface $package, $info = 'url')
264
    {
265
        $decode = $this->lookupPackage($package);
266
267
        $this->repository->setHttpClient($this->githubClient);
268
269
        if ($info == 'url') {
270
            $this->repository->setUrl($decode['url'], false);
271
272
            return $this->repository->getUrl();
273
        }
274
275
        if ($info == 'versions') {
276
            $tags = $this->repository->getTags();
277
            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...
278
                return version_compare($b, $a);
279
            });
280
281
            return $tags;
282
        }
283
284
        throw new RuntimeException(sprintf('Unsupported info option "%s".', $info));
285
    }
286
287
    /**
288
     * @param  string $name
289
     * @return array
290
     */
291
    public function lookupPackage($package)
292
    {
293
        return $this->findPackage($package);
294
    }
295
296
    /**
297
     * @param  PackageInterface $package
298
     * @return string
299
     */
300
    public function getPackageBowerFile(PackageInterface $package)
301
    {
302
        $this->repository->setHttpClient($this->githubClient);
303
        $lookupPackage = $this->lookupPackage($package);
304
        $this->repository->setUrl($lookupPackage['url'], false);
305
        $tag = $this->repository->findPackage($package->getRequiredVersion());
306
307
        return $this->repository->getBower($tag, true, $lookupPackage['url']);
308
    }
309
310
    /**
311
     * Uninstall a single package
312
     *
313
     * @param PackageInterface   $package
314
     * @param InstallerInterface $installer
315
     */
316
    public function uninstallPackage(PackageInterface $package, InstallerInterface $installer)
317
    {
318
        if (!$this->isPackageInstalled($package)) {
319
            throw new RuntimeException(sprintf('Package %s is not installed.', $package->getName()));
320
        }
321
        $installer->uninstall($package);
322
    }
323
324
    /**
325
     * Search packages by name
326
     *
327
     * @param  string $name
328
     * @return array
329
     */
330
    public function searchPackages($name)
331
    {
332
        try {
333
            $url = $this->config->getBasePackagesUrl() . 'search/' . $name;
334
            $response = $this->githubClient->getHttpClient()->get($url);
335
336
            return json_decode($response->getBody(true), true);
337
        } catch (RequestException $e) {
338
            throw new RuntimeException(sprintf('Cannot get package list from %s.', str_replace('/packages/', '', $this->config->getBasePackagesUrl())));
339
        }
340
    }
341
342
    /**
343
     * Get a list of installed packages
344
     *
345
     * @param  InstallerInterface $installer
346
     * @param  Finder             $finder
347
     * @return array
348
     */
349
    public function getInstalledPackages(InstallerInterface $installer, Finder $finder)
350
    {
351
        return $installer->getInstalled($finder);
352
    }
353
354
    /**
355
     * Check if package is installed
356
     *
357
     * @param  PackageInterface $package
358
     * @return bool
359
     */
360
    public function isPackageInstalled(PackageInterface $package)
361
    {
362
        return $this->filesystem->exists($this->config->getInstallDir() . '/' . $package->getName() . '/.bower.json');
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368
    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...
369
    {
370
        if ($checkInstall && !$this->isPackageInstalled($package)) {
371
            return false;
372
        }
373
        try {
374
            $bower = $this->config->getBowerFileContent();
375
        } catch (RuntimeException $e) { // no bower.json file, package is extraneous
376
377
            return true;
378
        }
379
        if (!isset($bower['dependencies'])) {
380
            return true;
381
        }
382
        // package is a direct dependencies
383
        if (isset($bower['dependencies'][$package->getName()])) {
384
            return false;
385
        }
386
        // look for dependencies of dependencies
387
        foreach ($bower['dependencies'] as $name => $version) {
388
            $dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name . '/.bower.json');
389
            $depBower = json_decode($dotBowerJson, true);
390
            if (isset($depBower['dependencies'][$package->getName()])) {
391
                return false;
392
            }
393
            // look for dependencies of dependencies of dependencies
394
            if (isset($depBower['dependencies'])) {
395
                foreach ($depBower['dependencies'] as $name1 => $version1) {
396
                    $dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name1 . '/.bower.json');
397
                    $depDepBower = json_decode($dotBowerJson, true);
398
                    if (isset($depDepBower['dependencies'][$package->getName()])) {
399
                        return false;
400
                    }
401
                }
402
            }
403
        }
404
405
        return true;
406
    }
407
408
    /**
409
     * @param  array $params
410
     * @return array
411
     */
412
    protected function createAClearBowerFile(array $params)
413
    {
414
        $authors = ['Beelab <[email protected]>'];
415
        if (!empty($params['author'])) {
416
            $authors[] = $params['author'];
417
        }
418
        $structure = [
419
            'name'         => $params['name'],
420
            'authors'      => $authors,
421
            'private'      => true,
422
            'dependencies' => new \StdClass(),
423
        ];
424
425
        return $structure;
426
    }
427
428
    /**
429
     * @param  PackageInterface $package
430
     * @param  bool             $setInfo
431
     * @return string
432
     */
433
    protected function getPackageTag(PackageInterface $package, $setInfo = false)
434
    {
435
        $decode = $this->findPackage($package);
436
        // open package repository
437
        $repoUrl = $decode['url'];
438
        $this->repository->setUrl($repoUrl)->setHttpClient($this->githubClient);
439
        $packageTag = $this->repository->findPackage($package->getRequiredVersion());
440
        if (is_null($packageTag)) {
441
            throw new RuntimeException(sprintf('Cannot find package %s version %s.', $package->getName(), $package->getRequiredVersion()));
442
        }
443
        $bowerJson = $this->repository->getBower($packageTag);
444
        $bower = json_decode($bowerJson, true);
445
        if (!is_array($bower)) {
446
            throw new RuntimeException(sprintf('Invalid bower.json found in package %s: %s.', $package->getName(), $bowerJson));
447
        }
448
        if ($setInfo) {
449
            $package->setInfo($bower);
450
        }
451
452
        return $packageTag;
453
    }
454
455
    /**
456
     * @param  string $name
457
     * @return array
458
     */
459
    protected function findPackage($package)
460
    {
461
        $name = $package->getName();
462
        
463
        if( $url = $package->getRequiredVersionUrl() ){
464
            return ['name'=>$name,'url'=>$url];
465
        }
466
        
467
        try {
468
            $response = $this->githubClient->getHttpClient()->get($this->config->getBasePackagesUrl().urlencode($name));
469
        } catch (RuntimeException $e) {
470
            throw new RuntimeException(sprintf('Cannot fetch registry info for package %s from search registry (%s).', $name, $e->getMessage()));
471
        }
472
        $packageInfo = json_decode($response->getBody(true), true);
473
        if (!is_array($packageInfo) || empty($packageInfo['url'])) {
474
            throw new RuntimeException(sprintf('Registry info for package %s has malformed json or is missing "url".', $name));
475
        }
476
477
        return $packageInfo;
478
    }
479
480
    /**
481
     * @param PackageInterface $package
482
     */
483
    private function cachePackage(PackageInterface $package)
484
    {
485
        // get release archive from repository
486
        $file = $this->repository->getRelease();
487
488
        $tmpFileName = $this->config->getCacheDir() . '/tmp/' . $package->getName();
489
        $this->filesystem->write($tmpFileName, $file);
490
    }
491
492
    /**
493
     * @param PackageInterface $package
494
     * @param bool             $isDependency
495
     */
496
    private function updateBowerFile(PackageInterface $package, $isDependency = false)
497
    {
498
        if ($this->config->isSaveToBowerJsonFile() && !$isDependency) {
499
            try {
500
                $this->config->updateBowerJsonFile($package);
501
            } catch (RuntimeException $e) {
502
                $this->output->writelnNoBowerJsonFile();
503
            }
504
        }
505
    }
506
}
507