Test Failed
Branch feature/v1_stable_fixes (e805e7)
by Diego
04:22
created

AbstractRepository::search()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Blackmine\Repository;
6
7
use Blackmine\Client\ClientInterface;
8
use Blackmine\Client\ClientOptions;
9
use Blackmine\Client\Client;
10
use Blackmine\Model\AbstractModel;
11
use Blackmine\Model\User\User;
12
use Doctrine\Common\Collections\ArrayCollection;
13
use Doctrine\Common\Collections\Collection;
14
use Error;
15
use JsonException;
16
use Blackmine\Exception\Api\AbstractApiException;
17
use Blackmine\Exception\InvalidModelException;
18
use Blackmine\Exception\Api\EntityNotFoundException;
19
20
abstract class AbstractRepository implements RepositoryInterface
21
{
22
    use RepositoryTrait;
23
24
    protected static array $relation_class_map = [];
25
26
    /**
27
     * @param Client $client
28
     * @param array|array[] $options
29
     */
30
    public function __construct(
31
        protected Client $client,
32
        protected array $options = [
33
            ClientOptions::CLIENT_OPTION_REQUEST_HEADERS => []
34
        ]
35
    ) {
36
    }
37
38
    /**
39
     * Returns the repository construction options
40
     *
41
     * @return array
42
     */
43
    public function getOptions(): array
44
    {
45
        return $this->options;
46
    }
47
48
    /**
49
     * Returns the ClientInterface instance
50
     *
51
     * @return ClientInterface
52
     */
53
    public function getClient(): ClientInterface
54
    {
55
        return $this->client;
56
    }
57
58
    /**
59
     * Starts impersonating user. Only if provided api_key is from an admin account.
60
     *
61
     * @param string|User $user
62
     * @return $this
63
     */
64
    public function actingAs(string | User $user): self
65
    {
66
        if ($user instanceof User) {
67
            $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS][ClientOptions::REDMINE_IMPERSONATE_HEADER] =
68
                $user->getLogin();
69
        } else {
70
            $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS][ClientOptions::REDMINE_IMPERSONATE_HEADER] =
71
                $user;
72
        }
73
74
        return $this;
75
    }
76
77
    /**
78
     * Retrieves the entity given by id.
79
     *
80
     * @param mixed $id
81
     * @return AbstractModel|null
82
     * @throws AbstractApiException
83
     * @throws EntityNotFoundException
84
     * @throws JsonException
85
     */
86
    public function get(mixed $id): ?AbstractModel
87
    {
88
        $params = [];
89
        $endpoint_url = $this->getEndpoint() . "/" . $id . "." . $this->client->getFormat();
90
91
        if (!empty($this->getFetchRelations())) {
92
            $params = $this->addRelations($params);
93
        }
94
95
        $api_response = $this->client->get(
96
            endpoint: $this->constructEndpointUrl($endpoint_url, $params),
97
            headers: $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS] ?? []
98
        );
99
100
        if ($api_response->isSuccess()) {
101
            $model_class = $this->getModelClass();
102
            $model = new $model_class();
103
            $model_data = $api_response->getData()[$model->getEntityName()] ?? null;
104
105
            if ($model_data) {
106
                $model->fromArray($model_data);
107
                $this->hydrateRelations($model);
108
109
                return $model;
110
            }
111
112
            throw new EntityNotFoundException();
113
        }
114
115
        throw AbstractApiException::fromApiResponse($api_response);
116
    }
117
118
    /**
119
     * Retrieves the whole entity collection for a given endpoint.
120
     * Allows custom endpoints passed as parameter.
121
     *
122
     * @param string|null $endpoint
123
     * @return Collection
124
     * @throws JsonException
125
     */
126
    public function all(?string $endpoint = null): Collection
127
    {
128
        $params = [];
129
130
        if (!empty($this->getFetchRelations())) {
131
            $params = $this->addRelations($params);
132
        }
133
134
        $ret = new ArrayCollection();
135
136
        $api_endpoint = $endpoint ?? $this->getEndpoint() . "." . $this->client->getFormat();
137
138
        $api_response = $this->client->get(
139
            endpoint: $this->constructEndpointUrl($api_endpoint, $params),
140
            headers: $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS] ?? []
141
        );
142
        if (isset($api_response->getData()[static::API_ROOT])) {
0 ignored issues
show
Bug introduced by
The constant Blackmine\Repository\AbstractRepository::API_ROOT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
143
            $ret = $this->getCollection($api_response->getData()[static::API_ROOT]);
144
        }
145
146
        return $ret;
147
    }
148
149
    /**
150
     * Creates the entity passed as parameter.
151
     * Returns the updated entity.
152
     *
153
     * @param AbstractModel $model
154
     * @return AbstractModel|null
155
     * @throws AbstractApiException
156
     * @throws InvalidModelException
157
     * @throws JsonException
158
     */
159
    public function create(AbstractModel $model): ?AbstractModel
