Completed
Push — master ( 0b8903...b5a73d )
by Dominik
01:50
created

AbstractDoctrineRepository   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 326
Duplicated Lines 6.75 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 10
dl 22
loc 326
rs 9
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
B find() 0 27 3
A findOneBy() 0 15 2
B findBy() 0 34 3
A addCriteriaToQueryBuilder() 0 7 2
A addOrderByToQueryBuilder() 0 10 3
B persist() 0 30 6
B remove() 0 25 4
A clear() 0 6 1
A insert() 11 11 1
A update() 11 11 1
A persistRelatedModels() 0 16 4
A persistRelatedModel() 0 4 1
A removeRelatedModels() 0 6 2
A removeRelatedModel() 0 4 1
fromPersistence() 0 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\Collection\ModelCollectionInterface;
8
use Chubbyphp\Model\ModelInterface;
9
use Chubbyphp\Model\RepositoryInterface;
10
use Chubbyphp\Model\ResolverInterface;
11
use Chubbyphp\Model\StorageCache\NullStorageCache;
12
use Chubbyphp\Model\StorageCache\StorageCacheInterface;
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 StorageCacheInterface
32
     */
33
    protected $storageCache;
34
35
    /**
36
     * @var LoggerInterface
37
     */
38
    protected $logger;
39
40
    /**
41
     * @param Connection           $connection
42
     * @param ResolverInterface    $resolver
43
     * @param StorageCacheInterface  $storageCache
44
     * @param LoggerInterface|null $logger
45
     */
