Completed
Pull Request — master (#47)
by Julien
03:47
created

EntityRepository::getIdentifier()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 2
eloc 10
nc 2
nop 1
1
<?php
2
3
namespace Mapado\RestClientSdk;
4
5
use Mapado\RestClientSdk\Exception\SdkException;
6
use Symfony\Component\Cache\CacheItem;
7
8
class EntityRepository
9
{
10
    /**
11
     * REST Client.
12
     *
13
     * @var RestClient
14
     */
15
    protected $restClient;
16
17
    /**
18
     * SDK Client.
19
     *
20
     * @var SdkClient
21
     */
22
    protected $sdk;
23
24
    /**
25
     * @var string
26
     */
27
    protected $entityName;
28
29
    /**
30
     * classMetadataCache
31
     *
32
     * @var ClassMetadata
33
     * @access private
34
     */
35
    private $classMetadataCache;
36
37
    /**
38
     * EntityRepository constructor
39
     *
40
     * @param SdkClient  $sdkClient  The client to connect to the datasource with
41
     * @param RestClient $restClient The client to process the http requests
42
     * @param string     $entityName The entity to work with
43
     */
44
    public function __construct(SdkClient $sdkClient, RestClient $restClient, $entityName)
45
    {
46
        $this->sdk        = $sdkClient;
47
        $this->restClient = $restClient;
48
        $this->entityName = $entityName;
49
    }
50
51
    /**
52
     * find - finds one item of the entity based on the @REST\Id field in the entity
53
     *
54
     * @param string $id          id of the element to fetch
55
     * @param array  $queryParams query parameters to add to the query
56
     * @access public
57
     * @return object
58
     */
59
    public function find($id, $queryParams = [])
60
    {
61
        $hydrator = $this->sdk->getModelHydrator();
62
        $id = $hydrator->convertId($id, $this->entityName);
63
64
        $id = $this->addQueryParameter($id, $queryParams);
65
66
        // if entity is found in cache, return it
67
        $entityFromCache = $this->fetchFromCache($id);
68
        if ($entityFromCache != false) {
69
            return $entityFromCache;
70
        }
71
72
        $data = $this->restClient->get($id);
73
        $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 72 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...
74
75
        // cache entity
76
        $this->saveToCache($id, $entity);
77
78
        return $entity;
79
    }
80
81
    /**
82
     * findAll
83
     *
84
     * @access public
85
     * @return array
86
     */
87
    public function findAll()
88
    {
89
        $mapping = $this->sdk->getMapping();
90
        $classMetadata = $this->getClassMetadata();
91
        $key = $classMetadata->getKey();
92
        $prefix = $mapping->getIdPrefix();
93
        $path = empty($prefix) ? '/' . $key : $prefix . '/' . $key;
94
95
        $entityListFromCache = $this->fetchFromCache($path);
96
97
        // if entityList is found in cache, return it
98
        if ($entityListFromCache !== false) {
99
            return $entityListFromCache;
100
        }
101
102
        $data = $this->restClient->get($path);
103
104
        $hydrator = $this->sdk->getModelHydrator();
105
        $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 102 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...
106
107
        // cache entity list
108
        $this->saveToCache($path, $entityList);
109
110
        // then cache each entity from list
111
        foreach ($entityList as $entity) {
112
            $this->saveToCache($this->getIdentifier($entity), $entity);
113
        }
114
115
        return $entityList;
116
    }
117
118
    /**
119
     * remove
120
     *
121
     * @param object $model
122
     * @access public
123
     * @return void
124
     *
125
     * @TODO STILL NEEDS TO BE CONVERTED TO ENTITY MODEL
126
     */
127
    public function remove($model)
128
    {
129
        $identifier = $this->getIdentifier($model);
130
        $this->removeFromCache($identifier);
131
132
        return $this->restClient->delete($identifier);
133
    }
134
135
    /**
136
     * update
137
     *
138
     * @param object $model
139
     * @access public
140
     * @return void
141
     */
142
    public function update($model, $serializationContext = [])
143
    {
144
        $data = $this->restClient->put(
145
            $this->getIdentifier($model),
146
            $this->sdk->getSerializer()->serialize($model, $this->entityName, $serializationContext)
147
        );
148
149
        $this->removeFromCache($this->getIdentifier($model));
150
151
        $hydrator = $this->sdk->getModelHydrator();
152
        return $hydrator->hydrate($data, $this->entityName);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->restClient->put($...$serializationContext)) on line 144 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...
153
    }
154
155
    /**
156
     * persist
157
     *
158
     * @param object $model
159
     * @access public
160
     * @return void
161
     */
162
    public function persist($model, $serializationContext = [])
163
    {
164
        $mapping = $this->sdk->getMapping();
165
        $prefix = $mapping->getIdPrefix();
166
        $key = $mapping->getKeyFromModel($this->entityName);
167
168
        $path = empty($prefix) ? '/' . $key : $prefix . '/' . $key;
169
        $data = $this->restClient->post($path, $this->sdk->getSerializer()->serialize($model, $this->entityName, $serializationContext));
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->post(...$serializationContext)) on line 169 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
     * Adds support for magic finders.
177
     *
178
     * @param string $method
179
     * @param mixed  $arguments
180
     *
181
     * @return array|object The found entity/entities.
182
     */
183
    public function __call($method, $arguments)
184
    {
185
        switch (true) {
186 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...
187
                $fieldName = strtolower(substr($method, 6));
188
                $methodName = 'findBy';
189
                break;
190
191 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...
192
                $fieldName = strtolower(substr($method, 9));
193
                $methodName = 'findOneBy';
194
                break;
195
196
            default:
197
                throw new \BadMethodCallException(
198
                    'Undefined method \'' . $method . '\'. The method name must start with
199
                    either findBy or findOneBy!'
200
                );
201
        }
202
203
        if (empty($arguments)) {
204
            throw new SdkException('You need to pass a parameter to ' . $method);
205
        }
206
207
        $mapping = $this->sdk->getMapping();
208
        $key = $mapping->getKeyFromModel($this->entityName);
209
        $prefix = $mapping->getIdPrefix();
210
        $path = empty($prefix) ? '/' . $key : $prefix . '/' . $key;
211
212
        if (!empty($fieldName)) {
213
            $queryParams = [$fieldName => current($arguments)];
214
        } else {
215
            $queryParams = current($arguments);
216
        }
217
        $path .= '?' . http_build_query($this->convertQueryParameters($queryParams));
218
219
        // if entityList is found in cache, return it
220
        $entityListFromCache = $this->fetchFromCache($path);
221
        if ($entityListFromCache !== false) {
222
            return $entityListFromCache;
223
        }
224
225
        $data = $this->restClient->get($path);
226
227
        $hydrator = $this->sdk->getModelHydrator();
228
229
        if ($methodName == 'findOneBy') {
230
            // If more results are found but one is requested return the first hit.
231
            if (!empty($data['hydra:member'])) {
232
                $data = current($data['hydra:member']);
233
                $hydratedData = $hydrator->hydrate($data, $this->entityName);
234
235
                $this->saveToCache($this->getIdentifier($hydratedData), $hydratedData);
236
            } else {
237
                $hydratedData = null;
238
            }
239
        } else {
240
            $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 225 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...
241
242
            // then cache each entity from list
243
            foreach ($hydratedData as $entity) {
244
                $this->saveToCache($this->getIdentifier($entity), $entity);
245
            }
246
        }
247
248
        $this->saveToCache($path, $hydratedData);
249
250
        return $hydratedData;
251
    }
252
253
    /**
254
     * convertQueryParameters
255
     *
256
     * @param array $queryParameters
257
     * @access private
258
     * @return array
259
     */
260
    private function convertQueryParameters($queryParameters)
261
    {
262
        $mapping = $this->sdk->getMapping();
263
264
        return array_map(
265
            function ($item) use ($mapping) {
266
                if (is_object($item)) {
267
                    $classname = get_class($item);
268
269
                    if ($mapping->hasClassMetadata($classname)) {
270
                        $idAttr = $mapping->getClassMetadata($classname)
271
                            ->getIdentifierAttribute();
272
273
                        if ($idAttr) {
274
                            $idGetter = 'get' . ucfirst($idAttr->getAttributeName());
275
276
                            return $item->{$idGetter}();
277
                        }
278
                    }
279
280
                    if (method_exists($item, 'getId')) {
281
                        return $item->getId();
282
                    }
283
                }
284
285
                return $item;
286
            },
287
            $queryParameters
288
        );
289
    }
290
291
    /**
292
     * fetchFromCache
293
     *
294
     * @access private
295
     * @param string $key
296
     * @return object|false
297
     */
298
    private function fetchFromCache($key)
299
    {
300
        $key = $this->normalizeCacheKey($key);
301
        $cacheItemPool = $this->sdk->getCacheItemPool();
302 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...
303
            $cacheKey = $this->sdk->getCachePrefix() . $key;
304
            if ($cacheItemPool->hasItem($cacheKey)) {
305
                $cacheItem = $cacheItemPool->getItem($cacheKey);
306
                $cacheData = $cacheItem->get();
307
                return $cacheData;
308
            }
309
        }
310
311
        return false;
312
    }
