Completed
Branch master (b86a05)
by Dominik
01:56
created

AbstractDoctrineRepository   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 310
Duplicated Lines 7.1 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 8
dl 22
loc 310
rs 9.2
c 0
b 0
f 0

16 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 28 6
B remove() 0 23 4
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\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
        $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->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
        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->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, 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' => $table, 'criteria' => $criteria]
0 ignored issues
show
Bug introduced by
The variable $table does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
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, $criteria);
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->cache->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
     */
182
    public function persist(ModelInterface $model)
183
    {
184
        $id = $model->getId();
185
        $row = $model->toPersistence();
186
187
        $modelCollections = [];
188
        foreach ($row as $field => $value) {
189
            if ($value instanceof ModelCollectionInterface) {
190
                $modelCollections[] = $value;
191
                unset($row[$field]);
192
            } elseif ($value instanceof ModelInterface) {
193
                $this->persistRelatedModel($value);
194
                unset($row[$field]);
195
            }
196
        }
197
198
        if (null === $this->find($id)) {
199
            $this->insert($id, $row);
200
        } else {
201
            $this->update($id, $row);
202
        }
203
204
        foreach ($modelCollections as $modelCollection) {
205
            $this->persistRelatedModels($modelCollection);
206
        }
207
208
        $this->cache->set($id, $row);
209
    }
210
211
    /**
212
     * @param ModelInterface $model
213
     */
214
    public function remove(ModelInterface $model)
215
    {
216
        $table = $this->getTable();
217
218
        $this->logger->info(
219
            'model: remove row from table {table} with id {id}',
220
            ['table' => $table, 'id' => $model->getId()]
221
        );
222
223
        $row = $model->toPersistence();
224
225
        foreach ($row as $field => $value) {
226
            if ($value instanceof ModelCollectionInterface) {
227
                $this->removeRelatedModels($value);
228
            } elseif ($value instanceof ModelInterface) {
229
                $this->removeRelatedModel($value);
230
            }
231
        }
232
233
        $this->connection->delete($table, ['id' => $model->getId()]);
234
235
        $this->cache->remove($model->getId());
236
    }
237
238
    /**
239
     * @param string $id
240
     * @param array  $row
241
     */
242 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...
243
    {
244
        $table = $this->getTable();
245
246
        $this->logger->info(
247
            'model: insert row into table {table} with id {id}',
248
            ['table' => $table, 'id' => $id]
249
        );
250
251
        $this->connection->insert($table, $row);
252
    }
253
254
    /**
255
     * @param string $id
256
     * @param array  $row
257
     */
258 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...
259
    {
260
        $table = $this->getTable();
261
262
        $this->logger->info(
263
            'model: update row into table {table} with id {id}',
264
            ['table' => $table, 'id' => $id]
265
        );
266
267
        $this->connection->update($table, $row, ['id' => $id]);
268
    }
269
270
    /**
271
     * @param ModelCollectionInterface $modelCollection
272
     */
273
    private function persistRelatedModels(ModelCollectionInterface $modelCollection)
274
    {
275
        $initialModels = $modelCollection->getInitialModels();
276
        $models = $modelCollection->getModels();
277
278
        foreach ($models as $model) {
279
            $this->persistRelatedModel($model);
280
            if (isset($initialModels[$model->getId()])) {
281
                unset($initialModels[$model->getId()]);
282
            }
283
        }
284
285
        foreach ($initialModels as $initialModel) {
286
            $this->removeRelatedModel($initialModel);
287
        }
288
    }
289
290
    /**
291
     * @param ModelInterface $model
292
     */
293
    private function persistRelatedModel(ModelInterface $model)
294
    {
295
        $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...
296
    }
297
298
    /**
299
     * @param ModelCollectionInterface $modelCollection
300
     */
301
    private function removeRelatedModels(ModelCollectionInterface $modelCollection)
302
    {
303
        foreach ($modelCollection->getInitialModels() as $initialModel) {
304
            $this->removeRelatedModel($initialModel);
305
        }
306
    }
307
308
    /**
309
     * @param ModelInterface $model
310
     */
311
    private function removeRelatedModel(ModelInterface $model)
312
    {
313
        $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...
314
    }
315
316
    /**
317
     * @param array $row
318
     *
319
     * @return ModelInterface
320
     */
321
    abstract protected function fromPersistence(array $row): ModelInterface;
322
323
    /**
324
     * @return string
325
     */
326
    abstract protected function getTable(): string;
327
}
328