Completed
Push — master ( df6aa6...29ce4a )
by Dominik
03:04
created

AbstractDoctrineRepository::findBy()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 34
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 20
nc 3
nop 4
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\Reference\ModelReferenceInterface;
10
use Chubbyphp\Model\RepositoryInterface;
11
use Chubbyphp\Model\ResolverInterface;
12
use Chubbyphp\Model\StorageCache\NullStorageCache;
13
use Chubbyphp\Model\StorageCache\StorageCacheInterface;
14
use Doctrine\DBAL\Connection;
15
use Doctrine\DBAL\Query\QueryBuilder;
16
use Psr\Log\LoggerInterface;
17
use Psr\Log\NullLogger;
18
19
abstract class AbstractDoctrineRepository implements RepositoryInterface
20
{
21
    /**
22
     * @var Connection
23
     */
24
    protected $connection;
25
26
    /**
27
     * @var ResolverInterface
28
     */
29
    protected $resolver;
30
31
    /**
32
     * @var StorageCacheInterface
33
     */
34
    protected $storageCache;
35
36
    /**
37
     * @var LoggerInterface
38
     */
39
    protected $logger;
40
41
    /**
42
     * @param Connection            $connection
43
     * @param ResolverInterface     $resolver
44
     * @param StorageCacheInterface $storageCache
45
     * @param LoggerInterface|null  $logger
46
     */
47
    public function __construct(
48
        Connection $connection,
49
        ResolverInterface $resolver,
50
        StorageCacheInterface $storageCache,
51
        LoggerInterface $logger = null
52
    ) {
53
        $this->connection = $connection;
54
        $this->resolver = $resolver;
55
        $this->storageCache = $storageCache ?? new NullStorageCache();
56
        $this->logger = $logger ?? new NullLogger();
57
    }
58
59
    /**
60
     * @param string $id
61
     *
62
     * @return ModelInterface|null
63
     */
64
    public function find(string $id = null)
65
    {
66
        if (null === $id) {
67
            return null;
68
        }
69
70
        $table = $this->getTable();
71
72
        $this->logger->info('model: find row within table {table} with id {id}', ['table' => $table, 'id' => $id]);
73
74
        if ($this->storageCache->has($id)) {
75
            return $this->fromPersistence($this->storageCache->get($id));
76
        }
77
78
        $qb = $this->connection->createQueryBuilder();
79
        $qb->select('*')->from($this->getTable())->where($qb->expr()->eq('id', ':id'))->setParameter('id', $id);
80
81
        $row = $qb->execute()->fetch(\PDO::FETCH_ASSOC);
82
        if (false === $row) {
83
            $this->logger->notice(
84
                'model: row within table {table} with id {id} not found',
85
                ['table' => $table, 'id' => $id]
86
            );
87
88
            return null;
89
        }
90
91
        $this->storageCache->set($row['id'], $row);
92
93
        return $this->fromPersistence($row);
94
    }
95
96
    /**
97
     * @param array $criteria
98
     *
99
     * @return null|ModelInterface
100
     */
101
    public function findOneBy(array $criteria, array $orderBy = null)
102
    {
103
        $models = $this->findBy($criteria, $orderBy, 1, 0);
104
105
        if ([] === $models) {
106
            $this->logger->notice(
107
                'model: row within table {table} with criteria {criteria} not found',
108
                [
109
                    'table' => $this->getTable(),
110
                    'criteria' => $criteria,
111
                    'orderBy' => $orderBy,
112
                    'limit' => 1,
113
                    'offset' => 0,
114
                ]
115
            );
116
117
            return null;
118
        }
119
120
        return reset($models);
121
    }
122
123
    /**
124
     * @param array $criteria
125
     *
126
     * @return ModelInterface[]|array
127
     */
128
    public function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array
129
    {
130
        $table = $this->getTable();
131
132
        $this->logger->info(
133
            'model: find rows within table {table} with criteria {criteria}',
134
            ['table' => $table, 'criteria' => $criteria, 'orderBy' => $orderBy, 'limit' => $limit, 'offset' => $offset]
135
        );
136
137
        $qb = $this->connection->createQueryBuilder()
138
            ->select('*')
139
            ->from($table)
140
            ->setFirstResult($offset)
141
            ->setMaxResults($limit)
142
        ;
143
144
        $this->addCriteriaToQueryBuilder($qb, $criteria);
145
        $this->addOrderByToQueryBuilder($qb, $orderBy);
146
147
        $rows = $qb->execute()->fetchAll(\PDO::FETCH_ASSOC);
148
149
        if ([] === $rows) {
150
            return [];
151
        }
152
153
        $models = [];
154
        foreach ($rows as $row) {
155
            $this->storageCache->set($row['id'], $row);
156
157
            $models[] = $this->fromPersistence($row);
158
        }
159
160
        return $models;
161
    }
162
163
    /**
164
     * @param QueryBuilder $qb
165
     * @param array        $criteria
166
     */
167
    protected function addCriteriaToQueryBuilder(QueryBuilder $qb, array $criteria)
168
    {
169
        foreach ($criteria as $field => $value) {
170
            $qb->andWhere($qb->expr()->eq($field, ':'.$field));
171
            $qb->setParameter($field, $value);
172
        }
173
    }
174
175
    /**
176
     * @param QueryBuilder $qb
177
     * @param array|null   $orderBy
178
     */
179
    protected function addOrderByToQueryBuilder(QueryBuilder $qb, array $orderBy = null)
180
    {
181
        if (null === $orderBy) {
182
            return;
183
        }
184
185
        foreach ($orderBy as $field => $direction) {
186
            $qb->addOrderBy($field, $direction);
187
        }
188
    }
189
190
    /**
191
     * @param ModelInterface $model
192
     *
193
     * @return RepositoryInterface
194
     */
195
    public function persist(ModelInterface $model): RepositoryInterface
196
    {
197
        $id = $model->getId();
198
        $row = $model->toPersistence();
199
200
        $modelCollections = [];
201
        foreach ($row as $field => $value) {
202
            if ($value instanceof ModelCollectionInterface) {
203
                $modelCollections[] = $value;
204
                unset($row[$field]);
205
            } elseif ($value instanceof ModelReferenceInterface) {
206
                $row[$field.'Id'] = $this->persistReference($value);
207
                unset($row[$field]);
208
            }
209
        }
210
211
        if (null === $this->find($id)) {
212
            $this->insert($id, $row);
213
        } else {
214
            $this->update($id, $row);
215
        }
216
217
        foreach ($modelCollections as $modelCollection) {
218
            $this->persistCollection($modelCollection);
219
        }
220
221
        $this->storageCache->set($id, $row);
222
223
        return $this;
224
    }
225
226
    /**
227
     * @param ModelInterface $model
228
     *
229
     * @return RepositoryInterface
230
     */
231
    public function remove(ModelInterface $model): RepositoryInterface
232
    {
233
        $table = $this->getTable();
234
235
        $this->logger->info(
236
            'model: remove row from table {table} with id {id}',
237
            ['table' => $table, 'id' => $model->getId()]
238
        );
239
240
        $row = $model->toPersistence();
241
242
        foreach ($row as $field => $value) {
243
            if ($value instanceof ModelCollectionInterface) {
244
                $this->removeRelatedModels($value);
245
            } elseif ($value instanceof ModelReferenceInterface) {
246
                if (null !== $initialModel = $value->getInitialModel()) {
247
                    $this->removeRelatedModel($initialModel);
248
                }
249
            }
250
        }
251
252
        $this->connection->delete($table, ['id' => $model->getId()]);
253
254
        $this->storageCache->remove($model->getId());
255
256
        return $this;
257
    }
258
259
    /**
260
     * @return RepositoryInterface
261
     */
262
    public function clear(): RepositoryInterface
263
    {
264
        $this->storageCache->clear();
265
266
        return $this;
267
    }
268
269
    /**
270
     * @param string $id
271
     * @param array  $row
272
     */
273 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...
274
    {
275
        $table = $this->getTable();
276
277
        $this->logger->info(
278
            'model: insert row into table {table} with id {id}',
279
            ['table' => $table, 'id' => $id]
280
        );
281
282
        $this->connection->insert($table, $row);
283
    }
284
285
    /**
286
     * @param string $id
287
     * @param array  $row
288
     */
289 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...
290
    {
291
        $table = $this->getTable();
292
293
        $this->logger->info(
294
            'model: update row into table {table} with id {id}',
295
            ['table' => $table, 'id' => $id]
296
        );
297
298
        $this->connection->update($table, $row, ['id' => $id]);
299
    }
300
301
    /**
302
     * @param ModelReferenceInterface $reference
303
     * @return null|string
304
     */
305
    private function persistReference(ModelReferenceInterface $reference)
306
    {
307
        $initialModel = $reference->getInitialModel();
308
        $model = $reference->getModel();
309
310
        if (null !== $initialModel && (null === $model || $model->getId() !== $initialModel->getId())) {
311
            $this->removeRelatedModel($initialModel);
312
        }
313
314
        if (null !== $model) {
315
            $this->persistRelatedModel($model);
316
317
            return $model->getId();
318
        }
319
320
        return null;
321
    }
322
323
    /**
324
     * @param ModelCollectionInterface $modelCollection
325
     */
326
    private function persistCollection(ModelCollectionInterface $modelCollection)
327
    {
328
        $initialModels = $modelCollection->getInitialModels();
329
        $models = $modelCollection->getModels();
330
331
        foreach ($models as $model) {
332
            $this->persistRelatedModel($model);
333
            if (isset($initialModels[$model->getId()])) {
334
                unset($initialModels[$model->getId()]);
335
            }
336
        }
337
338
        foreach ($initialModels as $initialModel) {
339
            $this->removeRelatedModel($initialModel);
340
        }
341
    }
342
343
    /**
344
     * @param ModelInterface $model
345
     */
346
    private function persistRelatedModel(ModelInterface $model)
347
    {
348
        $this->resolver->persist($model);
349
    }
350
351
    /**
352
     * @param ModelCollectionInterface $modelCollection
353
     */
354
    private function removeRelatedModels(ModelCollectionInterface $modelCollection)
355
    {
356
        foreach ($modelCollection->getInitialModels() as $initialModel) {
357
            $this->removeRelatedModel($initialModel);
358
        }
359
    }
360
361
    /**
362
     * @param ModelInterface $model
363
     */
364
    private function removeRelatedModel(ModelInterface $model)
365
    {
366
        $this->resolver->remove($model);
367
    }
368
369
    /**
370
     * @param array $row
371
     *
372
     * @return ModelInterface
373
     */
374
    abstract protected function fromPersistence(array $row): ModelInterface;
375
376
    /**
377
     * @return string
378
     */
379
    abstract protected function getTable(): string;
380
}
381