GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( f61021...307738 )
by
unknown
04:53
created

Base   D

Complexity

Total Complexity 92

Size/Duplication

Total Lines 537
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 11
Bugs 4 Features 4
Metric Value
c 11
b 4
f 4
dl 0
loc 537
rs 4.8717
wmc 92
lcom 1
cbo 6

15 Methods

Rating   Name   Duplication   Size   Complexity  
B configureVariables() 0 39 4
A pushPackages() 0 13 2
A doComposerUpdate() 0 14 3
C rewriteRequirements() 0 49 11
B rewriteRequirement() 0 35 3
A reloadRequires() 0 4 1
C checkoutPackage() 0 56 11
C mergePackage() 0 53 13
C resolveRequirementsConflict() 0 25 8
A jsonEncode() 0 6 1
C mergeRequirements() 0 21 13
B getPackages() 0 12 6
A assertPackageIsWhiteListed() 0 6 2
B isPackageAllowed() 0 19 6
C isPackageWhiteListed() 0 31 8

How to fix   Complexity   

Complex Class

Complex classes like Base 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Base, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * See class comment
4
 *
5
 * PHP Version 5
6
 *
7
 * @category   Netresearch
8
 * @package    Netresearch\Kite\Workflow
9
 * @subpackage Composer
10
 * @author     Christian Opitz <[email protected]>
11
 * @license    http://www.netresearch.de Netresearch Copyright
12
 * @link       http://www.netresearch.de
13
 */
14
15
namespace Netresearch\Kite\Workflow\Composer;
16
use Netresearch\Kite\Service\Composer\Package;
17
use Netresearch\Kite\Workflow;
18
use Netresearch\Kite\Exception;
19
20
/**
21
 * Abstract for composer workflows
22
 *
23
 * @category   Netresearch
24
 * @package    Netresearch\Kite\Workflow
25
 * @subpackage Composer
26
 * @author     Christian Opitz <[email protected]>
27
 * @license    http://www.netresearch.de Netresearch Copyright
28
 * @link       http://www.netresearch.de
29
 */