160
    {
161
        $model_class = $this->getModelClass();
162
        if (!$model instanceof $model_class) {
163
            throw new InvalidModelException(
164
                'Wrong model class for ' . $this->getEndpoint() . " api. Expected " . $this->getModelClass()
165
            );
166
        }
167
168
        $api_response = $this->client->post(
169
            endpoint: $this->getEndpoint() . "." . $this->client->getFormat(),
170
            body: json_encode($model->getPayload(), JSON_THROW_ON_ERROR),
171
            headers: $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS] ?? []
172
        );
173
174
        if ($api_response->isSuccess()) {
175
            $model_data = $api_response->getData()[$model->getEntityName()] ?? null;
176
177
            if ($model_data) {
178
                $model->fromArray($model_data);
179
                $this->hydrateRelations($model);
180
181
                return $model;
182
            }
183
        }
184
185
        throw AbstractApiException::fromApiResponse($api_response);
186
    }
187
188
    /**
189
     * Updates the entity passed as parameter.
190
     * Returns the updated entity.
191
     *
192
     * @param AbstractModel $model
193
     * @return AbstractModel|null
194
     * @throws AbstractApiException
195
     * @throws InvalidModelException
196
     * @throws JsonException
197
     */
198
    public function update(AbstractModel $model): ?AbstractModel
199
    {
200
        $model_class = $this->getModelClass();
201
        if (!$model instanceof $model_class) {
202
            throw new InvalidModelException(
203
                'Wrong model class for ' . $this->getEndpoint() . " api. Expected " . $this->getModelClass()
204
            );
205
        }
206
207
        $this->updateRelations($model);
208
209
        $api_response = $this->client->put(
210
            endpoint: $this->getEndpoint() . "/" . $model->getId() . "." . $this->client->getFormat(),
211
            body: json_encode($model->getPayload(), JSON_THROW_ON_ERROR),
212
            headers: $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS] ?? []
213
        );
214
215
        if ($api_response->isSuccess()) {
216
            return $model;
217
        }
218
219
        throw AbstractApiException::fromApiResponse($api_response);
220
    }
221
222
    /**
223
     * Deletes the entity passed as parameter.
224
     *
225
     * @param AbstractModel $model
226
     * @throws AbstractApiException
227
     * @throws InvalidModelException
228
     * @throws JsonException
229
     */
230
    public function delete(AbstractModel $model): void
231
    {
232
        $model_class = $this->getModelClass();
233
        if (!$model instanceof $model_class) {
234
            throw new InvalidModelException(
235
                'Wrong model class for ' . $this->getEndpoint() . " api. Expected " . $this->getModelClass()
236
            );
237
        }
238
239
        $endpoint_url = $this->getEndpoint() . "/" . $model->getId() . "." . $this->client->getFormat();
240
        $api_response = $this->client->delete(
241
            endpoint: $endpoint_url,
242
            headers: $this->options[ClientOptions::CLIENT_OPTION_REQUEST_HEADERS] ?? []
243
        );
244
245
        if (!$api_response->isSuccess()) {
246
            throw AbstractApiException::fromApiResponse($api_response);
247
        }
248
    }
249
250
    /**
251
     * @param string $relation
252
     * @return string|null
253
     * @ignore
254
     */
255
    public static function getRelationClassFor(string $relation): ?string
256
    {
257
        return static::$relation_class_map[$relation] ?? null;
258
    }
259
260
    /**
261
     * @return string
262
     * @ignore
263
     */
264
    public function getEndpoint(): string
265
    {
266
        if (defined('static::API_ROOT')) {
267
            return static::API_ROOT;
0 ignored issues
show
Bug introduced by
The constant Blackmine\Repository\AbstractRepository::API_ROOT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
268
        }
269
270
        throw new Error('Mandatory constant API_ROOT not defined in class: ' . get_class($this));
271
    }
272
273
    /**
274
     * @return array
275
     * @ignore
276
     */
277
    public function getAllowedFilters(): array
278
    {
279
        return static::$allowed_filters;
280
    }
281
282
    /**
283
     * @return array
284
     * @ignore
285
     */
286
    public function getRelationClassMap(): array
287
    {
288
        return static::$relation_class_map;
289
    }
290
291
    /**
292
     * @return array
293
     * @ignore
294
     */
295
    public function getFetchRelations(): array
296
    {
297
        return $this->fetch_relations;
298
    }
299
300
    /**
301
     * @param string $relation
302
     * @ignore
303
     */
304
    public function addRelationToFetch(string $relation): void
305
    {
306
        if (array_key_exists($relation, $this->getRelationClassMap())) {
307
            $this->fetch_relations[] = $relation;
308
        }
309
    }
310
311
    /**
312
     * @param string $url
313
     * @param array $params
314
     * @return string
315
     * @ignore
316
     */
317
    public function constructEndpointUrl(string $url, array $params): string
318
    {
319
        if (empty($params)) {
320
            return $url;
321
        }
322
323
        return $url . '?' . preg_replace(
324
            '/%5B[0-9]+%5D/simU',
325
            '%5B%5D',
326
            http_build_query($params)
327
        );
328
    }
329
330
    /**
331
     * @return string
332
     * @ignore
333
     */
334
    abstract public function getModelClass(): string;
335
}
336