313
314
    /**
315
     * saveToCache
316
     *
317
     * @access private
318
     * @return object
319
     */
320
    private function saveToCache($key, $value)
321
    {
322
        $key = $this->normalizeCacheKey($key);
323
        $cacheItemPool = $this->sdk->getCacheItemPool();
324 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...
325
            $cacheKey = $this->sdk->getCachePrefix() . $key;
326
327
            if (!$cacheItemPool->hasItem($cacheKey)) {
328
                $cacheItem = $cacheItemPool->getItem($cacheKey);
329
                $cacheItem->set($value);
330
                $cacheItemPool->save($cacheItem);
331
            }
332
        }
333
    }
334
335
    /**
336
     * normalizeCacheKey
337
     *
338
     * @access private
339
     * @return string
340
     */
341
    private function normalizeCacheKey($key)
342
    {
343
        return preg_replace('~[\\/\{\}@:\(\)]~', '_', $key);
344
    }
345
346
    /**
347
     * removeFromCache
348
     *
349
     * @param string $key
350
     * @access private
351
     * @return boolean true if no cache or cache successfully cleared, false otherwise
352
     */
353
    private function removeFromCache($key)
354
    {
355
        $key = $this->normalizeCacheKey($key);
356
        $cacheItemPool = $this->sdk->getCacheItemPool();
357
        if (isset($cacheItemPool)) {
358
            $cacheKey = $this->sdk->getCachePrefix() . $key;
359
360
            if ($cacheItemPool->hasItem($cacheKey)) {
361
                return $cacheItemPool->deleteItem($cacheKey);
362
            }
363
        }
364
365
        return true;
366
    }
367
368
    /**
369
     * addQueryParameter
370
     *
371
     * @param string $path path to call
372
     * @param array $params query parameters to add
373
     * @access private
374
     * @return string
375
     */
376
    private function addQueryParameter($path, $params = [])
377
    {
378
        if (empty($params)) {
379
            return $path;
380
        }
381
382
        return $path . '?' . http_build_query($params);
383
    }
384
385
    private function getClassMetadata()
386
    {
387
        if (!isset($this->classMetadata)) {
388
            $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...
389
                ->getMapping()
390
                ->getClassMetadata($this->entityName);
391
        }
392
393
        return $this->classMetadataCache;
394
    }
395
396
    private function getIdentifier($entity)
397
    {
398
        $mapping = $this->sdk->getMapping();
0 ignored issues
show
Unused Code introduced by
$mapping is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
399
        $classMetadata = $this->getClassMetadata();
400
401
        if ($classMetadata->getIdentifierAttribute()) {
402
            $idAttr = $classMetadata->getIdentifierAttribute()
403
                ->getAttributeName();
404
            $idGetter = 'get' . ucfirst($idAttr);
405
        } else {
406
            $idGetter = 'getId';
407
        }
408
409
        return $entity->{$idGetter}();
410
    }
411
}
412