30
abstract class Base extends Workflow
31
{
32
    /**
33
     * @var Package[]
34
     */
35
    protected $pushPackages = array();
36
37
    /**
38
     * @var array
39
     */
40
    protected $whitelists;
41
42
    /**
43
     * @var array
44
     */
45
    protected $packageNames;
46
47
    /**
48
     * Configure the variables
49
     *
50
     * @return array
51
     */
52
    protected function configureVariables()
53
    {
54
        $config = $this->getParent()->get('config');
55
        if (!array_key_exists('composer', $config)) {
56
            $config['composer'] = [];
57
        }
58
        foreach (['whitelistNames', 'whitelistPaths', 'whitelistRemotes'] as $key) {
59
            if (!array_key_exists($key, $config['composer'])) {
60
                $config['composer'][$key] = null;
61
            }
62
        }
63
        return [
64
            'packages' => array(
65
                'type' => 'array',
66
                'label' => 'Package name(s) to limit this operation to',
67
                'shortcut' => 'p',
68
                'option' => true
69
            ),
70
            'whitelistNames' => array(
71
                'default' => '{config["composer"]["whitelistNames"]}',
72
                'type' => 'string',
73
                'label' => 'Regular expression for package names, to limit this operation to',
74
                'option' => true
75
            ),
76
            'whitelistPaths' => array(
77
                'default' => '{config["composer"]["whitelistPaths"]}',
78
                'type' => 'string',
79
                'label' => 'Regular expression for package paths, to limit this operation to',
80
                'option' => true
81
            ),
82
            'whitelistRemotes' => array(
83
                'default' => '{config["composer"]["whitelistRemotes"]}',
84
                'type' => 'string',
85
                'label' => 'Regular expression for package remote urls, to limit this operation to',
86
                'option' => true
87
            ),
88
            '--'
89
        ] + parent::configureVariables();
90
    }
91
92
93
    /**
94
     * Push all packages marked to be pushed
95
     *
96
     * @return void
97
     */
98
    protected function pushPackages()
99
    {
100
        foreach ($this->pushPackages as $i => $package) {
101
            $this->assertPackageIsWhiteListed($package);
102
            $this->console->output("Pushing <comment>$package->name</comment>", false);
103
            $this->git('push', $package->path, array('u' => 'origin', $package->branch));
104
            $this->console->output(
105
                str_repeat(chr(8), strlen($package->name))
106
                . '<info>' . $package->name . '</info>'
107
            );
108
            unset($this->pushPackages[$i]);
109
        }
110
    }
111
112
    /**
113
     * doComposerUpdateIfNecessary and possible
114
     *
115
     * @return void
116
     */
117
    protected function doComposerUpdate()
118
    {
119
        try {
120
            $this->composer('update');
121
        } catch (\Exception $e) {
122
            $this->console->output('<warning>Composer update failed</warning>');
123
            $this->console->output('<comment>This might have occured because previous pushes to git did not reache the composer repo yet</comment>');
124
            if ($this->confirm('Retry?')) {
125
                $this->doComposerUpdate();
126
            } else {
127
                $this->doExit('', 1);
128
            }
129
        }
130
    }
131
132
    /**
133
     * Go through all packages and check if packages requiring those packages,
134
     * still require their (likely new) versions.
135
     *
136
     * If not and $autoFix or user agrees, the require-statements in the
137
     * dependent packages are changed accordingly.
138
     *
139
     * @param Package[] $packages Packages
140
     * @param bool      $autoFix  Whether to autofix wrong requirements
141
     *
142
     * @return void
143
     */
144
    protected function rewriteRequirements(array &$packages, $autoFix = false)
145
    {
146
        $checkedOutPackages = array_keys($packages);
147
        $unfixedRequirements = 0;
148
        while ($packageName = array_shift($checkedOutPackages)) {
149
            $branch = $packages[$packageName]->branch;
150
            $version = 'dev-' . $branch;
151
            foreach ($this->getPackages(false, false) as $package) {
152
                if (array_key_exists($packageName, $package->requires)) {
153
                    // TODO: Set required version to branch alias, if any
154
                    $requiredVersion = $package->requires[$packageName];
155
                    if ($requiredVersion === '@dev') {
156
                        $requiredVersion = 'dev-master';
157
                    }
158
                    if ($requiredVersion !== $version) {
159
                        $this->assertPackageIsWhiteListed($package);
160
                        if (!$package->git) {
161
                            throw new Exception("Package {$package->name} required to be installed from source");
162
                        }
163
                        if ($autoFix) {
164
                            $fix = true;
165
                            $this->output("Changing required version of {$packageName} in {$package->name} from {$requiredVersion} to {$version}");
166
                        } else {
167
                            $this->output("<warning>{$package->name} depends on {$packageName} {$package->requires[$packageName]} and not {$version} as expected</warning>");
168
                            $this->output('<comment>If you don\'t fix that, the branches will probably change with composer update</comment>');
169
                            $fix = $this->confirm('Fix that?');
170
                        }
171
                        if ($fix) {
172
                            if (!array_key_exists($package->name, $packages)) {
173
                                $this->checkoutPackage($package, $branch, true);
174
                                $checkedOutPackages[] = $package->name;
175
                                $packages[$package->name] = $package;
176
                            }
177
178
                            $this->pushPackages[$packageName] = $packages[$packageName];
179
                            $this->rewriteRequirement($package, $packageName, $version);
180
                        } else {
181
                            $unfixedRequirements++;
182
                        }
183
                    }
184
                }
185
            }
186
        }
187
        if ($unfixedRequirements) {
188
            $this->doExit(
189
                'It seems like a composer update is required but due to probably incorrect dependencies you have to do that manually', 1
190
            );
191
        }
192
    }
193
194
    /**
195
     * Change the require statement for $requiredPackage to the newVersion in $package
196
     *
197
     * @param Package $package         The package
198
     * @param string  $requiredPackage The required package
199
     * @param string  $newVersion      The new version
200
     *
201
     * @return void
202
     */
203
    protected function rewriteRequirement($package, $requiredPackage, $newVersion)
204
    {
205
        $this->assertPackageIsWhiteListed($package);
206
207
        $currentVersion = $package->requires[$requiredPackage];
208
        $composerFile = $package->path . '/composer.json';
209
        $composerFileContents = file_get_contents($composerFile);
210
        $newComposerFileContents = preg_replace(
211
            sprintf(
212
                '/(^\s*"require"\s*:\s*\{[^\}]+"%s"\s*:\s*")%s/m',
213
                preg_quote($requiredPackage, '/'),
214
                preg_quote($currentVersion, '/')
215
            ),
216
            '$1' . $newVersion,
217
            $composerFileContents
218
        );
219
        file_put_contents($composerFile, $newComposerFileContents);
220
        $this->reloadRequires($package);
0 ignored issues
show
Deprecated Code introduced by
The method Netresearch\Kite\Workflo...\Base::reloadRequires() has been deprecated with message: Use $package->reloadRequires()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
221
        if ($package->requires[$requiredPackage] !== $newVersion) {
222
            file_put_contents($composerFile, $composerFileContents);
223
            $this->output('<error>Could not replace version</error> - generated composer.json was:');
224
            $this->output($newComposerFileContents);
225
            throw new Exception('Replacing version failed');
226
        }
227
228
        $this->git('commit', $package->path, array('n' => true, 'm' => "Change required version of $requiredPackage to $newVersion", 'composer.json'));
229
        if (!isset($package->source)) {
230
            $package->source = new \stdClass();
231
        }
232
        $package->source->reference = $this->git('rev-parse', $package->path, array('HEAD'));
233
234
        $this->console->output("Made <comment>$package->name</comment> require <comment>$requiredPackage $newVersion</comment>");
235
236
        $this->pushPackages[$package->name] = $package;
237
    }
238
239
    /**
240
     * Reload the requires from $package composer.json to $package->requires
241
     *
242
     * If $detectChanges, and there are changes on the requirements not in $ignorePackages
243
     * composer update is requested
244
     *
245
     * @param Package $package The package
246
     *
247
     * @deprecated Use $package->reloadRequires()
248
     *
249
     * @return void
250
     */
251
    protected function reloadRequires($package)
252
    {
253
        $package->reloadRequires();
254
    }
255
256
    /**
257
     * Checkout a package at a branch
258
     *
259
     * @param Package $package The package
260
     * @param string  $branch  The branch
261
     * @param bool    $create  Create the branch if it doesn't exist
262
     *
263
     * @return bool|null Whether checkout was successful or null when package is already at this branch
264
     */
265
    protected function checkoutPackage($package, $branch, $create = false)
266
    {
267
        $this->assertPackageIsWhiteListed($package);
268
269
        if ($package->branch === $branch) {
270
            return null;
271
        }
272
        if (!$package->git) {
273
            throw new Exception('Non git package can not be checked out');
274
        }
275
        $remoteBranch = 'origin/' . $branch;
276
        $isRemote = in_array($remoteBranch, $package->branches, true);
277
        if (in_array($branch, $package->branches, true)) {
278
            $this->git('checkout', $package->path, $branch);
279
        } elseif ($isRemote) {
280
            $this->git('checkout', $package->path, array('b' => $branch, $remoteBranch));
281
        } elseif ($create) {
282
            $branches = array_unique(
283
                array_map(
284
                    function ($el) {
285
                        $parts = explode('/', $el);
286
                        return array_pop($parts);
287
                    },
288
                    $package->branches
289
                )
290
            );
291
            sort($branches);
292
            $inferFromBranch = $this->choose(
293
                "Select branch to create new branch '$branch' from in {$package->name}",
294
                $branches, in_array('master', $branches, true) ? 'master' : $branch
295
            );
296
            if ($inferFromBranch !== $package->branch) {
297
                $this->checkoutPackage($package, $inferFromBranch);
298
            }
299
            $this->git('checkout', $package->path, array('b' => $branch));
300
            $package->branches[] = $branch;
301
        } else {
302
            return false;
303
        }
304
305
        if ($isRemote) {
306
            if (!isset($package->upstreams[$branch]) || $package->upstreams[$branch] !== $remoteBranch) {
307
                $this->git('branch', $package->path, array('u' => $remoteBranch));
308
                $package->upstreams[$branch] = $remoteBranch;
309
            }
310
            $this->git('rebase', $package->path);
311
        }
312
313
        $this->console->output("Checked out <comment>{$package->name}</comment> at <comment>$branch</comment>");
314
315
        $this->reloadRequires($package);
0 ignored issues
show
Deprecated Code introduced by
The method Netresearch\Kite\Workflo...\Base::reloadRequires() has been deprecated with message: Use $package->reloadRequires()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
316
        $package->version = 'dev-' . $branch;
317
        $package->branch = $branch;
318
319
        return true;
320
    }
321
322
    /**
323
     * Merge a $branch into $package's current branch
324
     *
325
     * @param Package $package The package
326
     * @param string  $branch  The branch
327
     * @param null    $message The commit message (if any)
328
     * @param bool    $squash  Whether to squash the changes
329
     *
330
     * @return void
331
     */
332
    protected function mergePackage($package, $branch, $message = null, $squash = false)
333
    {
334
        $this->assertPackageIsWhiteListed($package);
335
336
        if (!$package->git) {
337
            throw new Exception('Non git package can not be merged');
338
        }
339
340
        $this->git('fetch', $package->path, array('force' => true, 'origin', $branch . ':' . $branch));
341
342
        $ff = $branch == 'master' ? 'ff' : 'no-ff';
343
        $optArg = array($ff => true, 'no-commit' => true);
344
        if ($squash) {
345
            $optArg['squash'] = true;
346
        }
347
        $optArg[] = $branch;
348
349
        try {
350
            $this->git('merge', $package->path, $optArg);
351
        } catch (\Exception $e) {
352
            $diff = $this->git('diff', $package->path, array('name-only' => true, 'diff-filter' => 'U'));
353
            $conflictedFiles = array_flip(explode("\n", $diff));
354
            if (array_key_exists('composer.json', $conflictedFiles)) {
355
                try {
356
                    $this->resolveRequirementsConflict($package);
357
                    $this->git('add', $package->path, 'composer.json');
358
                } catch (Exception $conflictSolvingException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
359
                }
360
            }
361
            if (array_diff(array_keys($conflictedFiles), ['composer.json'])) {
362
                throw new Exception(
363
                    'There are unresolved conflicts - please resolve them and then commit the result',
364
                    1458307785, isset($conflictSolvingException) ? $conflictSolvingException : null
365
                );
366
            } elseif (isset($conflictSolvingException)) {
367
                throw $conflictSolvingException;
368
            }
369
        }
370
        if (isset($conflictedFiles) || $this->git('status', $package->path, array('porcelain' => true))) {
371
            if (!$message) {
372
                $message = $this->answer(
373
                    'Enter commit message:',
374
                    'Merged ' . $branch . ' into ' . $package->branch
375
                );
376
            }
377
            $this->git('commit', $package->path, array('n' => true, 'm' => $message));
378
        }
379
380
        $this->console->output("Merged with <comment>$branch</comment> in <comment>{$package->name}</comment>");
381
382
        $this->reloadRequires($package);
0 ignored issues
show
Deprecated Code introduced by
The method Netresearch\Kite\Workflo...\Base::reloadRequires() has been deprecated with message: Use $package->reloadRequires()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
383
        $this->pushPackages[$package->name] = $package;
384
    }
385
386
    /**
387
     * Try to solve conflicts inside the require section of the $package composer.json
388
     *
389
     * @param Package $package The package
390
     *
391
     * @return void
392
     */
393
    private function resolveRequirementsConflict($package)
394
    {
395
        $contents = file_get_contents($package->path . '/composer.json');
396
        $ours = @json_decode(
397
            preg_replace('/^<{7}.+\n(.+)\n(\|{7}|={7}).+>{7}.+$/smU', '$1', $contents)
398
        );
399
        $theirs = @json_decode(
400
            preg_replace('/^<{7}.+\n={7}\n(.+)\n>{7}.+$/smU', '$1', $contents)
401
        );
402
        if (!is_object($ours) || !is_object($theirs)) {
403
            throw new Exception('Could not regenerate json file from solved conflicts');
404
        }
405
        $diff = array_diff_key($theirs = get_object_vars($theirs), $ours = get_object_vars($ours));
406
        foreach ($ours as $key => $value) {
407
            if ($key !== 'require' && (!array_key_exists($key, $theirs) || serialize($value) !== serialize($theirs[$key]))) {
408
                $diff[$key] = $value;
409
            }
410
        }
411
        if ($diff !== array()) {
412
            throw new Exception('Can not automerge composer.json due to conflicts outside require object', 1458307516);
413
        }
414
415
        $theirs['require'] = $this->mergeRequirements($package, $ours, $theirs);
416
        file_put_contents($package->path . '/composer.json', $this->jsonEncode($theirs));
417
    }
418
419
    /**
420
     * Encode a variable for JSON file
421
     *
422
     * @param mixed $var The var
423
     *
424
     * @return string
425
     */
426
    protected function jsonEncode($var)
427
    {
428
        return json_encode(
429
            $var, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
430
        ) . "\n";
431
    }
432
433
    /**
434
     * Merge the requirements from different sides of the current $package
435
     *
436
     * @param Package $package The current package
437
     * @param array   $ours    Our composer.json as array
438
     * @param array   $theirs  Their composer.json as array
439
     *
440
     * @return array
441
     */
442
    private function mergeRequirements($package, array $ours, array $theirs)
443
    {
444
        $oursRequire = isset($ours['require']) && is_object($ours['require']) ? get_object_vars($ours['require']) : [];
445
        $theirsRequire = isset($theirs['require']) && is_object($theirs['require']) ? get_object_vars($theirs['require']) : [];
446
        $mergedRequires = array_merge($oursRequire, $theirsRequire);
447
        $packages = $this->getPackages(false, false);
448
        $preferredVersion = 'dev-' . $package->branch;
449
        foreach ($mergedRequires as $packageName => $version) {
450
            $actualVersion = ($version === '@dev') ? 'dev-master' : $version;
451
            if (array_key_exists($packageName, $oursRequire)
452
                && $version !== $oursRequire[$packageName]
453
                && $actualVersion !== $oursRequire[$packageName]
454
                && $actualVersion !== $preferredVersion
455
                && array_key_exists($packageName, $packages)
456
                && in_array($package->branch, $packages[$packageName]->branches, true)
457
            ) {
458
                $mergedRequires[$packageName] = $preferredVersion;
459
            }
460
        }
461
        return $mergedRequires;
462
    }
463
464
    /**
465
     * Get the allowed packages
466
     *
467
     * @param bool $gitOnly     If git packages should be returned only
468
     * @param bool $allowedOnly If allowed packages should be returned only
469
     *
470
     * @return \Netresearch\Kite\Service\Composer\Package[]
471
     */
472
    protected function getPackages($gitOnly = true, $allowedOnly = true)
473
    {
474
        /* @var $packages \Netresearch\Kite\Service\Composer\Package[] */
475
        /* @var $package \Netresearch\Kite\Service\Composer\Package */
476
        $packages = array();
477
        foreach ($this->get('composer.packages') as $package) {
478
            if ((!$gitOnly || $package->git) && (!$allowedOnly || $this->isPackageAllowed($package))) {
479
                $packages[$package->name] = $package;
480
            }
481
        }
482
        return $packages;
483
    }
484
485
    /**
486
     * Assert that package is in white lists
487
     *
488
     * @param Package $package The package
489
     *
490
     * @throws Exception
491
     *
492
     * @return void
493
     */
494
    protected function assertPackageIsWhiteListed($package)
495
    {
496
        if ($this->isPackageWhiteListed($package) === false) {
497
            throw new Exception("Package {$package->name} is not in white list");
498
        }
499
    }
500
501
    /**
502
     * Determine if a package is not excluded by the packages option or white lists
503
     *
504
     * @param Package $package The package
505
     *
506
     * @return bool
507
     */
508
    protected function isPackageAllowed(Package $package)
509
    {
510
511
        if (!is_array($this->packageNames)) {
512
            $this->packageNames = $this->get('packages', []);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->get('packages', array()) of type * is incompatible with the declared type array of property $packageNames.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
513
        }
514
515
        if ($this->packageNames) {
516
            if (!in_array($package->name, $this->packageNames, true)
517
                || $this->isPackageWhiteListed($package) === false
518
                && !$this->confirm("The package $package->name is excluded by your whitelist configuration - are you sure you want to include it anyway?")
519
            ) {
520
                return false;
521
            }
522
            return true;
523
        }
524
525
        return (boolean) $this->isPackageWhiteListed($package);
526
    }
527
528
    /**
529
     * Determine if package is white listed
530
     *
531
     * @param Package $package The package
532
     *
533
     * @return bool|null
534
     */
535
    protected function isPackageWhiteListed(Package $package)
536
    {
537
        if (!is_array($this->whitelists)) {
538
            $this->whitelists = [];
539
            foreach (['path', 'remote', 'name'] as $whiteListType) {
540
                $option = $this->get('whitelist' . ucfirst($whiteListType) . 's');
541
                if ($option) {
542
                    $this->whitelists[$whiteListType] = '#^' . $option . '$#';
543
                }
544
            }
545
        }
546
547
        foreach ($this->whitelists as $type => $pattern) {
548
            $subject = $package->$type;
549
            if ($type === 'path') {
550
                $subject = rtrim(
551
                    $this->console->getFilesystem()->findShortestPath(
552
                        $this->get('composer.rootPackage.path'),
553
                        $subject,
554
                        true
555
                    ),
556
                    '/'
557
                );
558
            }
559
            if (preg_match($pattern, $subject)) {
560
                return true;
561
            }
562
        }
563
564
        return $this->whitelists ? false : null;
565
    }
566
}
567
?>
568