CommandService::__construct()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 3
nc 3
nop 3
1
<?php
2
3
/**
4
 * Copyright 2014 SURFnet bv
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace Surfnet\StepupMiddlewareClient\Service;
20
21
use GuzzleHttp\Client;
22
use RuntimeException;
23
use Surfnet\StepupMiddlewareClient\Exception\CommandExecutionFailedException;
24
use Surfnet\StepupMiddlewareClient\Exception\InvalidArgumentException;
25
use Surfnet\StepupMiddlewareClient\Helper\JsonHelper;
26
27
class CommandService
28
{
29
    /**
30
     * @var Client
31
     */
32
    private $guzzleClient;
33
34
    /**
35
     * @var string
36
     */
37
    private $username;
38
39
    /**
40
     * @var string
41
     */
42
    private $password;
43
44
    /**
45
     * @param Client $guzzleClient A Guzzle client preconfigured with the command URL.
46
     * @param string $username
47
     * @param string $password
48
     */
49
    public function __construct(Client $guzzleClient, $username, $password)
50
    {
51
        if (!is_string($username)) {
52
            throw InvalidArgumentException::invalidType('string', 'username', $username);
53
        }
54
55
        if (!is_string($password)) {
56
            throw InvalidArgumentException::invalidType('string', 'password', $password);
57
        }
58
59
        $this->guzzleClient = $guzzleClient;
60
        $this->username = $username;
61
        $this->password = $password;
62
    }
63
64
    /**
65
     * @param string $commandName
66
     * @param string $uuid
67
     * @param array $payload
68
     * @param array $metadata
69
     * @return ExecutionResult
70
     * @throws CommandExecutionFailedException
71
     */
72
    public function execute($commandName, $uuid, array $payload, array $metadata = [])
73
    {
74
        $this->assertIsValidCommandName($commandName);
75
        if (!is_string($uuid)) {
76
            throw InvalidArgumentException::invalidType('string', 'uuid', $uuid);
77
        }
78
79
        $command = [
80
            'name' => $commandName,
81
            'uuid' => $uuid,
82
            'payload' => $payload,
83
        ];
84
85
        $body = ['command' => $command];
86
87
        if (count($metadata) > 0) {
88
            $body['meta'] = $metadata;
89
        }
90
91
        $requestOptions = [
92
            'json'        => $body,
93
            'http_errors' => false,
94
            'auth'        => [$this->username, $this->password, 'basic'],
95
            'headers'     => ['Accept' => 'application/json'],
96
        ];
97
        $httpResponse = $this->guzzleClient->post(null, $requestOptions);
98
99
        try {
100
            $response = JsonHelper::decode((string) $httpResponse->getBody());
101
        } catch (RuntimeException $e) {
102
            throw new CommandExecutionFailedException(
103
                'Server response could not be decoded as it isn\'t valid JSON.',
104
                0,
105
                $e
106
            );
107
        }
108
109
        return $httpResponse->getStatusCode() == 200
110
            ? $this->processOkResponse($uuid, $response)
111
            : $this->processErrorResponse($uuid, $response);
112
    }
113
114
    /**
115
     * @param mixed $commandName
116
     * @throws InvalidArgumentException
117
     */
118
    private function assertIsValidCommandName($commandName)
119
    {
120
        if (!is_string($commandName)) {
121
            InvalidArgumentException::invalidType('string', 'command', $commandName);
122
        }
123
124
        if (!preg_match('~^[a-z0-9_]+:([a-z0-9_].)*[a-z0-9_]+$~i', $commandName)) {
125
            throw new InvalidArgumentException(
126
                'Command must be formatted AggregateRoot:Command or AggregateRoot:Name.Space.Command'
127
            );
128
        }
129
    }
130
131
    /**
132
     * @param string $uuid
133
     * @param mixed $response
134
     * @return ExecutionResult
135
     */
136
    private function processOkResponse($uuid, $response)
137
    {
138
        if (!isset($response['command'])) {
139
            throw new CommandExecutionFailedException('Unexpected response format: command key missing.');
140
        }
141
142
        if ($response['command'] !== $uuid) {
143
            throw new CommandExecutionFailedException(sprintf(
144
                'Unexpected response: returned command UUID "%s" does not match sent UUID "%s".',
145
                $response['command'],
146
                $uuid
147
            ));
148
        }
149
150
        if (!isset($response['processed_by'])) {
151
            throw new CommandExecutionFailedException('Unexpected response format: processed_by key missing.');
152
        }
153
154
        if (!is_string($response['processed_by'])) {
155
            throw new CommandExecutionFailedException(sprintf(
156
                'Unexpected response format: processed_by should be a string, "%s" given.',
157
                gettype($response['processed_by'])
158
            ));
159
        }
160
161
        return new ExecutionResult($uuid, $response['processed_by']);
162
    }
163
164
    /**
165
     * @param string $uuid
166
     * @param mixed $response
167
     * @return ExecutionResult
168
     */
169
    private function processErrorResponse($uuid, $response)
170
    {
171
        if (!isset($response['errors'])) {
172
            throw new CommandExecutionFailedException('Unexpected response format: errors key missing.');
173
        }
174
175
        if (!is_array($response['errors'])) {
176
            throw new CommandExecutionFailedException(sprintf(
177
                'Unexpected response format: errors should be an array, "%s" given.',
178
                gettype($response['errors'])
179
            ));
180
        }
181
182
        return new ExecutionResult($uuid, null, $response['errors']);
183
    }
184
}
185