Completed
Pull Request — master (#58)
by
unknown
01:55
created

EntityRepository::normalizeCacheKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Mapado\RestClientSdk;
4
5
use Mapado\RestClientSdk\Exception\SdkException;
6
use Mapado\RestClientSdk\Helper\ArrayHelper;
7
use Symfony\Component\Cache\CacheItem;
8
use Mapado\RestClientSdk\UnitOfWork;
9
10
class EntityRepository
11
{
12
    /**
13
     * REST Client.
14
     *
15
     * @var RestClient
16
     */
17
    protected $restClient;
18
19
    /**
20
     * SDK Client.
21
     *
22
     * @var SdkClient
23
     */
24
    protected $sdk;
25
26
    /**
27
     * @var string
28
     */
29
    protected $entityName;
30
31
    /**
32
     * classMetadataCache
33
     *
34
     * @var ClassMetadata
35
     * @access private
36
     */
37
    private $classMetadataCache;
38
39
    /**
40
     * unitOfWork
41
     *
42
     * @var UnitOfWork
43
     * @access private
44
     */
45
    private $unitOfWork;
46
47
    /**
48
     * EntityRepository constructor
49
     *
50
     * @param SdkClient  $sdkClient  The client to connect to the datasource with
51
     * @param RestClient $restClient The client to process the http requests
52
     * @param string     $entityName The entity to work with
53
     */
54
    public function __construct(SdkClient $sdkClient, RestClient $restClient, UnitOfWork $unitOfWork, $entityName)
55
    {
56
        $this->sdk        = $sdkClient;
57
        $this->restClient = $restClient;
58
        $this->unitOfWork = $unitOfWork;
59
        $this->entityName = $entityName;
60
    }
61
62
    /**
63
     * find - finds one item of the entity based on the @REST\Id field in the entity
64
     *
65
     * @param string $id          id of the element to fetch
66
     * @param array  $queryParams query parameters to add to the query
67
     * @access public
68
     * @return object
69
     */
70
    public function find($id, $queryParams = [])
71
    {
72
        $hydrator = $this->sdk->getModelHydrator();
73
        $id = $hydrator->convertId($id, $this->entityName);
74
75
        $id = $this->addQueryParameter($id, $queryParams);
76
77
        // if entity is found in cache, return it
78
        $entityFromCache = $this->fetchFromCache($id);
79
        if ($entityFromCache != false) {
80
            return $entityFromCache;
81
        }
82
83
        $data = $this->restClient->get($id);
84
        $entity = $hydrator->hydrate($data, $this->entityName);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->restClient->get($id) on line 83 can also be of type null or object<Psr\Http\Message\ResponseInterface>; however, Mapado\RestClientSdk\Mod...odelHydrator::hydrate() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
85
86
        // cache entity
87
        $this->saveToCache($id, $entity);
88
        $this->unitOfWork->registerClean($id, $entity);
0 ignored issues
show
Bug introduced by
It seems like $entity defined by $hydrator->hydrate($data, $this->entityName) on line 84 can also be of type null; however, Mapado\RestClientSdk\UnitOfWork::registerClean() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
89
90
        return $entity;
91
    }
92
93
    /**
94
     * findAll
95
     *
96
     * @access public
97
     * @return array
98
     */
99
    public function findAll()
100
    {
101
        $mapping = $this->sdk->getMapping();
102
        $key = $this->getClassMetadata()->getKey();
103
        $prefix = $mapping->getIdPrefix();
104
        $path = empty($prefix) ? '/' . $key : $prefix . '/' . $key;
105
106
        $entityListFromCache = $this->fetchFromCache($path);
107
108
        // if entityList is found in cache, return it
109
        if ($entityListFromCache !== false) {
110
            return $entityListFromCache;
111
        }
112
113
        $data = $this->restClient->get($path);
114
115
        $hydrator = $this->sdk->getModelHydrator();
116
        $entityList = $hydrator->hydrateList($data, $this->entityName);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->restClient->get($path) on line 113 can also be of type null or object<Psr\Http\Message\ResponseInterface>; however, Mapado\RestClientSdk\Mod...Hydrator::hydrateList() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
117
118
        // cache entity list
119
        $this->saveToCache($path, $entityList);
120
121
        // then cache each entity from list
122 View Code Duplication
        foreach ($entityList as $entity) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
123
            $identifier = $entity->{$this->getClassMetadata()->getIdGetter()}();
124
            $this->unitOfWork->registerClean($identifier, $entity);
125
            $this->saveToCache($identifier, $entity);
126
        }
127
128
        return $entityList;
129
    }
130
131
    /**
132
     * remove
133
     *
134
     * @param object $model
135
     * @access public
136
     * @return void
137
     *
138
     * @TODO STILL NEEDS TO BE CONVERTED TO ENTITY MODEL
139
     */
140
    public function remove($model)
141
    {
142
        $identifier = $model->{$this->getClassMetadata()->getIdGetter()}();
143
        $this->removeFromCache($identifier);
144
        $this->unitOfWork->clear($identifier);
145
146
        return $this->restClient->delete($identifier);
147
    }
148
149
    /**
150
     * update
151
     *
152
     * @param object $model
153
     * @access public
154
     * @return void
155
     */
156
    public function update($model, $serializationContext = [], $queryParams = [])
157
    {
158
        $identifier = $model->{$this->getClassMetadata()->getIdGetter()}();
159
        $serializer = $this->sdk->getSerializer();
160
        $newSerializedModel = $serializer->serialize($model, $this->entityName, $serializationContext);
161
        $oldSerializedModel = $serializer->serialize($this->unitOfWork->getDirtyEntity($identifier), $this->entityName, $serializationContext);
162
163
        $data = $this->restClient->put(
164
            $this->addQueryParameter($identifier, $queryParams),
165
            $this->unitOfWork->getDirtyData($newSerializedModel, $oldSerializedModel, $this->getClassMetadata())
166
        );
167
168
        $this->removeFromCache($identifier);
169
        $this->unitOfWork->registerClean($identifier, $newSerializedModel);
0 ignored issues
show
Documentation introduced by
$newSerializedModel is of type array, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
170
171
        $hydrator = $this->sdk->getModelHydrator();
172
        return $hydrator->hydrate($data, $this->entityName);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->restClient->put($...s->getClassMetadata())) on line 163 can also be of type object<Psr\Http\Message\ResponseInterface>; however, Mapado\RestClientSdk\Mod...odelHydrator::hydrate() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
173
    }
