EntityRepository::__call()   C
last analyzed

Complexity

Conditions 11
Paths 43

Size

Total Lines 80
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 11.0105

Importance

Changes 6
Bugs 1 Features 1
Metric Value
cc 11
eloc 52
c 6
b 1
f 1
nc 43
nop 2
dl 0
loc 80
ccs 43
cts 45
cp 0.9556
crap 11.0105
rs 6.9006

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Mapado\RestClientSdk;
6
7
use Mapado\RestClientSdk\Collection\Collection;
8
use Mapado\RestClientSdk\Exception\HydratorException;
9
use Mapado\RestClientSdk\Exception\RestException;
10
use Mapado\RestClientSdk\Exception\SdkException;
11
use Mapado\RestClientSdk\Exception\UnexpectedTypeException;
12
use Mapado\RestClientSdk\Helper\ArrayHelper;
13
use Mapado\RestClientSdk\Mapping\ClassMetadata;
14
use Psr\Http\Message\ResponseInterface;
15
16
class EntityRepository
17
{
18
    /**
19
     * REST Client.
20
     *
21
     * @var RestClient
22
     */
23
    protected $restClient;
24
25
    /**
26
     * SDK Client.
27
     *
28
     * @var SdkClient
29
     */
30
    protected $sdk;
31
32
    /**
33
     * @var string
34
     */
35
    protected $entityName;
36
37
    /**
38
     * classMetadataCache
39
     *
40
     * @var ClassMetadata
41
     */
42
    private $classMetadataCache;
43
44
    /**
45
     * unitOfWork
46
     *
47
     * @var UnitOfWork
48
     */
49
    private $unitOfWork;
50
51
    /**
52
     * EntityRepository constructor
53
     *
54
     * @param SdkClient  $sdkClient  The client to connect to the datasource with
55
     * @param RestClient $restClient The client to process the http requests
56
     * @param string     $entityName The entity to work with
57
     */
58
    public function __construct(
59
        SdkClient $sdkClient,
60
        RestClient $restClient,
61
        UnitOfWork $unitOfWork,
62
        string $entityName
63
    ) {
64 1
        $this->sdk = $sdkClient;
65 1
        $this->restClient = $restClient;
66 1
        $this->unitOfWork = $unitOfWork;
67 1
        $this->entityName = $entityName;
68 1
    }
69
70
    /**
71
     * Adds support for magic finders.
72
     *
73
     * @return array|object|null the found entity/entities
74
     */
75
    public function __call(string $method, array $arguments)
76
    {
77
        switch (true) {
78 1
            case 0 === mb_strpos($method, 'findBy'):
79 1
                $fieldName = mb_strtolower(mb_substr($method, 6));
80 1
                $methodName = 'findBy';
81 1
                break;
82
83 1
            case 0 === mb_strpos($method, 'findOneBy'):
84 1
                $fieldName = mb_strtolower(mb_substr($method, 9));
85 1
                $methodName = 'findOneBy';
86 1
                break;
87
88
            default:
89
                throw new \BadMethodCallException('Undefined method \'' . $method . '\'. The method name must start with
90
                    either findBy or findOneBy!');
91
        }
92
93 1
        if (empty($arguments)) {
94
            throw new SdkException('You need to pass a parameter to ' . $method);
95
        }
96
97 1
        $mapping = $this->sdk->getMapping();
98 1
        $key = $mapping->getKeyFromModel($this->entityName);
99 1
        $prefix = $mapping->getIdPrefix();
100 1
        $path = empty($prefix) ? '/' . $key : $prefix . '/' . $key;
101
102 1
        if (!empty($fieldName)) {
103 1
            $queryParams = [$fieldName => current($arguments)];
104
        } else {
105 1
            $queryParams = current($arguments);
106
        }
107
        $path .=
108 1
            '?' . http_build_query($this->convertQueryParameters($queryParams));
109
110
        // if entityList is found in cache, return it
111 1
        $entityListFromCache = $this->fetchFromCache($path);
112 1
        if (false !== $entityListFromCache) {
113 1
            return $entityListFromCache;
114
        }
115
116 1
        $data = $this->restClient->get($path);
117 1
        $hydrator = $this->sdk->getModelHydrator();
118
119 1
        if ('findOneBy' == $methodName) {
120
            // If more results are found but one is requested return the first hit.
121 1
            $collectionKey = $mapping->getConfig()['collectionKey'];
122
123 1
            $data = $this->assertArray($data, $methodName);
124 1
            $entityList = ArrayHelper::arrayGet($data, $collectionKey);
125 1
            if (!empty($entityList)) {
126 1
                $data = current($entityList);
127 1
                $hydratedData = $hydrator->hydrate($data, $this->entityName);
128
129 1
                $identifier = $hydratedData->{$this->getClassMetadata()->getIdGetter()}();
130 1
                if (null !== $hydratedData) {
131 1
                    $this->unitOfWork->registerClean(
132 1
                        $identifier,
133
                        $hydratedData
134
                    );
135
                }
136 1
                $this->saveToCache($identifier, $hydratedData);
137
            } else {
138 1
                $hydratedData = null;
139
            }
140
        } else {
141 1
            $data = $this->assertNotObject($data, $methodName);
142 1
            $hydratedData = $hydrator->hydrateList($data, $this->entityName);
143
144
            // then cache each entity from list
145 1
            foreach ($hydratedData as $entity) {
146 1
                $identifier = $entity->{$this->getClassMetadata()->getIdGetter()}();
147 1
                $this->saveToCache($identifier, $entity);
148 1
                $this->unitOfWork->registerClean($identifier, $entity);
149
            }
150
        }
151
152 1
        $this->saveToCache($path, $hydratedData);
153
154 1
        return $hydratedData;
155
    }
156
157
    /**
158
     * find - finds one item of the entity based on the @REST\Id field in the entity
159
     *
160
     * @param string|int|mixed $id          id of the element to fetch
161
     * @param array  $queryParams query parameters to add to the query
162
     */
163
    public function find($id, array $queryParams = []): ?object
164
    {
165 1
        $hydrator = $this->sdk->getModelHydrator();
166 1
        $id = $hydrator->convertId($id, $this->entityName);
167
168 1
        $id = $this->addQueryParameter($id, $queryParams);
169
170
        // if entity is found in cache, return it
171 1
        $entityFromCache = $this->fetchFromCache($id);
172 1
        if (false != $entityFromCache) {
173 1
            return $entityFromCache;
174
        }
175
176 1
        $data = $this->restClient->get($id);
177 1
        $data = $this->assertNotObject($data, __METHOD__);
178
179 1
        $entity = $hydrator->hydrate($data, $this->entityName);
180
181
        // cache entity
182 1
        $this->saveToCache($id, $entity);
183 1
        if (null !== $entity) {
184 1
            $this->unitOfWork->registerClean($id, $entity); // another register clean will be made in the Serializer if the id different from the called uri
185
        }
186
187 1
        return $entity;
188
    }
189
190
    public function findAll(): Collection
191
    {
192 1
        $mapping = $this->sdk->getMapping();
193 1
        $key = $this->getClassMetadata()->getKey();
194 1
        $prefix = $mapping->getIdPrefix();
195 1
        $path = empty($prefix) ? '/' . $key : $prefix . '/' . $key;
196
197 1
        $entityListFromCache = $this->fetchFromCache($path);
198
199
        // if entityList is found in cache, return it
200 1
        if (false !== $entityListFromCache) {
201 1
            if (!$entityListFromCache instanceof Collection) {
202
                throw new \RuntimeException('Entity list in cache should be an instance of ' . Collection::class . '. This should not happen.');
203
            }
204
205 1
            return $entityListFromCache;
206
        }
207
208 1
        $data = $this->restClient->get($path);
209 1
        $data = $this->assertNotObject($data, __METHOD__);
210
211 1
        $hydrator = $this->sdk->getModelHydrator();
212 1
        $entityList = $hydrator->hydrateList($data, $this->entityName);
213
214
        // cache entity list
215 1
        $this->saveToCache($path, $entityList);
216
217
        // then cache each entity from list
218 1
        foreach ($entityList as $entity) {
219 1
            $identifier = $entity->{$this->getClassMetadata()->getIdGetter()}();
220 1
            $this->unitOfWork->registerClean($identifier, $entity);
221 1
            $this->saveToCache($identifier, $entity);
222
        }
223
224 1
        return $entityList;
225
    }
226
227
    /**
228
     * remove entity
229
     *
230
     * @TODO STILL NEEDS TO BE CONVERTED TO ENTITY MODEL
231
     */
232
    public function remove(object $model): void
233
    {
234 1
        $identifier = $model->{$this->getClassMetadata()->getIdGetter()}();
235 1
        $this->removeFromCache($identifier);
236 1
        $this->unitOfWork->clear($identifier);
237
238 1
        $this->restClient->delete($identifier);
239 1
    }
240
241
    public function update(
242
        object $model,
243
        array $serializationContext = [],
244
        array $queryParams = []
245
    ): object {
246 1
        $identifier = $model->{$this->getClassMetadata()->getIdGetter()}();
247 1
        $serializer = $this->sdk->getSerializer();
248 1
        $newSerializedModel = $serializer->serialize(
249 1
            $model,
250 1
            $this->entityName,
251
            $serializationContext
252
        );
253
254 1
        $oldModel = $this->unitOfWork->getDirtyEntity($identifier);
255 1
        if ($oldModel) {
256 1
            $oldSerializedModel = $serializer->serialize(
257 1
                $oldModel,
258 1
                $this->entityName,
259
                $serializationContext
260
            );
261 1
            $newSerializedModel = $this->unitOfWork->getDirtyData(
262 1
                $newSerializedModel,
263
                $oldSerializedModel,
264 1
                $this->getClassMetadata()
265
            );
266
        }
267
268 1
        $path = $this->addQueryParameter($identifier, $queryParams);
269
270 1
        $data = $this->restClient->put($path, $newSerializedModel);
271 1
        $data = $this->assertArray($data, __METHOD__);
272
273 1
        $this->removeFromCache($identifier);
274
        // $this->unitOfWork->registerClean($identifier, $data);
275 1
        $hydrator = $this->sdk->getModelHydrator();
276 1
        $out = $hydrator->hydrate($data, $this->entityName);
277
278 1
        if (null === $out) {
279
            throw new HydratorException("Unable to convert data from PUT request ({$path}) to an instance of {$this->entityName}. Maybe you have a custom hydrator returning null?");
280
        }
281
282 1
        return $out;
283
    }
284
285
    public function persist(
286
        object $model,
287
        array $serializationContext = [],
288
        array $queryParams = []
289
    ): object {
290 1
        $mapping = $this->sdk->getMapping();
291 1
        $prefix = $mapping->getIdPrefix();
292 1
        $key = $mapping->getKeyFromModel($this->entityName);
293
294 1
        $path = empty($prefix) ? '/' . $key : $prefix . '/' . $key;
295
296 1
        $oldSerializedModel = $this->getClassMetadata()->getDefaultSerializedModel();
297 1
        $newSerializedModel = $this->sdk
298 1
            ->getSerializer()
299 1
            ->serialize($model, $this->entityName, $serializationContext);
300
301 1
        $diff = $this->unitOfWork->getDirtyData(
302 1
            $newSerializedModel,
303
            $oldSerializedModel,
304 1
            $this->getClassMetadata()
305
        );
306
307 1
        $data = $this->restClient->post(
308 1
            $this->addQueryParameter($path, $queryParams),
309
            $diff
310
        );
311 1
        $data = $this->assertNotObject($data, __METHOD__);
312
313 1
        if (null === $data) {
0 ignored issues
show
introduced by
The condition null === $data is always false.
Loading history...
314
            throw new RestException("No data found after sending a `POST` request to {$path}. Did the server returned a 4xx or 5xx status code?", $path);
315
        }
316
317 1
        $hydrator = $this->sdk->getModelHydrator();
318
319 1
        $out = $hydrator->hydrate($data, $this->entityName);
320
321 1
        if (null === $out) {
322
            throw new HydratorException("Unable to convert data from POST request ({$path}) to an instance of {$this->entityName}. Maybe you have a custom hydrator returning null?");
323
        }
324
325 1
        return $out;
326
    }
327
328
    /**
329
     * @return object|false
330
     */
331
    protected function fetchFromCache(string $key)
332
    {
333 1
        $key = $this->normalizeCacheKey($key);
334 1
        $cacheItemPool = $this->sdk->getCacheItemPool();
335 1
        if ($cacheItemPool) {
336 1
            $cacheKey = $this->sdk->getCachePrefix() . $key;
337 1
            if ($cacheItemPool->hasItem($cacheKey)) {
338 1
                $cacheItem = $cacheItemPool->getItem($cacheKey);
339 1
                $cacheData = $cacheItem->get();
340
341 1
                return $cacheData;
342
            }
343
        }
344
345 1
        return false;
346
    }
347
348
    protected function saveToCache(string $key, ?object $value): void
349
    {
350 1
        $key = $this->normalizeCacheKey($key);
351 1
        $cacheItemPool = $this->sdk->getCacheItemPool();
352 1
        if ($cacheItemPool) {
353 1
            $cacheKey = $this->sdk->getCachePrefix() . $key;
354
355 1
            if (!$cacheItemPool->hasItem($cacheKey)) {
356 1
                $cacheItem = $cacheItemPool->getItem($cacheKey);
357 1
                $cacheItem->set($value);
358 1
                $cacheItemPool->save($cacheItem);
359
            }
360
        }
361 1
    }
362
363
    /**
364
     * remove from cache
365
     *
366
     * @return bool true if no cache or cache successfully cleared, false otherwise
367
     */
368
    protected function removeFromCache(string $key): bool
369
    {
370 1
        $key = $this->normalizeCacheKey($key);
371 1
        $cacheItemPool = $this->sdk->getCacheItemPool();
372 1
        if ($cacheItemPool) {
373 1
            $cacheKey = $this->sdk->getCachePrefix() . $key;
374
375 1
            if ($cacheItemPool->hasItem($cacheKey)) {
376 1
                return $cacheItemPool->deleteItem($cacheKey);
377
            }
378
        }
379
380 1
        return true;
381
    }
382
383
    protected function addQueryParameter(
384
        string $path,
385
        array $params = []
386
    ): string {
387 1
        if (empty($params)) {
388 1
            return $path;
389
        }
390
391 1
        return $path . '?' . http_build_query($params);
392
    }
393
394
    private function convertQueryParameters(array $queryParameters): array
395
    {
396 1
        $mapping = $this->sdk->getMapping();
397
398 1
        return array_map(function ($item) use ($mapping) {
399 1
            if (is_object($item)) {
400 1
                $classname = get_class($item);
401
402 1
                if ($mapping->hasClassMetadata($classname)) {
403
                    $idGetter = $mapping
404 1
                        ->getClassMetadata($classname)
405 1
                        ->getIdGetter();
406
407 1
                    return $item->{$idGetter}();
408
                }
409
            }
410
411 1
            return $item;
412 1
        }, $queryParameters);
413
    }
414
415
    private function normalizeCacheKey(string $key): string
416
    {
417 1
        $out = preg_replace('~[\\/\{\}@:\(\)]~', '_', $key);
418
419 1
        if (null === $out) {
420
            throw new \RuntimeException('Unable to normalize cache key. This should not happen.');
421
        }
422
423 1
        return $out;
424
    }
425
426
    private function getClassMetadata(): ClassMetadata
427
    {
428 1
        if (!isset($this->classMetadata)) {
0 ignored issues
show
Bug introduced by
The property classMetadata does not exist on Mapado\RestClientSdk\EntityRepository. Did you mean classMetadataCache?
Loading history...
429 1
            $this->classMetadataCache = $this->sdk
430 1
                ->getMapping()
431 1
                ->getClassMetadata($this->entityName);
432
        }
433
434 1
        return $this->classMetadataCache;
435
    }
436
437
    /**
438
     * @param array|ResponseInterface|null $data
439
     */
440
    private function assertArray($data, string $methodName): array
441
    {
442 1
        if (is_array($data)) {
443 1
            return $data;
444
        }
445
446
        $type = null === $data ? 'null' : get_class($data);
447
448
        throw new UnexpectedTypeException("Return of method {$methodName} should be an array. {$type} given.");
449
    }
450
451
    /**
452
     * @param array|ResponseInterface|null $data
453
     *
454
     * @return array|null
455
     */
456
    private function assertNotObject($data, string $methodName)
457
    {
458 1
        if (!is_object($data)) {
459 1
            return $data;
460
        }
461
462
        $type = get_class($data);
463
464
        throw new UnexpectedTypeException("Return of method {$methodName} should be an array. {$type} given.");
465
    }
466
}
467