46
    public function __construct(
47
        Connection $connection,
48
        ResolverInterface $resolver,
49
        StorageCacheInterface $storageCache,
50
        LoggerInterface $logger = null
51
    ) {
52
        $this->connection = $connection;
53
        $this->resolver = $resolver;
54
        $this->storageCache = $storageCache ?? new NullStorageCache();
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
        $table = $this->getTable();
66
67
        $this->logger->info('model: find row within table {table} with id {id}', ['table' => $table, 'id' => $id]);
68
69
        if ($this->storageCache->has($id)) {
70
            return $this->fromPersistence($this->storageCache->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
        if (false === $row) {
78
            $this->logger->warning(
79
                'model: row within table {table} with id {id} not found',
80
                ['table' => $table, 'id' => $id]
81
            );
82
83
            return null;
84
        }
85
86
        $this->storageCache->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, array $orderBy = null)
97
    {
98
        $models = $this->findBy($criteria, $orderBy, 1, 0);
99
100
        if ([] === $models) {
101
            $this->logger->warning(
102
                'model: row within table {table} with criteria {criteria} not found',
103
                ['table' => $this->getTable(), 'criteria' => $criteria]
104
            );
105
106
            return null;
107
        }
108
109
        return reset($models);
110
    }
111
112
    /**
113
     * @param array $criteria
114
     *
115
     * @return ModelInterface[]|array
116
     */
117
    public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array
118
    {
119
        $table = $this->getTable();
120
121
        $this->logger->info(
122
            'model: find rows within table {table} with criteria {criteria}',
123
            ['table' => $table, 'criteria' => $criteria, 'orderBy' => $orderBy, 'limit' => $limit, 'offset' => $offset]
124
        );
125
126
        $qb = $this->connection->createQueryBuilder()
127
            ->select('*')
128
            ->from($table)
129
            ->setFirstResult($offset)
130
            ->setMaxResults($limit)
131
        ;
132
133
        $this->addCriteriaToQueryBuilder($qb, $criteria);
134
        $this->addOrderByToQueryBuilder($qb, $orderBy);
135
136
        $rows = $qb->execute()->fetchAll(\PDO::FETCH_ASSOC);
137
138
        if ([] === $rows) {
139
            return [];
140
        }
141
142
        $models = [];
143
        foreach ($rows as $row) {
144
            $this->storageCache->set($row['id'], $row);
145
146
            $models[] = $this->fromPersistence($row);
147
        }
148
149
        return $models;
150
    }
151
152
    /**
153
     * @param QueryBuilder $qb
154
     * @param array        $criteria
155
     */
156
    protected function addCriteriaToQueryBuilder(QueryBuilder $qb, array $criteria)
157
    {
158
        foreach ($criteria as $field => $value) {
159
            $qb->andWhere($qb->expr()->eq($field, ':'.$field));
160
            $qb->setParameter($field, $value);
161
        }
162
    }
163
164
    /**
165
     * @param QueryBuilder $qb
166
     * @param array|null   $orderBy
167
     */
168
    protected function addOrderByToQueryBuilder(QueryBuilder $qb, array $orderBy = null)
169
    {
170
        if (null === $orderBy) {
171
            return;
172
        }
173
174
        foreach ($orderBy as $field => $direction) {
175
            $qb->addOrderBy($field, $direction);
176
        }
177
    }
178
179
    /**
180
     * @param ModelInterface $model
181
     * @return RepositoryInterface
182
     */
183
    public function persist(ModelInterface $model): RepositoryInterface
184
    {
185
        $id = $model->getId();
186
        $row = $model->toPersistence();
187
188
        $modelCollections = [];
189
        foreach ($row as $field => $value) {
190
            if ($value instanceof ModelCollectionInterface) {
191
                $modelCollections[] = $value;
192
                unset($row[$field]);
193
            } elseif ($value instanceof ModelInterface) {
194
                $this->persistRelatedModel($value);
195
                unset($row[$field]);
196
            }
197
        }
198
199
        if (null === $this->find($id)) {
200
            $this->insert($id, $row);
201
        } else {
202
            $this->update($id, $row);
203
        }
204
205
        foreach ($modelCollections as $modelCollection) {
206
            $this->persistRelatedModels($modelCollection);
207
        }
208
209
        $this->storageCache->set($id, $row);
210
211
        return $this;
212
    }
213
214
    /**
215
     * @param ModelInterface $model
216
     * @return RepositoryInterface
217
     */
218
    public function remove(ModelInterface $model): RepositoryInterface
219
    {
220
        $table = $this->getTable();
221
222
        $this->logger->info(
223
            'model: remove row from table {table} with id {id}',
224
            ['table' => $table, 'id' => $model->getId()]
225
        );
226
227
        $row = $model->toPersistence();
228
229
        foreach ($row as $field => $value) {
230
            if ($value instanceof ModelCollectionInterface) {
231
                $this->removeRelatedModels($value);
232
            } elseif ($value instanceof ModelInterface) {
233
                $this->removeRelatedModel($value);
234
            }
235
        }
236
237
        $this->connection->delete($table, ['id' => $model->getId()]);
238
239
        $this->storageCache->remove($model->getId());
240
241
        return $this;
242
    }
243
244
    /**
245
     * @return RepositoryInterface
246
     */
247
    public function clear(): RepositoryInterface
248
    {
249
        $this->storageCache->clear();
250
251
        return $this;
252
    }
253
254
    /**
255
     * @param string $id
256
     * @param array  $row
257
     */
258 View Code Duplication
    protected function insert(string $id, array $row)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
259
    {
260
        $table = $this->getTable();
261
262
        $this->logger->info(
263
            'model: insert row into table {table} with id {id}',
264
            ['table' => $table, 'id' => $id]
265
        );
266
267
        $this->connection->insert($table, $row);
268
    }
269
270
    /**
271
     * @param string $id
272
     * @param array  $row
273
     */
274 View Code Duplication
    protected function update(string $id, array $row)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
275
    {
276
        $table = $this->getTable();
277
278
        $this->logger->info(
279
            'model: update row into table {table} with id {id}',
280
            ['table' => $table, 'id' => $id]
281
        );
282
283
        $this->connection->update($table, $row, ['id' => $id]);
284
    }
285
286
    /**
287
     * @param ModelCollectionInterface $modelCollection
288
     */
289
    private function persistRelatedModels(ModelCollectionInterface $modelCollection)
290
    {
291
        $initialModels = $modelCollection->getInitialModels();
292
        $models = $modelCollection->getModels();
293
294
        foreach ($models as $model) {
295
            $this->persistRelatedModel($model);
296
            if (isset($initialModels[$model->getId()])) {
297
                unset($initialModels[$model->getId()]);
298
            }
299
        }
300
301
        foreach ($initialModels as $initialModel) {
302
            $this->removeRelatedModel($initialModel);
303
        }
304
    }
305
306
    /**
307
     * @param ModelInterface $model
308
     */
309
    private function persistRelatedModel(ModelInterface $model)
310
    {
311
        $this->resolver->persist($model);
312
    }
313
314
    /**
315
     * @param ModelCollectionInterface $modelCollection
316
     */
317
    private function removeRelatedModels(ModelCollectionInterface $modelCollection)
318
    {
319
        foreach ($modelCollection->getInitialModels() as $initialModel) {
320
            $this->removeRelatedModel($initialModel);
321
        }
322
    }
323
324
    /**
325
     * @param ModelInterface $model
326
     */
327
    private function removeRelatedModel(ModelInterface $model)
328
    {
329
        $this->resolver->remove($model);
330
    }
331
332
    /**
333
     * @param array $row
334
     *
335
     * @return ModelInterface
336
     */
337
    abstract protected function fromPersistence(array $row): ModelInterface;
338
339
    /**
340
     * @return string
341
     */
342
    abstract protected function getTable(): string;
343
}
344