174
175
    /**
176
     * persist
177
     *
178
     * @param object $model
179
     * @access public
180
     * @return void
181
     */
182
    public function persist($model, $serializationContext = [], $queryParams = [])
183
    {
184
        $mapping = $this->sdk->getMapping();
185
        $prefix = $mapping->getIdPrefix();
186
        $key = $mapping->getKeyFromModel($this->entityName);
187
188
        $path = empty($prefix) ? '/' . $key : $prefix . '/' . $key;
189
        $data = $this->restClient->post(
190
            $this->addQueryParameter($path, $queryParams),
191
            $this->sdk->getSerializer()->serialize($model, $this->entityName, $serializationContext)
192
        );
193
194
        $hydrator = $this->sdk->getModelHydrator();
195
        return $hydrator->hydrate($data, $this->entityName);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->restClient->post(...$serializationContext)) on line 189 can also be of type object<Psr\Http\Message\ResponseInterface>; however, Mapado\RestClientSdk\Mod...odelHydrator::hydrate() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
196
    }
197
198
    /**
199
     * Adds support for magic finders.
200
     *
201
     * @param string $method
202
     * @param mixed  $arguments
203
     *
204
     * @return array|object The found entity/entities.
205
     */
206
    public function __call($method, $arguments)
207
    {
208
        switch (true) {
209 View Code Duplication
            case (0 === strpos($method, 'findBy')):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
210
                $fieldName = strtolower(substr($method, 6));
211
                $methodName = 'findBy';
212
                break;
213
214 View Code Duplication
            case (0 === strpos($method, 'findOneBy')):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
215
                $fieldName = strtolower(substr($method, 9));
216
                $methodName = 'findOneBy';
217
                break;
218
219
            default:
220
                throw new \BadMethodCallException(
221
                    'Undefined method \'' . $method . '\'. The method name must start with
222
                    either findBy or findOneBy!'
223
                );
224
        }
225
226
        if (empty($arguments)) {
227
            throw new SdkException('You need to pass a parameter to ' . $method);
228
        }
229
230
        $mapping = $this->sdk->getMapping();
231
        $key = $mapping->getKeyFromModel($this->entityName);
232
        $prefix = $mapping->getIdPrefix();
233
        $path = empty($prefix) ? '/' . $key : $prefix . '/' . $key;
234
235
        if (!empty($fieldName)) {
236
            $queryParams = [$fieldName => current($arguments)];
237
        } else {
238
            $queryParams = current($arguments);
239
        }
240
        $path .= '?' . http_build_query($this->convertQueryParameters($queryParams));
241
242
        // if entityList is found in cache, return it
243
        $entityListFromCache = $this->fetchFromCache($path);
244
        if ($entityListFromCache !== false) {
245
            return $entityListFromCache;
246
        }
247
248
        $data = $this->restClient->get($path);
249
250
        $hydrator = $this->sdk->getModelHydrator();
251
252
        if ($methodName == 'findOneBy') {
253
            // If more results are found but one is requested return the first hit.
254
            $collectionKey = $mapping->getConfig()['collectionKey'];
255
            $entityList = ArrayHelper::arrayGet($data, $collectionKey);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->restClient->get($path) on line 248 can also be of type null or object<Psr\Http\Message\ResponseInterface>; however, Mapado\RestClientSdk\Hel...ArrayHelper::arrayGet() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
256
            if (!empty($entityList)) {
257
                $data = current($entityList);
258
                $hydratedData = $hydrator->hydrate($data, $this->entityName);
259
260
                $identifier = $hydratedData->{$this->getClassMetadata()->getIdGetter()}();
261
                $this->unitOfWork->registerClean($identifier, $hydratedData);
0 ignored issues
show
Bug introduced by
It seems like $hydratedData defined by $hydrator->hydrate($data, $this->entityName) on line 258 can also be of type null; however, Mapado\RestClientSdk\UnitOfWork::registerClean() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
262
                $this->saveToCache($identifier, $hydratedData);
263
            } else {
264
                $hydratedData = null;
265
            }
266
        } else {
267
            $hydratedData = $hydrator->hydrateList($data, $this->entityName);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->restClient->get($path) on line 248 can also be of type null or object<Psr\Http\Message\ResponseInterface>; however, Mapado\RestClientSdk\Mod...Hydrator::hydrateList() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
268
269
            // then cache each entity from list
270 View Code Duplication
            foreach ($hydratedData as $entity) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
271
                $identifier = $entity->{$this->getClassMetadata()->getIdGetter()}();
272
                $this->saveToCache($identifier, $entity);
273
                $this->unitOfWork->registerClean($identifier, $entity);
274
            }
275
        }
