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

RepositoryTrait::getCollection()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 19
rs 9.9332
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Blackmine\Repository;
6
7
use Blackmine\Client\ClientInterface;
8
use Blackmine\Client\Response\ApiResponse;
9
use Blackmine\Collection\IdentityCollection;
10
use Blackmine\Collection\PaginatedCollection;
11
use Blackmine\Exception\Api\AbstractApiException;
12
use Blackmine\Model\AbstractModel;
13
use Blackmine\Model\FetchableInterface;
14
use Blackmine\Model\Identity;
15
use Blackmine\Tool\Inflect;
16
use Doctrine\Common\Collections\ArrayCollection;
17
use Doctrine\Common\Collections\Collection;
18
use JsonException;
19
20
use function is_initialized;
21
22
trait RepositoryTrait
23
{
24
25
    protected array $fetch_relations = [];
26
27
    /**
28
     * Adds a relation to get to the query. It applies to the single get operations too.
29
     *
30
     * @param string|array $include
31
     * @return AbstractRepository|CacheableRepository|Issues\Issues|RepositoryTrait
32
     */
33
    public function with(string | array $include): self
34
    {
35
        if (!is_array($include)) {
0 ignored issues
show
introduced by
The condition is_array($include) is always true.
Loading history...
36
            $include = [$include];
37
        }
38
39
        foreach ($include as $item) {
40
            $this->addRelationToFetch($item);
0 ignored issues
show
Bug introduced by
The method addRelationToFetch() does not exist on Blackmine\Repository\RepositoryTrait. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

40
            $this->/** @scrutinizer ignore-call */ 
41
                   addRelationToFetch($item);
Loading history...
41
        }
42
43
        return $this;
44
    }
45
46
    protected function addRelations(array $params): array
47
    {
48
        if (!empty($this->fetch_relations)) {
49
            $params["include"] = implode(",", $this->fetch_relations);
50
        }
51
52
        return $params;
53
    }
54
55
    protected function hydrateRelations(AbstractModel $model): AbstractModel
56
    {
57
        foreach ($this->getFetchRelations() as $relation) {
58
            if ($this->isFetchable($relation)) {
59
                $getter = "get" . ucfirst(Inflect::camelize($relation));
60
                $setter = "set" . ucfirst(Inflect::camelize($relation));
61
                if (method_exists($this, $getter) || method_exists($this, "__call")) {
62
                    $collection = $this->$getter($model);
63
                    if ($collection) {
64
                        $model->$setter($collection);
65
                    }
66
                }
67
            }
68
        }
69
70
        return $model;
71
    }
72
73
    protected function updateRelations(AbstractModel $model): AbstractModel
74
    {
75
        foreach ($this->getRelationClassMap() as $relation_name => $relation_class) {
76
            $model_getter = $this->getGetter($relation_name);
77
            $repository_adder = $this->getAdder(Inflect::singularize($relation_name));
78
79
            if (is_initialized($model, $relation_name)) {
80
                $related_collection = $model->$model_getter();
81
                if ($related_collection instanceof Collection) {
82
                    foreach ($related_collection as $related_model) {
83
                        if (!$related_model->isPersisted()) {
84
                            $this->$repository_adder($model, $related_model);
85
                        }
86
                    }
87
                }
88
            }
89
        }
90
91
        return $model;
92
    }
93
94
    protected function isFetchable(string $relation_name): bool
95
    {
96
        $related_class = self::getRelationClassFor($relation_name);
97
        if ($related_class !== null && class_exists($related_class)) {
98
            $interfaces = class_implements($related_class);
99
            return is_array($interfaces) && in_array(FetchableInterface::class, $interfaces, true);
100
        }
101
102
        return false;
103
    }
104
105
    /**
106
     * @param string $method
107
     * @param array $args
108
     * @return mixed
109
     * @throws AbstractApiException
110
     * @throws JsonException
111
     * @ignore
112
     */
113
    public function __call(string $method, array $args): mixed
114
    {
115
        if ($this->isRelationGetter($method)) {
116
            return $this->getRelation($method, $args);
117
        }
118
119
        if ($this->isRelationAdder($method)) {
120
            return $this->addRelation($method, $args);
121
        }
122
123
        return null;
124
    }
125
126
    protected function getRelation(string $method, array $args): ?Collection
127
    {
128
        $relation_name = strtolower(Inflect::snakeize(substr($method, 3)));
129
        $relation_class = $this->getRelationClassMap()[$relation_name];
130
131
        if ($args[0] instanceof AbstractModel) {
132
            $endpoint = $this->getEndpoint() . "/" . $args[0]->getId() . "/" . $relation_name . "." . $this->getClient()->getFormat();
133
            $response = $this->getClient()->get($endpoint);
134
135
            if ($response->isSuccess()) {
136
                $collection = $this->initCollectionFromResponse($response);
137
138
                foreach ($response->getData()[$relation_name] as $relation_data) {
139
                    $relation = (new $relation_class())->fromArray($relation_data);
140
                    $collection->add($relation);
141
                }
142
143
                return $collection;
144
            }
145
        }
146
147
        return null;
148
    }
149
150
    /**
151
     * @throws JsonException
152
     */
153
    protected function addRelation(string $method, array $args): ?AbstractModel
154
    {
155
        $relation = Inflect::pluralize(strtolower(Inflect::snakeize(substr($method, 3))));
156
        if ($args[0] instanceof AbstractModel && $args[1] instanceof AbstractModel) {
157
            $endpoint = $this->getEndpoint() . "/" . $args[0]->getId() . "/" . $relation . "." . $this->getClient()->getFormat();
158
            $response = $this->getClient()->post($endpoint, json_encode($args[1]->getPayload(), JSON_THROW_ON_ERROR));
159
160
            if ($response->isSuccess()) {
161
                $adder = Inflect::ADDER_PREFIX . Inflect::singularize(Inflect::camelize($relation));
162
                $args[0]->$adder($args[1]);
163
            }
164
            return $args[0];
165
        }
166
167
        return null;
168
    }
169
170
171
    protected function isRelationGetter(string $method): bool
172
    {
173
        $relation = strtolower(Inflect::snakeize(substr($method, 3)));
174
        return str_starts_with($method, "get") && array_key_exists($relation, $this->getRelationClassMap());
175
    }
176
177
    protected function isRelationAdder(string $method): bool
178
    {
179
        $relation = strtolower(Inflect::pluralize(Inflect::snakeize(substr($method, 3))));
180
        return str_starts_with($method, "add") && array_key_exists($relation, $this->getRelationClassMap());
181
    }
182
183
    protected function getAdder(string $property): string
184
    {
185
        return "add" . Inflect::camelize($property);
186
    }
187
188
    protected function getGetter(string $property): string
189
    {
190
        return "get" . Inflect::camelize($property);
191
    }
192
193
    protected function initCollectionFromResponse(ApiResponse $response): Collection
194
    {
195
        if ($response->isPaginated()) {
196
            $collection = new PaginatedCollection();
197
            $collection->setLimit($response->getLimit());
198
            $collection->setOffset($response->getOffset());
199
            $collection->setTotalCount($response->getTotalCount());
200
        } else {
201
            $collection = new IdentityCollection();
202
        }
203
204
        return $collection;
205
    }
206
207
    protected function getCollection(array $items): ArrayCollection
208
    {
209
        $elements = [];
210
211
        foreach ($items as $item) {
212
            $object_class = $this->getModelClass();
0 ignored issues
show
Bug introduced by
The method getModelClass() does not exist on Blackmine\Repository\RepositoryTrait. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

212
            /** @scrutinizer ignore-call */ 
213
            $object_class = $this->getModelClass();
Loading history...
213
            $object = new $object_class();
214
            $object->fromArray($item);
215
216
            $this->hydrateRelations($object);
217
218
            $elements[] = $object;
219
        }
220
221
        if (!empty($elements) && $elements[0] instanceof Identity) {
222
            return new IdentityCollection($elements);
223
        }
224
225
        return new ArrayCollection($elements);
226
    }
227
228
229
    /**
230
     * @return ClientInterface
231
     * @ignore
232
     */
233
    abstract public function getClient(): ClientInterface;
234
235
    /**
236
     * @param string $relation
237
     * @return string|null
238
     * @ignore
239
     */
240
    abstract public static function getRelationClassFor(string $relation): ?string;
241
242
    /**
243
     * @return array
244
     * @ignore
245
     */
246
    abstract public function getFetchRelations(): array;
247
248
    /**
249
     * @return array
250
     * @ignore
251
     */
252
    abstract public function getRelationClassMap(): array;
253
254
    /**
255
     * @return string
256
     * @ignore
257
     */
258
    abstract public function getEndpoint(): string;
259
}
260