Completed
Push — master ( c9ec76...c066a0 )
by Dominik
01:42
created

AbstractDoctrineRepository   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 285
Duplicated Lines 5.61 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 8
dl 16
loc 285
rs 9.6
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
B find() 8 27 3
B findOneBy() 8 25 2
B findBy() 0 35 5
A getFindByQueryBuilder() 0 12 2
B persist() 0 31 6
A persistRelatedModels() 0 15 4
A persistRelatedModel() 0 4 1
A remove() 0 20 4
A removeRelatedModels() 0 6 2
A removeRelatedModel() 0 4 1
A fromPersistence() 0 7 1
getTable() 0 1 ?

How to fix   Duplicated Code   

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:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Chubbyphp\Model\Doctrine\DBAL\Repository;
6
7
use Chubbyphp\Model\Cache\ModelCache;
8
use Chubbyphp\Model\Cache\ModelCacheInterface;
9
use Chubbyphp\Model\Collection\ModelCollectionInterface;
10
use Chubbyphp\Model\ModelInterface;
11
use Chubbyphp\Model\RepositoryInterface;
12
use Chubbyphp\Model\ResolverInterface;
13
use Doctrine\DBAL\Connection;
14
use Doctrine\DBAL\Query\QueryBuilder;
15
use Psr\Log\LoggerInterface;
16
use Psr\Log\NullLogger;
17
18
abstract class AbstractDoctrineRepository implements RepositoryInterface
19
{
20
    /**
21
     * @var Connection
22
     */
23
    protected $connection;
24
25
    /**
26
     * @var ResolverInterface
27
     */
28
    protected $resolver;
29
30
    /**
31
     * @var ModelCacheInterface
32
     */
33
    protected $cache;
34
35
    /**
36
     * @var LoggerInterface
37
     */
38
    protected $logger;
39
40
    /**
41
     * @param Connection               $connection
42
     * @param ResolverInterface        $resolver
43
     * @param ModelCacheInterface|null $cache
44
     * @param LoggerInterface|null     $logger
45
     */
46
    public function __construct(
47
        Connection $connection,
48
        ResolverInterface $resolver,
49
        ModelCacheInterface $cache = null,
50
        LoggerInterface $logger = null
51
    ) {
52
        $this->connection = $connection;
53
        $this->resolver = $resolver;
54
        $this->cache = $cache ?? new ModelCache();
55
        $this->logger = $logger ?? new NullLogger();
56
    }
57
58
    /**
59
     * @param string $id
60
     *
61
     * @return ModelInterface|null
62
     */
63
    public function find(string $id)
64
    {
65
        $modelClass = $this->getModelClass();
66
67
        $this->logger->info('model: find model {model} with id {id}', ['model' => $modelClass, 'id' => $id]);
68
69
        if ($this->cache->has($id)) {
70
            return $this->fromPersistence($this->cache->get($id));
71
        }
72
73
        $qb = $this->connection->createQueryBuilder();
74
        $qb->select('*')->from($this->getTable())->where($qb->expr()->eq('id', ':id'))->setParameter('id', $id);
75
76
        $row = $qb->execute()->fetch(\PDO::FETCH_ASSOC);
77 View Code Duplication
        if (false === $row) {
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...
78
            $this->logger->warning(
79
                'model: model {model} with id {id} not found',
80
                ['model' => $modelClass, 'id' => $id]
81
            );
82
83
            return null;
84
        }
85
86
        $this->cache->set($row['id'], $row);
87
88
        return $this->fromPersistence($row);
89
    }
90
91
    /**
92
     * @param array $criteria
93
     *
94
     * @return null|ModelInterface
95
     */
96
    public function findOneBy(array $criteria)
97
    {
98
        $modelClass = $this->getModelClass();
99
100
        $this->logger->info(
101
            'model: find model {model} with criteria {criteria}',
102
            ['model' => $modelClass, 'criteria' => $criteria]
103
        );
104
105
        $qb = $this->getFindByQueryBuilder($criteria)->setMaxResults(1);
106
107
        $row = $qb->execute()->fetch(\PDO::FETCH_ASSOC);
108 View Code Duplication
        if (false === $row) {
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...
109
            $this->logger->warning(
110
                'model: model {model} with criteria {criteria} not found',
111
                ['model' => $modelClass, 'criteria' => $criteria]
112
            );
113
114
            return null;
115
        }
116
117
        $this->cache->set($row['id'], $row);
118
119
        return $this->fromPersistence($row);
120
    }
121
122
    /**
123
     * @param array $criteria
124
     *
125
     * @return ModelInterface[]|array
126
     */
127
    public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array
128
    {
129
        $modelClass = $this->getModelClass();
130
131
        $this->logger->info(
132
            'model: find model {model} with criteria {criteria}',
133
            ['model' => $modelClass, 'criteria' => $criteria]
134
        );
135
136
        $qb = $this
137
            ->getFindByQueryBuilder($criteria)
138
            ->setFirstResult($offset)
139
            ->setMaxResults($limit)
140
        ;
141
142
        if (null !== $orderBy) {
143
            foreach ($orderBy as $field => $direction) {
144
                $qb->addOrderBy($field, $direction);
145
            }
146
        }
147
148
        $rows = $qb->execute()->fetchAll(\PDO::FETCH_ASSOC);
149
150
        if ([] === $rows) {
151
            return [];
152
        }
153
154
        $models = [];
155
        foreach ($rows as $row) {
156
            $this->cache->set($row['id'], $row);
157
            $models[] = $this->fromPersistence($row);
158
        }
159
160
        return $models;
161
    }
162
163
    /**
164
     * @param array $criteria
165
     *
166
     * @return QueryBuilder
167
     */
168
    private function getFindByQueryBuilder(array $criteria = []): QueryBuilder
169
    {
170
        $qb = $this->connection->createQueryBuilder();
171
        $qb->select('*')->from($this->getTable());
172
173
        foreach ($criteria as $field => $value) {
174
            $qb->andWhere($qb->expr()->eq($field, ':'.$field));
175
            $qb->setParameter($field, $value);
176
        }
177
178
        return $qb;
179
    }
180
181
    /**
182
     * @param ModelInterface $model
183
     */
184
    public function persist(ModelInterface $model)
185
    {
186
        $this->logger->info(
187
            'model: persist model {model} with id {id}',
188
            ['model' => get_class($model), 'id' => $model->getId()]
189
        );
190
191
        $row = $model->toPersistence();
192
        $modelCollections = [];
193
        foreach ($row as $field => $value) {
194
            if ($value instanceof ModelCollectionInterface) {
195
                $modelCollections[] = $value;
196
                unset($row[$field]);
197
            } elseif ($value instanceof ModelInterface) {
198
                $this->persistRelatedModel($value);
199
                unset($row[$field]);
200
            }
201
        }
202
203
        if (null === $this->find($model->getId())) {
204
            $this->connection->insert($this->getTable(), $row);
205
        } else {
206
            $this->connection->update($this->getTable(), $row, ['id' => $model->getId()]);
207
        }
208
209
        foreach ($modelCollections as $modelCollection) {
210
            $this->persistRelatedModels($modelCollection);
211
        }
212
213
        $this->cache->set($model->getId(), $row);
214
    }
215
216
    /**
217
     * @param ModelCollectionInterface $modelCollection
218
     */
219
    private function persistRelatedModels(ModelCollectionInterface $modelCollection)
220
    {
221
        $initialModels = $modelCollection->getInitial();
222
        $models = $modelCollection->get();
223
        foreach ($models as $model) {
224
            $this->persistRelatedModel($model);
225
            if (isset($initialModels[$model->getId()])) {
226
                unset($initialModels[$model->getId()]);
227
            }
228
        }
229
230
        foreach ($initialModels as $initialModel) {
231
            $this->removeRelatedModel($initialModel);
232
        }
233
    }
234
235
    /**
236
     * @param ModelInterface $model
237
     */
238
    private function persistRelatedModel(ModelInterface $model)
239
    {
240
        $this->resolver->persist($model);
0 ignored issues
show
Bug introduced by
The method persist() does not seem to exist on object<Chubbyphp\Model\ResolverInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
241
    }
242
243
    /**
244
     * @param ModelInterface $model
245
     */
246
    public function remove(ModelInterface $model)
247
    {
248
        $this->logger->info(
249
            'model: remove model {model} with id {id}',
250
            ['model' => get_class($model), 'id' => $model->getId()]
251
        );
252
253
        $row = $model->toPersistence();
254
        foreach ($row as $field => $value) {
255
            if ($value instanceof ModelCollectionInterface) {
256
                $this->removeRelatedModels($value);
257
            } elseif ($value instanceof ModelInterface) {
258
                $this->removeRelatedModel($value);
259
            }
260
        }
261
262
        $this->connection->delete($this->getTable(), ['id' => $model->getId()]);
263
264
        $this->cache->remove($model->getId());
265
    }
266
267
    /**
268
     * @paramModelCollectionInterface $modelCollection
269
     */
270
    private function removeRelatedModels(ModelCollectionInterface $modelCollection)
271
    {
272
        foreach ($modelCollection->getInitial() as $initialModel) {
273
            $this->removeRelatedModel($initialModel);
274
        }
275
    }
276
277
    /**
278
     * @param ModelInterface $model
279
     */
280
    private function removeRelatedModel(ModelInterface $model)
281
    {
282
        $this->resolver->remove($model);
0 ignored issues
show
Bug introduced by
The method remove() does not seem to exist on object<Chubbyphp\Model\ResolverInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
283
    }
284
285
    /**
286
     * @param array $row
287
     *
288
     * @return ModelInterface
289
     */
290
    protected function fromPersistence(array $row): ModelInterface
291
    {
292
        /** @var ModelInterface $modelClass */
293
        $modelClass = $this->getModelClass();
294
295
        return $modelClass::fromPersistence($row);
296
    }
297
298
    /**
299
     * @return string
300
     */
301
    abstract protected function getTable(): string;
302
}
303