Completed
Push — master ( c1d3d0...8a5019 )
by Dominik
01:46
created

AbstractDoctrineRepository::persist()   C

Complexity

Conditions 7
Paths 32

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 36
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 23
nc 32
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Chubbyphp\Model\Doctrine\DBAL;
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
        $models = [];
194
        foreach ($row as $field => $value) {
195
            if ($value instanceof ModelCollectionInterface) {
196
                $modelCollections[] = $value;
197
                unset($row[$field]);
198
            } elseif ($value instanceof ModelInterface) {
199
                $models[] = $value;
200
                unset($row[$field]);
201
            }
202
        }
203
204
        if (null === $this->find($model->getId())) {
205
            $this->connection->insert($this->getTable(), $row);
206
        } else {
207
            $this->connection->update($this->getTable(), $row, ['id' => $model->getId()]);
208
        }
209
210
        foreach ($modelCollections as $modelCollection) {
211
            $this->persistRelatedModels($modelCollection);
212
        }
213
214
        foreach ($models as $model) {
215
            $this->persistRelatedModel($model);
216
        }
217
218
        $this->cache->set($model->getId(), $row);
219
    }
220
221
    /**
222
     * @param ModelCollectionInterface $modelCollection
223
     */
224
    private function persistRelatedModels(ModelCollectionInterface $modelCollection)
225
    {
226
        $initialModels = $modelCollection->getInitial();
227
        $models = $modelCollection->get();
228
        foreach ($models as $model) {
229
            $this->persistRelatedModel($model);
230
            if (isset($initialModels[$model->getId()])) {
231
                unset($initialModels[$model->getId()]);
232
            }
233
        }
234
235
        foreach ($initialModels as $initialModel) {
236
            $this->removeRelatedModel($initialModel);
237
        }
238
    }
239
240
    /**
241
     * @param ModelInterface $model
242
     */
243
    private function persistRelatedModel(ModelInterface $model)
244
    {
245
        $this->resolver->getRepositoryByClass(get_class($model))->persist($model);
0 ignored issues
show
Bug introduced by
The method getRepositoryByClass() 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...
246
    }
247
248
    /**
249
     * @param ModelInterface $model
250
     */
251
    public function remove(ModelInterface $model)
252
    {
253
        $this->logger->info(
254
            'model: remove model {model} with id {id}',
255
            ['model' => get_class($model), 'id' => $model->getId()]
256
        );
257
258
        $row = $model->toPersistence();
259
        foreach ($row as $field => $value) {
260
            if ($value instanceof ModelCollectionInterface) {
261
                $this->removeRelatedModels($value);
262
            } elseif ($value instanceof ModelInterface) {
263
                $this->removeRelatedModel($value);
264
            }
265
        }
266
267
        $this->connection->delete($this->getTable(), ['id' => $model->getId()]);
268
269
        $this->cache->remove($model->getId());
270
    }
271
272
    /**
273
     * @paramModelCollectionInterface $modelCollection
274
     */
275
    private function removeRelatedModels(ModelCollectionInterface $modelCollection)
276
    {
277
        foreach ($modelCollection->getInitial() as $initialModel) {
278
            $this->removeRelatedModel($initialModel);
279
        }
280
    }
281
282
    /**
283
     * @param ModelInterface $model
284
     */
285
    private function removeRelatedModel(ModelInterface $model)
286
    {
287
        $this->resolver->getRepositoryByClass(get_class($model))->remove($model);
0 ignored issues
show
Bug introduced by
The method getRepositoryByClass() 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...
288
    }
289
290
    /**
291
     * @param array $row
292
     *
293
     * @return ModelInterface
294
     */
295
    protected function fromPersistence(array $row): ModelInterface
296
    {
297
        /** @var ModelInterface $modelClass */
298
        $modelClass = $this->getModelClass();
299
300
        return $modelClass::fromPersistence($row);
301
    }
302
303
    /**
304
     * @return string
305
     */
306
    abstract protected function getTable(): string;
307
}
308