Completed
Pull Request — master (#154)
by Alexey
04:34
created

Bowerphp::isNeedUpdate()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
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
348
            return true;
349
        }
350
        if (!isset($bower['dependencies'])) {
351
            return true;
352
        }
353
        // package is a direct dependencies
354
        if (isset($bower['dependencies'][$package->getName()])) {
355
            return false;
356
        }
357
        // look for dependencies of dependencies
358
        foreach ($bower['dependencies'] as $name => $version) {
359
            $dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name . '/.bower.json');
360
            $depBower = json_decode($dotBowerJson, true);
361
            if (isset($depBower['dependencies'][$package->getName()])) {
362
                return false;
363
            }
364
            // look for dependencies of dependencies of dependencies
365
            if (isset($depBower['dependencies'])) {
366
                foreach ($depBower['dependencies'] as $name1 => $version1) {
367
                    $dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name1 . '/.bower.json');
368
                    $depDepBower = json_decode($dotBowerJson, true);
369
                    if (isset($depDepBower['dependencies'][$package->getName()])) {
370
                        return false;
371
                    }
372
                }
373
            }
374
        }
375
376
        return true;
377
    }
378
379
    /**
380
     * @param  array $params
381
     * @return array
382
     */
383
    protected function createAClearBowerFile(array $params)
384
    {
385
        $authors = ['Beelab <[email protected]>'];
386
        if (!empty($params['author'])) {
387
            $authors[] = $params['author'];
388
        }
389
        $structure = [
390
            'name'         => $params['name'],
391
            'authors'      => $authors,
392
            'private'      => true,
393
            'dependencies' => new \StdClass(),
394
        ];
395
396
        return $structure;
397
    }
398
399
    /**
400
     * @param  PackageInterface $package
401
     * @param  bool             $setInfo
402
     * @return string
403
     */
404
    protected function getPackageTag(PackageInterface $package, $setInfo = false)
405
    {
406
        $decode = $this->findPackage($package->getName());
407
        // open package repository
408
        $repoUrl = $decode['url'];
409
        $this->repository->setUrl($repoUrl)->setHttpClient($this->githubClient);
410
        $packageTag = $this->repository->findPackage($package->getRequiredVersion());
411
        if (is_null($packageTag)) {
412
            throw new RuntimeException(sprintf('Cannot find package %s version %s.', $package->getName(), $package->getRequiredVersion()));
413
        }
414
        $bowerJson = $this->repository->getBower($packageTag);
415
        $bower = json_decode($bowerJson, true);
416
        if (!is_array($bower)) {
417
            throw new RuntimeException(sprintf('Invalid bower.json found in package %s: %s.', $package->getName(), $bowerJson));
418
        }
419
        if ($setInfo) {
420
            $package->setInfo($bower);
421
        }
422
423
        return $packageTag;
424
    }
425
426
    /**
427
     * @param  string $name
428
     * @return array
429
     */
430
    protected function findPackage($name)
431
    {
432
        try {
433
            $response = $this->githubClient->getHttpClient()->get($this->config->getBasePackagesUrl() . urlencode($name));
434
        } catch (RuntimeException $e) {
435
            throw new RuntimeException(sprintf('Cannot fetch registry info for package %s from search registry (%s).', $name, $e->getMessage()));
436
        }
437
        $packageInfo = json_decode($response->getBody(true), true);
438
        if (!is_array($packageInfo) || empty($packageInfo['url'])) {
439
            throw new RuntimeException(sprintf('Registry info for package %s has malformed json or is missing "url".', $name));
440
        }
441
442
        return $packageInfo;
443
    }
444
445
    /**
446
     * @param PackageInterface $package
447
     */
448
    private function cachePackage(PackageInterface $package)
449
    {
450
        // get release archive from repository
451
        $file = $this->repository->getRelease();
452
453
        $tmpFileName = $this->config->getCacheDir() . '/tmp/' . $package->getName();
454
        $this->filesystem->write($tmpFileName, $file);
455
    }
456
457
    /**
458
     * @param PackageInterface $package
459
     * @param bool             $isDependency
460
     */
461
    private function updateBowerFile(PackageInterface $package, $isDependency = false)
462
    {
463
        if ($this->config->isSaveToBowerJsonFile() && !$isDependency) {
464
            try {
465
                $this->config->updateBowerJsonFile($package);
466
            } catch (RuntimeException $e) {
467
                $this->output->writelnNoBowerJsonFile();
468
            }
469
        }
470
    }
471
    
472
    /**
473
     * Update only if needed is greater version
474
     * 
475
     * @param PackageInterface $package
476
     * @return boolean
477
     */
478
    function isNeedUpdate($package){
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Comprehensibility Best Practice introduced by
It is recommend to declare an explicit visibility for isNeedUpdate.

Generally, we recommend to declare visibility for all methods in your source code. This has the advantage of clearly communication to other developers, and also yourself, how this method should be consumed.

If you are not sure which visibility to choose, it is a good idea to start with the most restrictive visibility, and then raise visibility as needed, i.e. start with private, and only raise it to protected if a sub-class needs to have access, or public if an external class needs access.

Loading history...
479
		if(!$package->getRequiredVersion()){
480
			return false;
481
		}
482
		$packageBower = $this->config->getPackageBowerFileContent($package);
483
		$requireVersion = $package->getRequiredVersion();
484
        $compare = '=';
485
        
486
		preg_match( '!^[^\\d]+!', $requireVersion, $matches);
487
		if(!empty($matches[0])){
488
			$compare = $matches[0];
489
			$requireVersion = substr($requireVersion, strlen($compare));
490
			if($compare == '~'){
491
				$compare = '>';
492
				$requireVersion = substr($requireVersion, 0, strrpos($requireVersion, '.'));
493
			}
494
		}
495
		return version_compare($requireVersion, $packageBower['version'], $compare);
496
	}
497
}
498