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

Command::getPayloadData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
1
<?php
2
3
namespace Laravel\Forge\Commands;
4
5
use Laravel\Forge\Contracts\ResourceContract;
6
use InvalidArgumentException;
7
8
abstract class Command
9
{
10
    /**
11
     * Command payload.
12
     *
13
     * @var array
14
     */
15
    protected $payload = [];
16
17
    /**
18
     * Command name.
19
     *
20
     * @return string
21
     */
22
    abstract public function command();
0 ignored issues
show
Coding Style Best Practice introduced by
Please use __construct() instead of a PHP4-style constructor that is named after the class.
Loading history...
23
24
    /**
25
     * Command description.
26
     *
27
     * @return string
28
     */
29
    public function description()
30
    {
31
        return '';
32
    }
33
34
    /**
35
     * Determines if command can run.
36
     *
37
     * @return bool
38
     */
39
    public function runnable()
40
    {
41
        return true;
42
    }
43
44
    /**
45
     * HTTP request method.
46
     *
47
     * @return string
48
     */
49
    public function requestMethod()
50
    {
51
        return 'POST';
52
    }
53
54
    /**
55
     * HTTP request URL.
56
     *
57
     * @param \Laravel\Forge\Contracts\ResourceContract $owner
58
     *
59
     * @return string
60
     */
61
    public function requestUrl(ResourceContract $owner)
62
    {
63
        return $owner->apiUrl();
64
    }
65
66
    /**
67
     * HTTP request options.
68
     *
69
     * @return array
70
     */
71
    public function requestOptions()
72
    {
73
        return [
74
            'form_params' => $this->payload,
75
        ];
76
    }
77
78
    /**
79
     * Set command payload.
80
     *
81
     * @param array $payload
82
     *
83
     * @return static
84
     */
85
    public function withPayload(array $payload)
86
    {
87
        $this->payload = $payload;
88
89
        return $this;
90
    }
91
92
    /**
93
     * Set payload data.
94
     *
95
     * @param string|int $key
96
     * @param mixed      $value
97
     *
98
     * @return static
99
     */
100
    public function attachPayload($key, $value)
101
    {
102
        if (is_null($this->payload)) {
103
            $this->payload = [];
104
        }
105
106
        $this->payload[$key] = $value;
107
108
        return $this;
109
    }
110
111
    /**
112
     * Return payload data.
113
     *
114
     * @param string|int $key
115
     * @param mixed      $default = null
116
     *
117
     * @return mixed|null
118
     */
119
    public function getPayloadData($key, $default = null)
120
    {
121
        if (is_null($this->payload)) {
122
            return;
123
        }
124
125
        return $this->payload[$key] ?? $default;
126
    }
127
128
    /**
129
     * Determines if payload has requried keys.
130
     *
131
     * @param string|int|array $keys
132
     *
133
     * @return bool
134
     */
135
    public function hasPayloadData($keys): bool
136
    {
137
        if (is_null($this->payload)) {
138
            return false;
139
        }
140
141
        if (!is_array($keys)) {
142
            $keys = [$keys];
143
        }
144
145
        foreach ($keys as $key) {
146
            if (!isset($this->payload[$key])) {
147
                return false;
148
            }
149
        }
150
151
        return true;
152
    }
153
154
    /**
155
     * Execute command on single or multiple resources.
156
     *
157
     * @param array|\Laravel\Forge\Contracts\ResourceContract $resource
158
     *
159
     * @throws \InvalidArgumentException
160
     *
161
     * @return bool|array
162
     */
163
    public function on($resource)
164
    {
165
        if (!$this->runnable()) {
166
            throw new InvalidArgumentException('Command execution is restricted.');
167
        }
168
169
        if (is_array($resource)) {
170
            return $this->executeOnMulitpleResources($resource);
171
        }
172
173
        return $this->executeOn($resource);
174
    }
175
176
    /**
177
     * Alias for "on" command.
178
     *
179
     * @param array|\Laravel\Forge\Contracts\ResourceContract $resource
180
     *
181
     * @throws \InvalidArgumentException
182
     *
183
     * @see \Laravel\Forge\Services\Commands\AbstractServiceCommand::on
184
     *
185
     * @return bool|array
186
     */
187
    public function from($resource)
188
    {
189
        return $this->on($resource);
190
    }
191
192
    /**
193
     * Execute current command on given resource.
194
     *
195
     * @param \Laravel\Forge\Contracts\ResourceContract $resource
196
     *
197
     * @return bool|mixed
198
     */
199
    protected function executeOn(ResourceContract $resource)
200
    {
201
        $response = $this->execute($resource);
202
203
        if (method_exists($this, 'handleResponse')) {
204
            return $this->handleResponse($response, $resource);
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\Command as the method handleResponse() does only exist in the following sub-classes of Laravel\Forge\Commands\Command: Laravel\Forge\Commands\ResourceCommand, Laravel\Forge\Daemons\Commands\CreateDaemonCommand, Laravel\Forge\Daemons\Commands\DaemonCommand, Laravel\Forge\Daemons\Commands\GetDaemonCommand, Laravel\Forge\Daemons\Commands\ListDaemonsCommand, Laravel\Forge\Deployment\Commands\DeployCommand, Laravel\Forge\Deployment...isableDeploymentCommand, Laravel\Forge\Deployment...EnableDeploymentCommand, Laravel\Forge\Deployment...GetDeploymentLogCommand, Laravel\Forge\Deployment...DeploymentScriptCommand, Laravel\Forge\Deployment...DeploymentStatusCommand, Laravel\Forge\Deployment...DeploymentScriptCommand, Laravel\Forge\Firewall\C...eateFirewallRuleCommand, Laravel\Forge\Firewall\C...nds\FirewallRuleCommand, Laravel\Forge\Firewall\C...\GetFirewallRuleCommand, Laravel\Forge\Firewall\C...istFirewallRulesCommand, Laravel\Forge\Jobs\Commands\CreateJobCommand, Laravel\Forge\Jobs\Commands\GetJobCommand, Laravel\Forge\Jobs\Commands\JobCommand, Laravel\Forge\Jobs\Commands\ListJobsCommand, Laravel\Forge\Services\M...ateMysqlDatabaseCommand, Laravel\Forge\Services\M...\CreateMysqlUserCommand, Laravel\Forge\Services\M...GetMysqlDatabaseCommand, Laravel\Forge\Services\M...nds\GetMysqlUserCommand, Laravel\Forge\Services\M...stMysqlDatabasesCommand, Laravel\Forge\Services\M...s\ListMysqlUsersCommand, Laravel\Forge\Services\M...ds\MysqlDatabaseCommand, Laravel\Forge\Services\M...mmands\MysqlUserCommand, Laravel\Forge\Sites\Commands\CreateSiteCommand, Laravel\Forge\Sites\Commands\GetSiteCommand, Laravel\Forge\Sites\Commands\ListSitesCommand, Laravel\Forge\Sites\Commands\SiteCommand, Laravel\Forge\SshKeys\Commands\CreateSshKeyCommand, Laravel\Forge\SshKeys\Commands\GetSshKeyCommand, Laravel\Forge\SshKeys\Commands\ListSshKeysCommand, Laravel\Forge\SshKeys\Commands\SshKeyCommand, Laravel\Forge\Workers\Commands\CreateWorkerCommand, Laravel\Forge\Workers\Commands\GetWorkerCommand, Laravel\Forge\Workers\Commands\ListWorkersCommand, Laravel\Forge\Workers\Commands\WorkerCommand. 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...
205
        }
206
207
        return true;
208
    }
209
210
    /**
211
     * Execute current command on multiple resources.
212
     *
213
     * @param array $resources
214
     *
215
     * @return array
216
     */
217
    protected function executeOnMulitpleResources(array $resources): array
218
    {
219
        $results = [];
220
221
        foreach ($resources as $resource) {
222
            $results[$resource->name()] = $this->executeOn($resource);
223
        }
224
225
        return $results;
226
    }
227
228
    /**
229
     * Execute current command.
230
     *
231
     * @param \Laravel\Forge\Contracts\ResourceContract $resource
232
     */
233
    protected function execute(ResourceContract $resource)
234
    {
235
        return $resource->getHttpClient()->request(
236
            $this->requestMethod(),
237
            $this->requestUrl($resource),
238
            $this->requestOptions()
239
        );
240
    }
241
}
242