Issues (47)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Cli/HubphCommands.php (15 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Hubph\Cli;
4
5
use Consolidation\AnnotatedCommand\CommandData;
6
use Consolidation\Filter\FilterOutputData;
7
use Consolidation\Filter\LogicalOpFactory;
8
use Consolidation\OutputFormatters\Options\FormatterOptions;
9
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
10
use Consolidation\OutputFormatters\StructuredData\PropertyList;
11
use Psr\Log\LoggerAwareInterface;
12
use Psr\Log\LoggerAwareTrait;
13
use Robo\Common\ConfigAwareTrait;
14
use Robo\Contract\ConfigAwareInterface;
15
use Consolidation\AnnotatedCommand\CommandError;
16
use Hubph\HubphAPI;
17
use Hubph\VersionIdentifiers;
18
use Hubph\PullRequests;
19
use Hubph\Git\WorkingCopy;
20
21
class HubphCommands extends \Robo\Tasks implements ConfigAwareInterface, LoggerAwareInterface
22
{
23
    use ConfigAwareTrait;
24
    use LoggerAwareTrait;
25
26
    /**
27
     * Report who we have authenticated as
28
     *
29
     * @command whoami
30
     */
31
    public function whoami($options = ['as' => 'default'])
32
    {
33
        $api = $this->api($options['as']);
34
        $authenticated = $api->whoami();
35
        $authenticatedUser = $authenticated['login'];
36
37
        $this->say("Authenticated as $authenticatedUser.");
38
    }
39
40
    /**
41
     * @command pr:close
42
     */
43 View Code Duplication
    public function prClose($projectWithOrg = '', $number = '', $options = ['as' => 'default'])
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
44
    {
45
        if (empty($number) && preg_match('#^[0-9]*$#', $projectWithOrg)) {
46
            $number = $projectWithOrg;
47
            $projectWithOrg = '';
48
        }
49
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
50
        list($org, $project) = explode('/', $projectWithOrg, 2);
51
52
        $api = $this->api($options['as']);
53
        $api->prClose($org, $project, $number);
0 ignored issues
show
$number is of type string, but the function expects a object<Hubph\PullRequests>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
54
    }
55
56
    /*
57
     * hubph pr:check --vid=php-7.0./31 --vid=php-7.1./20
58
     *
59
     * status 0 and csv with PR numbers to close
60
     *
61
     *  - or -
62
     *
63
     * status 1 if all vid/vvals exist and nothing more needs to be done
64
     */
65
66
    /**
67
     * @command pr:check
68
     */
69
    public function prCheck(
70
        $options = [
71
            'message|m' => '',
72
            'file|F' => '',
73
            'base' => '',
74
            'head' => '',
75
            'as' => 'default',
76
            'format' => 'yaml',
77
            'idempotent' => false
78
        ]
79
    ) {
80
        $projectWithOrg = $this->projectWithOrg();
81
82
        // Get the commit message from --message or --file
83
        $message = $this->getMessage($options);
84
85
        // Determine all of the vid/vval pairs if idempotent
86
        $vids = $this->getVids($options, $message);
87
88
        $api = $this->api($options['as']);
89
        list($status, $result) = $api->prCheck($projectWithOrg, $vids);
90
91
        if ($status) {
92
            return new CommandError($result, $status);
0 ignored issues
show
$status is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
93
        }
94
        if (is_string($result)) {
95
            $this->logger->notice("No open pull requests that need to be closed.");
96
            return;
97
        }
98
        return implode(',', (array)$result);
99
    }
100
101
    /**
102
     * @command pr:create
103
     * @aliases pull-request
104
     */
105
    public function prCreate(
106
        $options = [
107
            'message|m' => '',
108
            'body' => '',
109
            'file|F' => '',
110
            'base' => 'master',
111
            'head' => '',
112
            'as' => 'default',
113
            'format' => 'yaml',
114
            'idempotent' => false
115
        ]
116
    ) {
117
        $projectWithOrg = $this->projectWithOrg();
118
119
        // Get the commit message from --message or --file
120
        $message = $this->getMessage($options);
121
        list($org, $project) = explode('/', $projectWithOrg, 2);
122
123
        // Determine all of the vid/vval pairs if idempotent
124
        $vids = $this->getVids($options, $message);
125
126
        $api = $this->api($options['as']);
127
        list($status, $result) = $api->prCheck($projectWithOrg, $vids);
128
129
        if ($status) {
130
            return new CommandError($result, $status);
0 ignored issues
show
$status is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
131
        }
132
133
        // TODO: We could look up 'head' if it is not provided.
134
        if (empty($options['head'])) {
135
            throw new \Exceptions('Must provide --head');
136
        }
137
138
        // Create the requested PR
139
        $api->prCreate($org, $project, $message, $options['body'], $options['base'], $options['head']);
140
141
        // If $result is an array, it will contain
142
        // all of the pull request numbers to close.
143
        // TODO: We should make a wrapper object for $result
144
        if (is_array($result)) {
145
            list($org, $project) = explode('/', $projectWithOrg, 2);
146
            $api->prClose($org, $project, $result);
0 ignored issues
show
$result is of type array, but the function expects a object<Hubph\PullRequests>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
147
        }
148
    }
149
150
    protected function getMessage($options)
151
    {
152
        if (!empty($options['message'])) {
153
            return $options['message'];
154
        }
155
        if (!empty($options['file'])) {
156
            return file_get_contents($options['file']);
157
        }
158
        return '';
159
    }
160
161
    protected function getVids($options, $message)
162
    {
163
        $vids = new VersionIdentifiers();
164
165
        //if (empty($options['idempotent'])) {
166
        //    return $vids;
167
        //}
168
169
        // Allow the caller to define more specific vid / vval patterns
170
        if (!empty($options['vid'])) {
171
            $vids->setVidPattern($options['vid']);
172
        }
173
        if (!empty($options['vval'])) {
174
            $vids->setVvalPattern($options['vval']);
175
        }
176
177
        $vids->addVidsFromMessage($message);
178
        return $vids;
179
    }
180
181
    protected function projectWithOrg($projectWithOrg = '')
182
    {
183
        if (!empty($projectWithOrg)) {
184
            return $projectWithOrg;
185
        }
186
187
        return $this->getProjectWithOrgFromRemote();
188
    }
189
190
    protected function getProjectWithOrgFromRemote($remote = 'origin', $cwd = '')
191
    {
192
        $remote = $this->getRemote($remote, $cwd);
193
194
        return $this->getProjectWithOrfFromUrl($remote);
195
    }
196
197 View Code Duplication
    protected function getProjectWithOrfFromUrl($remote)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
198
    {
199
        $remote = preg_replace('#^git@[^:]*:#', '', $remote);
200
        $remote = preg_replace('#^[^:]*://[^/]/#', '', $remote);
201
        $remote = preg_replace('#\.git$#', '', $remote);
202
203
        return $remote;
204
    }
205
206
    protected function getRemote($remote = 'origin', $cwd = '')
207
    {
208
        if (!empty($cwd)) {
209
            $cwd = "-C $cwd";
210
        }
211
        return exec("git {$cwd} config --get remote.{$remote}.url");
212
    }
213
214
    /**
215
     * @command pr:find
216
     * @param $projectWithOrg The project to work on, e.g. org/project
217
     * @option $q Query term
218
     * @filter-output
219
     * @field-labels
220
     *   url: Url
221
     *   id: ID
222
     *   node_id: Node ID
223
     *   html_url: HTML Url
224
     *   diff_url: Diff Url
225
     *   patch_url: Patch Url
226
     *   issue_url: Issue Url
227
     *   number: Number
228
     *   state: State
229
     *   locked: Locked
230
     *   title: Title
231
     *   user: User
232
     *   body: Boday
233
     *   created_at: Created
234
     *   updated_at: Updated
235
     *   closed_at: Closed
236
     *   merged_at: Merged
237
     *   merge_commit_sha: Merge Commit
238
     *   assignee: Assignee
239
     *   assignees: Assignees
240
     *   requested_reviewers: Requested Reviewers
241
     *   requested_teams: Requested Teams
242
     *   labels: Labels
243
     *   milestone: Milestone
244
     *   commits_url: Commit Url
245
     *   review_comments_url: Review Comments Url
246
     *   review_comment_url: Review Comment Url
247
     *   comments_url: Comments Url
248
     *   statuses_url: Statuses Url
249
     *   head: Head
250
     *   base: Base
251
     *   _links: Links
252
     * @default-fields number,user,title
253
     * @default-string-field number
254
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
255
    */
256
    public function prFind($projectWithOrg = '', $options = ['as' => 'default', 'format' => 'yaml', 'q' => ''])
257
    {
258
        $api = $this->api($options['as']);
259
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
260
        $q = $options['q'];
261
262
        if (!empty($q)) {
263
            $q = $q . ' ';
264
        }
265
        $q = $q . 'repo:' . $projectWithOrg;
266
        $searchResults = $api->gitHubAPI()->api('search')->issues($q);
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Github\Api\ApiInterface as the method issues() does only exist in the following implementations of said interface: Github\Api\CurrentUser, Github\Api\Enterprise\Stats, Github\Api\Organization, Github\Api\Search.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
267
        $pullRequests = $searchResults['items'];
268
269
        $pullRequests = $this->keyById($pullRequests, 'number');
270
        $result = new RowsOfFields($pullRequests);
271
        $this->addTableRenderFunction($result);
272
273
        return $result;
274
    }
275
276
    /**
277
     * @command org:repos
278
     * @param $org The org to list
279
     * @filter-output
280
     * @field-labels
281
     *   url: Url
282
     *   id: ID
283
     *   owner: Owner
284
     *   name: Shortname
285
     *   full_name: Name
286
     *   private: Private
287
     *   fork: Fork
288
     *   created_at: Created
289
     *   updated_at: Updated
290
     *   pushed_at: Pushed
291
     *   git_url: Git URL
292
     *   ssh_url: SSH URL
293
     *   svn_url: SVN URL
294
     *   homepage: Homepage
295
     *   size: Size
296
     *   stargazers_count: Stargazers
297
     *   watchers_count: Watchers
298
     *   language: Language
299
     *   has_issues: Has Issues
300
     *   has_projects: Has Projects
301
     *   has_downloads: Has Downloads
302
     *   has_wiki: Has Wiki
303
     *   has_pages: Has Pages
304
     *   forks_count: Forks
305
     *   archived: Archived
306
     *   disabled: Disabled
307
     *   open_issues_count: Open Issues
308
     *   default_branch: Default Branch
309
     *   license: License
310
     *   permissions: Permissions
311
     * @default-fields full_name,language,default_branch
312
     * @default-string-field full_name
313
     *
314
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
315
     */
316
    public function orgRepos($org, $options = ['as' => 'default', 'format' => 'table'])
317
    {
318
        $api = $this->api($options['as']);
319
        $pager = $api->resultPager();
320
321
        $repoApi = $api->gitHubAPI()->api('organization');
322
        $repos = $pager->fetchAll($repoApi, 'repositories', [$org]);
323
324
        $data = new \Consolidation\OutputFormatters\StructuredData\RowsOfFields($repos);
325
        $this->addTableRenderFunction($data);
326
327
        return $data;
328
    }
329
330
    /**
331
     * @command repo:info
332
     * @param $projectWithOrg The project to work on, e.g. org/project
333
     * @field-labels
334
     *   url: Url
335
     *   id: ID
336
     *   owner: Owner
337
     *   name: Shortname
338
     *   full_name: Name
339
     *   private: Private
340
     *   fork: Fork
341
     *   created_at: Created
342
     *   updated_at: Updated
343
     *   pushed_at: Pushed
344
     *   git_url: Git URL
345
     *   ssh_url: SSH URL
346
     *   svn_url: SVN URL
347
     *   homepage: Homepage
348
     *   size: Size
349
     *   stargazers_count: Stargazers
350
     *   watchers_count: Watchers
351
     *   language: Language
352
     *   has_issues: Has Issues
353
     *   has_projects: Has Projects
354
     *   has_downloads: Has Downloads
355
     *   has_wiki: Has Wiki
356
     *   has_pages: Has Pages
357
     *   forks_count: Forks
358
     *   archived: Archived
359
     *   disabled: Disabled
360
     *   open_issues_count: Open Issues
361
     *   default_branch: Default Branch
362
     *   license: License
363
     *   permissions: Permissions
364
     *
365
     * @return Consolidation\OutputFormatters\StructuredData\PropertyList
366
     */
367
    public function repoInfo($projectWithOrg = '', $options = ['as' => 'default', 'format' => 'table'])
368
    {
369
        $api = $this->api($options['as']);
370
371
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
372
        list($org, $project) = explode('/', $projectWithOrg, 2);
373
374
        $info = $api->gitHubAPI()->api('repo')->show($org, $project);
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Github\Api\ApiInterface as the method show() does only exist in the following implementations of said interface: Github\Api\Authorizations, Github\Api\CurrentUser, Github\Api\CurrentUser\Notifications, Github\Api\CurrentUser\PublicKeys, Github\Api\Deployment, Github\Api\Enterprise\License, Github\Api\Enterprise\Stats, Github\Api\Gist\Comments, Github\Api\Gists, Github\Api\GitData\Blobs, Github\Api\GitData\Commits, Github\Api\GitData\References, Github\Api\GitData\Tags, Github\Api\GitData\Trees, Github\Api\Issue, Github\Api\Issue\Comments, Github\Api\Issue\Events, Github\Api\Issue\Labels, Github\Api\Issue\Milestones, Github\Api\Miscellaneous\CodeOfConduct, Github\Api\Miscellaneous\Gitignore, Github\Api\Miscellaneous\Licenses, Github\Api\Organization, Github\Api\Organization\Hooks, Github\Api\Organization\Members, Github\Api\Organization\Projects, Github\Api\Organization\Teams, Github\Api\Project\AbstractProjectApi, Github\Api\Project\Cards, Github\Api\Project\Columns, Github\Api\PullRequest, Github\Api\PullRequest\Comments, Github\Api\PullRequest\Review, Github\Api\Repo, Github\Api\Repository\Assets, Github\Api\Repository\Comments, Github\Api\Repository\Commits, Github\Api\Repository\Contents, Github\Api\Repository\DeployKeys, Github\Api\Repository\Downloads, Github\Api\Repository\Hooks, Github\Api\Repository\Labels, Github\Api\Repository\Pages, Github\Api\Repository\Projects, Github\Api\Repository\Protection, Github\Api\Repository\Releases, Github\Api\Repository\Statuses, Github\Api\User.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
375
376
        $data = new \Consolidation\OutputFormatters\StructuredData\PropertyList($info);
377
        $this->addTableRenderFunction($data);
378
379
        return $data;
380
    }
381
382
    /**
383
     * @command repo:default-branch:switch
384
     * @aliases switch-default
385
     */
386
    public function switchDefaultBranch($projectWithOrg = '', $options = ['as' => 'default', 'branch' => 'main'])
387
    {
388
        $api = $this->api($options['as']);
389
390
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
391
        list($org, $project) = explode('/', $projectWithOrg, 2);
392
393
        $repoApi = $api->gitHubAPI()->api('repo');
394
        $info = $repoApi->show($org, $project);
395
        $currentDefault = $info['default_branch'];
396
        $newDefault = $options['branch'];
397
398
        if ($currentDefault == $newDefault) {
399
            $this->logger->notice("Default branch is already {default}.", ['default' => $currentDefault]);
400
            return;
401
        }
402
403
        $referencesApi = $api->gitHubAPI()->api('gitData')->references();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Github\Api\ApiInterface as the method references() does only exist in the following implementations of said interface: Github\Api\GitData.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
404
405
        // Get the sha of the current HEAD of the current default
406
        $currentDefaultInfo = $referencesApi->show($org, $project, "heads/$currentDefault");
407
        $currentHeadSha = $currentDefaultInfo['object']['sha'];
408
409
        // TODO: We could pass $api here, but that would modify the remote url of 'origin'.
410
        // For now we'll use whatever auth is set up for the project at the cwd.
411
        $workingCopy = WorkingCopy::fromDir(getcwd());
412
413
        if ($workingCopy->valid() && ($workingCopy->projectWithOrg() == $projectWithOrg)) {
414
            $this->configureDefaultWithWorkingCopy($workingCopy, $currentDefault, $newDefault, $currentHeadSha);
415
        } else {
416
            // Create a new branch for the new default. If it's already there,
417
            // then we'll assume it's at the desired SHA.
418
            try {
419
                $referencesApi->create($org, $project, ['ref' => "refs/heads/$newDefault", 'sha' => $currentHeadSha]);
420
            } catch (\Exception $e) {
421
                $this->logger->notice("Branch {new} already exists; using it as-is.", ['new' => $newDefault]);
422
            }
423
        }
424
425
        $result = $repoApi->update($org, $project, ['default_branch' => $newDefault]);
0 ignored issues
show
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
426
        $this->logger->notice("Set default branch to {new}.", ['new' => $newDefault]);
427
    }
428
429
    protected function configureDefaultWithWorkingCopy($workingCopy, $currentDefault, $newDefault, $currentHeadSha)
430
    {
431
        $statusResult = $workingCopy->status();
432
        if (!empty($statusResult)) {
433
            throw new \Exception('Working copy not clean; commit, reset or ignore all modified files.');
434
        }
435
436
        // TODO: Check to see if new branch already exists, as we do for the API case?
437
        $workingCopy->createBranch($newDefault, $currentHeadSha);
438
439
        $fixupList = ['README.md', '.travis.yml', 'composer.json', '.circleci/config.yml'];
440
        $alteredList = [];
441
        foreach ($fixupList as $file) {
442
            if (file_exists($file)) {
443
                $contents = file_get_contents($file);
444
                $altered = str_replace($currentDefault, $newDefault, $contents);
445
                if ($altered != $contents) {
446
                    file_put_contents($file, $altered);
447
                    $alteredList[] = $file;
448
                }
449
            }
450
        }
451
452
        $statusResult = $workingCopy->status();
453
        if (!empty($statusResult)) {
454
            passthru('git diff');
455
456
            // If we modified composer.json, update composer.lock
457
            if (in_array('composer.json', $alteredList)) {
458
                passthru('composer update');
459
            }
460
461
            $workingCopy->add('.');
462
463
            $workingCopy->commit("Change references to old default branch '$currentDefault' to new default branch '$newDefault'");
464
        }
465
466
        $workingCopy->push('origin', $newDefault);
467
    }
468
469
    /**
470
     * @command pr:show
471
     * @field-labels
472
     *   url: Url
473
     *   id: ID
474
     *   node_id: Node ID
475
     *   html_url: HTML Url
476
     *   diff_url: Diff Url
477
     *   patch_url: Patch Url
478
     *   issue_url: Issue Url
479
     *   number: Number
480
     *   state: State
481
     *   locked: Locked
482
     *   title: Title
483
     *   user: User
484
     *   body: Boday
485
     *   created_at: Created
486
     *   updated_at: Updated
487
     *   closed_at: Closed
488
     *   mergeable: Mergeable
489
     *   mergeable_state: Mergable State
490
     *   merged_at: Merged
491
     *   merge_commit_sha: Merge Commit
492
     *   assignee: Assignee
493
     *   assignees: Assignees
494
     *   requested_reviewers: Requested Reviewers
495
     *   requested_teams: Requested Teams
496
     *   labels: Labels
497
     *   milestone: Milestone
498
     *   commits_url: Commit Url
499
     *   review_comments_url: Review Comments Url
500
     *   review_comment_url: Review Comment Url
501
     *   comments_url: Comments Url
502
     *   statuses_url: Statuses Url
503
     *   head: Head
504
     *   base: Base
505
     *   _links: Links
506
     * @return Consolidation\OutputFormatters\StructuredData\PropertyList
507
     */
508
    public function prShow($projectWithOrg = '', $number = '', $options = ['as' => 'default', 'format' => 'table'])
509
    {
510
        if (empty($number) && preg_match('#^[0-9]*$#', $projectWithOrg)) {
511
            $number = $projectWithOrg;
512
            $projectWithOrg = '';
513
        }
514
        $api = $this->api($options['as']);
515
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
516
517
        list($org, $project) = explode('/', $projectWithOrg, 2);
518
519
        $pullRequest = $api->gitHubAPI()->api('pull_request')->show($org, $project, $number);
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Github\Api\ApiInterface as the method show() does only exist in the following implementations of said interface: Github\Api\Authorizations, Github\Api\CurrentUser, Github\Api\CurrentUser\Notifications, Github\Api\CurrentUser\PublicKeys, Github\Api\Deployment, Github\Api\Enterprise\License, Github\Api\Enterprise\Stats, Github\Api\Gist\Comments, Github\Api\Gists, Github\Api\GitData\Blobs, Github\Api\GitData\Commits, Github\Api\GitData\References, Github\Api\GitData\Tags, Github\Api\GitData\Trees, Github\Api\Issue, Github\Api\Issue\Comments, Github\Api\Issue\Events, Github\Api\Issue\Labels, Github\Api\Issue\Milestones, Github\Api\Miscellaneous\CodeOfConduct, Github\Api\Miscellaneous\Gitignore, Github\Api\Miscellaneous\Licenses, Github\Api\Organization, Github\Api\Organization\Hooks, Github\Api\Organization\Members, Github\Api\Organization\Projects, Github\Api\Organization\Teams, Github\Api\Project\AbstractProjectApi, Github\Api\Project\Cards, Github\Api\Project\Columns, Github\Api\PullRequest, Github\Api\PullRequest\Comments, Github\Api\PullRequest\Review, Github\Api\Repo, Github\Api\Repository\Assets, Github\Api\Repository\Comments, Github\Api\Repository\Commits, Github\Api\Repository\Contents, Github\Api\Repository\DeployKeys, Github\Api\Repository\Downloads, Github\Api\Repository\Hooks, Github\Api\Repository\Labels, Github\Api\Repository\Pages, Github\Api\Repository\Projects, Github\Api\Repository\Protection, Github\Api\Repository\Releases, Github\Api\Repository\Statuses, Github\Api\User.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
520
521
        $result = new PropertyList($pullRequest);
522
        $this->addTableRenderFunction($result);
523
524
        return $result;
525
    }
526
527
    /**
528
     * @command pr:statuses
529
     * @field-labels
530
     *   url: Url
531
     *   id: ID
532
     *   state: State
533
     *   description: Description
534
     *   node_id: Node ID
535
     *   context: Context
536
     *   avatar_url: Avatar URL
537
     *   target_url: Target URL
538
     *   creator: Creator
539
     *   created_at: Created
540
     *   updated_at: Updated
541
     * @default-fields id,creator,state,description
542
     * @default-string-field description
543
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
544
     */
545 View Code Duplication
    public function prStatuses($projectWithOrg = '', $number = '', $options = ['as' => 'default', 'format' => 'yaml'])
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
546
    {
547
        if (empty($number) && preg_match('#^[0-9]*$#', $projectWithOrg)) {
548
            $number = $projectWithOrg;
549
            $projectWithOrg = '';
550
        }
551
        $api = $this->api($options['as']);
552
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
553
554
        $pullRequestStatus = $api->prStatuses($projectWithOrg, $number);
555
556
        $result = new RowsOfFields($pullRequestStatus);
557
        $this->addTableRenderFunction($result);
558
559
        return $result;
560
    }
561
562
    /**
563
     * @command pr:list
564
     * @param $projectWithOrg The project to work on, e.g. org/project
565
     * @filter-output
566
     * @field-labels
567
     *   url: Url
568
     *   id: ID
569
     *   node_id: Node ID
570
     *   html_url: HTML Url
571
     *   diff_url: Diff Url
572
     *   patch_url: Patch Url
573
     *   issue_url: Issue Url
574
     *   number: Number
575
     *   state: State
576
     *   locked: Locked
577
     *   title: Title
578
     *   user: User
579
     *   body: Boday
580
     *   created_at: Created
581
     *   updated_at: Updated
582
     *   closed_at: Closed
583
     *   merged_at: Merged
584
     *   merge_commit_sha: Merge Commit
585
     *   assignee: Assignee
586
     *   assignees: Assignees
587
     *   requested_reviewers: Requested Reviewers
588
     *   requested_teams: Requested Teams
589
     *   labels: Labels
590
     *   milestone: Milestone
591
     *   commits_url: Commit Url
592
     *   review_comments_url: Review Comments Url
593
     *   review_comment_url: Review Comment Url
594
     *   comments_url: Comments Url
595
     *   statuses_url: Statuses Url
596
     *   head: Head
597
     *   base: Base
598
     *   _links: Links
599
     * @default-fields number,user,title
600
     * @default-string-field number
601
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
602
     */
603
    public function prList($projectWithOrg = '', $options = ['state' => 'open', 'as' => 'default', 'format' => 'table'])
604
    {
605
        $api = $this->api($options['as']);
606
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
607
608
        list($org, $project) = explode('/', $projectWithOrg, 2);
609
610
        $pullRequests = $api->gitHubAPI()->api('pull_request')->all($org, $project, ['state' => $options['state']]);
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Github\Api\ApiInterface as the method all() does only exist in the following implementations of said interface: Github\Api\Authorizations, Github\Api\CurrentUser\Emails, Github\Api\CurrentUser\Followers, Github\Api\CurrentUser\Memberships, Github\Api\CurrentUser\Notifications, Github\Api\CurrentUser\PublicKeys, Github\Api\CurrentUser\Starring, Github\Api\CurrentUser\Watchers, Github\Api\Deployment, Github\Api\Enterprise\Stats, Github\Api\Gist\Comments, Github\Api\Gists, Github\Api\GitData\References, Github\Api\GitData\Tags, Github\Api\Issue, Github\Api\Issue\Comments, Github\Api\Issue\Events, Github\Api\Issue\Labels, Github\Api\Issue\Milestones, Github\Api\Issue\Timeline, Github\Api\Miscellaneous\CodeOfConduct, Github\Api\Miscellaneous\Emojis, Github\Api\Miscellaneous\Gitignore, Github\Api\Miscellaneous\Licenses, Github\Api\Notification, Github\Api\Organization, Github\Api\Organization\Hooks, Github\Api\Organization\Members, Github\Api\Organization\Projects, Github\Api\Organization\Teams, Github\Api\Project\Cards, Github\Api\Project\Columns, Github\Api\PullRequest, Github\Api\PullRequest\Comments, Github\Api\PullRequest\Review, Github\Api\PullRequest\ReviewRequest, Github\Api\Repo, Github\Api\Repository\Assets, Github\Api\Repository\Collaborators, Github\Api\Repository\Comments, Github\Api\Repository\Commits, Github\Api\Repository\DeployKeys, Github\Api\Repository\Downloads, Github\Api\Repository\Forks, Github\Api\Repository\Hooks, Github\Api\Repository\Labels, Github\Api\Repository\Projects, Github\Api\Repository\Releases, Github\Api\Repository\Stargazers, Github\Api\User.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
611
612
        $pullRequests = $this->keyById($pullRequests, 'number');
613
614
        $result = new RowsOfFields($pullRequests);
615
        $this->addTableRenderFunction($result);
616
617
        return $result;
618
    }
619
620
    protected function addTableRenderFunction($data)
621
    {
622
        $data->addRendererFunction(
623
            function ($key, $cellData, FormatterOptions $options, $rowData) {
0 ignored issues
show
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $rowData is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
624
                if (empty($cellData)) {
625
                    return '';
626
                }
627
                if (is_array($cellData)) {
628
                    if ($key == 'permissions') {
629
                        return implode(',', array_filter(array_keys($cellData)));
630
                    }
631
                    foreach (['login', 'label', 'name'] as $k) {
632
                        if (isset($cellData[$k])) {
633
                            return $cellData[$k];
634
                        }
635
                    }
636
                    // TODO: simplify
637
                    //   assignees
638
                    //   requested_reviewers
639
                    //   requested_teams
640
                    //   labels
641
                    //   _links
642
                    return json_encode($cellData, true);
643
                }
644
                if (!is_string($cellData)) {
645
                    return var_export($cellData, true);
646
                }
647
                return $cellData;
648
            }
649
        );
650
    }
651
652
    protected function keyById($data, $field)
653
    {
654
        return
655
            array_column(
656
                array_map(
657
                    function ($k) use ($data, $field) {
658
                        return [$data[$k][$field], $data[$k]];
659
                    },
660
                    array_keys($data)
661
                ),
662
                1,
663
                0
664
            );
665
    }
666
667
    protected function api($as = 'default')
668
    {
669
        $api = new HubphAPI($this->getConfig());
670
        $api->setAs($as);
671
672
        return $api;
673
    }
674
}
675