Passed
Pull Request — master (#68)
by Julien
17:46 queued 14:47
created

EntityRepository   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 428
Duplicated Lines 8.18 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 98.08%

Importance

Changes 10
Bugs 1 Features 2
Metric Value
wmc 41
c 10
b 1
f 2
lcom 1
cbo 10
dl 35
loc 428
ccs 153
cts 156
cp 0.9808
rs 8.2769

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A find() 0 22 2
B findAll() 5 31 4
A remove() 0 8 1
A update() 0 23 2
A persist() 0 23 2
C __call() 13 75 10
A fetchFromCache() 8 15 3
A saveToCache() 9 14 3
A removeFromCache() 0 14 3
A addQueryParameter() 0 8 2
B convertQueryParameters() 0 30 5
A normalizeCacheKey() 0 4 1
A getClassMetadata() 0 10 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EntityRepository often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EntityRepository, and based on these observations, apply Extract Interface, too.

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