276
277
        $this->saveToCache($path, $hydratedData);
278
279
        return $hydratedData;
280
    }
281
282
    /**
283
     * fetchFromCache
284
     *
285
     * @access protected
286
     * @param string $key
287
     * @return object|false
288
     */
289
    protected function fetchFromCache($key)
290
    {
291
        $key = $this->normalizeCacheKey($key);
292
        $cacheItemPool = $this->sdk->getCacheItemPool();
293 View Code Duplication
        if (isset($cacheItemPool)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
294
            $cacheKey = $this->sdk->getCachePrefix() . $key;
295
            if ($cacheItemPool->hasItem($cacheKey)) {
296
                $cacheItem = $cacheItemPool->getItem($cacheKey);
297
                $cacheData = $cacheItem->get();
298
                return $cacheData;
299
            }
300
        }
301
302
        return false;
303
    }
304
305
    /**
306
     * saveToCache
307
     *
308
     * @access protected
309
     * @return object
310
     */
311
    protected function saveToCache($key, $value)
312
    {
313
        $key = $this->normalizeCacheKey($key);
314
        $cacheItemPool = $this->sdk->getCacheItemPool();
315 View Code Duplication
        if (isset($cacheItemPool)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
316
            $cacheKey = $this->sdk->getCachePrefix() . $key;
317
318
            if (!$cacheItemPool->hasItem($cacheKey)) {
319
                $cacheItem = $cacheItemPool->getItem($cacheKey);
320
                $cacheItem->set($value);
321
                $cacheItemPool->save($cacheItem);
322
            }
323
        }
324
    }
