Completed
Push — master ( 9d3c1c...022d9c )
by Philip
05:04
created

DoctrineCrudService::findAllPaginated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
ccs 5
cts 5
cp 1
rs 9.4285
cc 1
eloc 5
nc 1
nop 2
crap 1
1
<?php
2
3
namespace Dontdrinkandroot\Service;
4
5
use Doctrine\Common\Collections\Collection;
6
use Doctrine\ORM\EntityManager;
7
use Doctrine\ORM\EntityRepository;
8
use Doctrine\ORM\Mapping\ClassMetadata;
9
use Doctrine\ORM\QueryBuilder;
10
use Doctrine\ORM\Tools\Pagination\Paginator;
11
use Dontdrinkandroot\Repository\TransactionManager;
12
use Psr\Log\LoggerInterface;
13
use Psr\Log\NullLogger;
14
use Symfony\Component\PropertyAccess\PropertyAccess;
15
16
class DoctrineCrudService extends EntityRepository implements CrudServiceInterface
17
{
18
    /**
19
     * @var  LoggerInterface
20
     */
21
    private $logger;
22
23
    /**
24
     * @var TransactionManager
25
     */
26
    private $transactionManager;
27
28
    /**
29
     * DoctrineCrudService constructor.
30
     *
31
     * @param EntityManager        $em
32
     * @param ClassMetadata|string $class
33
     */
34 10
    public function __construct(EntityManager $em, $class)
35
    {
36 10
        $classMetaData = $class;
37 10
        if (is_string($classMetaData)) {
38
            $classMetaData = $em->getClassMetadata($classMetaData);
39
        }
40 10
        parent::__construct($em, $classMetaData);
41 10
        $this->transactionManager = new TransactionManager($em);
42 10
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47 2
    public function findAllPaginated(int $page = 1, int $perPage = 50): Paginator
48
    {
49 2
        $queryBuilder = $this->createFindAllQueryBuilder();
50 2
        $queryBuilder->setFirstResult(($page - 1) * $perPage);
51 2
        $queryBuilder->setMaxResults($perPage);
52
53 2
        return new Paginator($queryBuilder);
54
    }
55
56 2
    protected function createFindAllQueryBuilder(): QueryBuilder
57
    {
58 2
        $queryBuilder = $this->createQueryBuilder('entity');
59
60 2
        return $queryBuilder;
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66
    public function create($entity, bool $flush = true)
67
    {
68
        $this->getEntityManager()->persist($entity);
69
        if ($flush) {
70
            $this->getEntityManager()->flush($entity);
71
        }
72
73
        return $entity;
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function update($entity, bool $flush = false)
80
    {
81
        if ($flush || $this->isVersioned($entity)) {
82
            $this->getEntityManager()->flush($entity);
83
        }
84
85
        return $entity;
86
    }
87
88
    protected function isVersioned($entity)
89
    {
90
        $classMetadata = $this->getEntityManager()->getClassMetadata(get_class($entity));
91
92
        return $classMetadata->isVersioned;
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function remove($entity, bool $flush = false)
99
    {
100
        $this->getEntityManager()->remove($entity);
101
        if ($flush) {
102
            $this->getEntityManager()->flush($entity);
103
        }
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109 6
    public function findAssociationPaginated($entity, string $fieldName, int $page = 1, $perPage = 50)
110
    {
111 6
        $classMetadata = $this->getEntityManager()->getClassMetadata(get_class($entity));
112 6
        $association = $classMetadata->associationMappings[$fieldName];
113 6
        $targetClass = $classMetadata->getAssociationTargetClass($fieldName);
114 6
        $inverseFieldName = $this->getInverseFieldName($fieldName, $classMetadata);
115
116 6
        $queryBuilder = $this->getEntityManager()->createQueryBuilder();
117 6
        $queryBuilder->select('association');
118 6
        $queryBuilder->from($targetClass, 'association');
119 6
        $queryBuilder->join('association.' . $inverseFieldName, 'entity');
120 6
        $queryBuilder->where('entity = :entity');
121
122 6
        if (array_key_exists('orderBy', $association)) {
123 4
            $orderBy = $association['orderBy'];
124 4
            foreach ($orderBy as $fieldName => $order) {
125 4
                $queryBuilder->addOrderBy('association.' . $fieldName, $order);
126
            }
127
        }
128
129 6
        $queryBuilder->setParameter('entity', $entity);
130
131 6
        $queryBuilder->setFirstResult(($page - 1) * $perPage);
132 6
        $queryBuilder->setMaxResults($perPage);
133
134 6
        return new Paginator($queryBuilder);
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function createAssociation($entity, string $fieldName)
141
    {
142
        $classMetadata = $this->getEntityManager()->getClassMetadata(get_class($entity));
143
        $targetClass = $classMetadata->getAssociationTargetClass($fieldName);
144
        $child = new $targetClass;
145
146
        $inverseFieldName = $this->getInverseFieldName($fieldName, $classMetadata);
147
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
148
        $propertyAccessor->setValue($child, $inverseFieldName, $entity);
149
150
        return $child;
0 ignored issues
show
Bug Compatibility introduced by
The expression return $child; of type object|array is incompatible with the return type declared by the interface Dontdrinkandroot\Service...face::createAssociation of type object as it can also be of type array which is not included in this return type.
Loading history...
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156 2
    public function addAssociation($entity, string $fieldName, $id)
157
    {
158 2
        $classMetadata = $this->getEntityManager()->getClassMetadata(get_class($entity));
159 2
        $collection = $classMetadata->isCollectionValuedAssociation($fieldName);
160 2
        $targetClass = $classMetadata->getAssociationTargetClass($fieldName);
161 2
        $inverse = $classMetadata->isAssociationInverseSide($fieldName);
162 2
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
163 2
        $reference = $this->getEntityManager()->getReference($targetClass, $id);
164
165 2
        if (!$inverse) {
166 2
            if ($collection) {
167
                /** @var Collection $collection */
168 2
                $collection = $propertyAccessor->getValue($entity, $fieldName);
169 2
                $collection->add($reference);
170
            } else {
171
                $propertyAccessor->setValue($entity, $fieldName, $reference);
172
            }
173 2
            $this->getEntityManager()->flush($entity);
174
        } else {
175
            $inverseClassMetadata = $this->getEntityManager()->getClassMetadata($targetClass);
176
            $association = $classMetadata->getAssociationMapping($fieldName);
177
            $inverseFieldName = $association['mappedBy'];
178
            $inverseCollection = $inverseClassMetadata->isCollectionValuedAssociation($inverseFieldName);
179
            if ($inverseCollection) {
180
                /** @var Collection $collection */
181
                $collection = $propertyAccessor->getValue($reference, $inverseFieldName);
0 ignored issues
show
Bug introduced by
It seems like $reference defined by $this->getEntityManager(...ence($targetClass, $id) on line 163 can also be of type null; however, Symfony\Component\Proper...rtyAccessor::getValue() does only seem to accept object|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
182
                $collection->add($entity);
183
            } else {
184
                $propertyAccessor->setValue($reference, $inverseFieldName, $entity);
0 ignored issues
show
Bug introduced by
It seems like $reference defined by $this->getEntityManager(...ence($targetClass, $id) on line 163 can also be of type null; however, Symfony\Component\Proper...rtyAccessor::setValue() does only seem to accept object|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
185
            }
186
            $this->getEntityManager()->flush($reference);
187
        }
188 2
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193
    public function removeAssociation($entity, string $fieldName, $id = null)
194
    {
195
        $classMetadata = $this->getEntityManager()->getClassMetadata(get_class($entity));
196
        $collection = $classMetadata->isCollectionValuedAssociation($fieldName);
197
        $targetClass = $classMetadata->getAssociationTargetClass($fieldName);
198
        $inverse = $classMetadata->isAssociationInverseSide($fieldName);
199
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
200
201
        if ($inverse) {
202
            $reference = $this->getEntityManager()->getReference($targetClass, $id);
203
            $inverseClassMetadata = $this->getEntityManager()->getClassMetadata($targetClass);
204
            $association = $classMetadata->getAssociationMapping($fieldName);
205
            $inverseFieldName = $association['mappedBy'];
206
            $inverseCollection = $inverseClassMetadata->isCollectionValuedAssociation($inverseFieldName);
207
            if ($inverseCollection) {
208
                /** @var Collection $collection */
209
                $collection = $propertyAccessor->getValue($reference, $inverseFieldName);
0 ignored issues
show
Bug introduced by
It seems like $reference defined by $this->getEntityManager(...ence($targetClass, $id) on line 202 can also be of type null; however, Symfony\Component\Proper...rtyAccessor::getValue() does only seem to accept object|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
210
                $collection->removeElement($entity);
211
            } else {
212
                $propertyAccessor->setValue($reference, $inverseFieldName, null);
0 ignored issues
show
Bug introduced by
It seems like $reference defined by $this->getEntityManager(...ence($targetClass, $id) on line 202 can also be of type null; however, Symfony\Component\Proper...rtyAccessor::setValue() does only seem to accept object|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
213
            }
214
            $this->getEntityManager()->flush($reference);
215
        } else {
216
            if ($collection) {
217
                $reference = $this->getEntityManager()->getReference($targetClass, $id);
218
                /** @var Collection $collection */
219
                $collection = $propertyAccessor->getValue($entity, $fieldName);
220
                $collection->removeElement($reference);
221
            } else {
222
                $propertyAccessor->setValue($entity, $fieldName, null);
223
            }
224
            $this->getEntityManager()->flush($entity);
225
        }
226
    }
227
228
    /**
229
     * @param string        $fieldName
230
     * @param ClassMetadata $classMetadata
231
     *
232
     * @return string
233
     */
234 6
    protected function getInverseFieldName(string $fieldName, ClassMetadata $classMetadata)
235
    {
236 6
        $association = $classMetadata->getAssociationMapping($fieldName);
237 6
        if ($classMetadata->isAssociationInverseSide($fieldName)) {
238 2
            return $association['mappedBy'];
239
        } else {
240 4
            return $association['inversedBy'];
241
        }
242
    }
243
244
    /**
245
     * @param LoggerInterface $logger
246
     */
247
    public function setLogger(LoggerInterface $logger): void
248
    {
249
        $this->logger = $logger;
250
    }
251
252
    /**
253
     * @return LoggerInterface
254
     */
255
    public function getLogger(): LoggerInterface
256
    {
257
        if (null === $this->logger) {
258
            $this->logger = new NullLogger();
259
        }
260
261
        return $this->logger;
262
    }
263
}
264