Completed
Push — master ( c49cbc...039121 )
by Timur
02:32
created

ResourceCommand::command()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Laravel\Forge\Commands;
4
5
use Laravel\Forge\Contracts\ResourceContract;
6
use InvalidArgumentException;
7
use Psr\Http\Message\ResponseInterface;
8
9
abstract class ResourceCommand extends Command
10
{
11
    /**
12
     * Resource ID.
13
     */
14
    protected $resourceId;
15
16
    /**
17
     * Resource path.
18
     *
19
     * @return string
20
     */
21
    abstract public function resourcePath();
22
23
    /**
24
     * Resource class name.
25
     *
26
     * @return string
27
     */
28
    abstract public function resourceClass();
29
30
    /**
31
     * Determines if current command is list command.
32
     *
33
     * @return bool
34
     */
35
    protected function isListCommand(): bool
36
    {
37
        return method_exists($this, 'listResponseItemsKey');
38
    }
39
40
    /**
41
     * Command name.
42
     *
43
     * @return string
44
     */
45
    public function command()
46
    {
47
        return $this->resourcePath();
48
    }
49
50
    /**
51
     * HTTP request method.
52
     *
53
     * @return string
54
     */
55
    public function requestMethod()
56
    {
57
        if ($this->isListCommand()) {
58
            return 'GET';
59
        }
60
61
        return is_null($this->getResourceId()) ? 'POST' : 'GET';
62
    }
63
64
    /**
65
     * HTTP request URL.
66
     *
67
     * @param \Laravel\Forge\Contracts\ResourceContract $resource
68
     *
69
     * @return string
70
     */
71
    public function requestUrl(ResourceContract $resource)
72
    {
73
        $resourcePath = $this->resourcePath();
74
75
        if (!is_null($this->getResourceId())) {
76
            $resourcePath .= '/'.$this->getResourceId();
77
        }
78
79
        return $resource->apiUrl($resourcePath);
80
    }
81
82
    /**
83
     * Set resource ID.
84
     *
85
     * @param int|string $resourceId
86
     *
87
     * @return static
88
     */
89
    public function setResourceId($resourceId)
90
    {
91
        $this->resourceId = $resourceId;
92
93
        return $this;
94
    }
95
96
    /**
97
     * Get resource ID.
98
     *
99
     * @return int|string|null
100
     */
101
    public function getResourceId()
102
    {
103
        return $this->resourceId;
104
    }
105
106
    /**
107
     * Handle command response.
108
     *
109
     * @param \Psr\Http\Message\ResponseInterface       $response
110
     * @param \Laravel\Forge\Contracts\ResourceContract $owner
111
     *
112
     * @return \Laravel\Forge\Contracts\ResourceContract|array|bool|string
113
     */
114
    public function handleResponse(ResponseInterface $response, ResourceContract $owner)
115
    {
116
        if ($this->isListCommand()) {
117
            return $this->handleListCommandResponse($response, $owner);
118
        }
119
120
        $className = $this->resourceClass();
121
122
        return $className::createFromResponse($response, $owner->getApi(), $owner);
123
    }
124
125
    /**
126
     * List response handler.
127
     *
128
     * @param \Psr\Http\Message\ResponseInterface       $response
129
     * @param \Laravel\Forge\Contracts\ResourceContract $owner
130
     *
131
     * @throws \InvalidArgumentException
132
     *
133
     * @return array
134
     */
135
    public function handleListCommandResponse(ResponseInterface $response, ResourceContract $owner)
136
    {
137
        $itemsKey = $this->listResponseItemsKey();
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Laravel\Forge\Commands\ResourceCommand as the method listResponseItemsKey() does only exist in the following sub-classes of Laravel\Forge\Commands\ResourceCommand: Laravel\Forge\Daemons\Commands\ListDaemonsCommand, Laravel\Forge\Firewall\C...istFirewallRulesCommand, Laravel\Forge\Jobs\Commands\ListJobsCommand, Laravel\Forge\Services\M...stMysqlDatabasesCommand, Laravel\Forge\Services\M...s\ListMysqlUsersCommand, Laravel\Forge\Sites\Commands\ListSitesCommand, Laravel\Forge\SshKeys\Commands\ListSshKeysCommand, Laravel\Forge\Workers\Commands\ListWorkersCommand. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
138
139
        $json = json_decode((string) $response->getBody(), true);
140
141
        if (empty($json[$itemsKey])) {
142
            throw new InvalidArgumentException('Given response is not a '.$this->resourcePath().' response.');
143
        }
144
145
        $items = [];
146
        $className = $this->resourceClass();
147
148
        foreach ($json[$itemsKey] as $item) {
149
            $items[] = new $className($owner->getApi(), $item, $owner);
150
        }
151
152
        return $items;
153
    }
154
}
155