325
326
    /**
327
     * removeFromCache
328
     *
329
     * @param string $key
330
     * @access private
331
     * @return boolean true if no cache or cache successfully cleared, false otherwise
332
     */
333
    protected function removeFromCache($key)
334
    {
335
        $key = $this->normalizeCacheKey($key);
336
        $cacheItemPool = $this->sdk->getCacheItemPool();
337
        if (isset($cacheItemPool)) {
338
            $cacheKey = $this->sdk->getCachePrefix() . $key;
339
340
            if ($cacheItemPool->hasItem($cacheKey)) {
341
                return $cacheItemPool->deleteItem($cacheKey);
342
            }
343
        }
344
345
        return true;
346
    }
347
348
    /**
349
     * addQueryParameter
350
     *
351
     * @param string $path path to call
352
     * @param array $params query parameters to add
353
     * @access private
354
     * @return string
355
     */
356
    protected function addQueryParameter($path, $params = [])
357
    {
358
        if (empty($params)) {
359
            return $path;
360
        }
361
362
        return $path . '?' . http_build_query($params);
363
    }
364
365
    /**
366
     * convertQueryParameters
367
     *
368
     * @param array $queryParameters
369
     * @access private
370
     * @return array
371
     */
372
    private function convertQueryParameters($queryParameters)
373
    {
374
        $mapping = $this->sdk->getMapping();
375
376
        return array_map(
377
            function ($item) use ($mapping) {
378
                if (is_object($item)) {
379
                    $classname = get_class($item);
380
381
                    if ($mapping->hasClassMetadata($classname)) {
382
                        $idAttr = $mapping->getClassMetadata($classname)
383
                            ->getIdentifierAttribute();
384
385
                        if ($idAttr) {
386
                            $idGetter = 'get' . ucfirst($idAttr->getAttributeName());
387
388
                            return $item->{$idGetter}();
389
                        }
390
                    }
391
392
                    if (method_exists($item, 'getId')) {
393
                        return $item->getId();
394
                    }
395
                }
396
397
                return $item;
398
            },
399
            $queryParameters
400
        );
401
    }
402
403
    /**
404
     * normalizeCacheKey
405
     *
406
     * @access private
407
     * @return string
408
     */
409
    private function normalizeCacheKey($key)
410
    {
411
        return preg_replace('~[\\/\{\}@:\(\)]~', '_', $key);
412
    }
413
414
    private function getClassMetadata()
415
    {
416
        if (!isset($this->classMetadata)) {
417
            $this->classMetadataCache = $this->sdk
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->sdk->getMapping()...data($this->entityName) of type object<Mapado\RestClient...\Mapping\ClassMetadata> is incompatible with the declared type object<Mapado\RestClientSdk\ClassMetadata> of property $classMetadataCache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
418
                ->getMapping()
419
                ->getClassMetadata($this->entityName);
420
        }
421
422
        return $this->classMetadataCache;
423
    }
424
}
425