Completed
Push — master ( 156090...254e6f )
by Philip
05:47
created

DoctrineCrudService::findAssociationPaginated()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 18
cts 18
cp 1
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 18
nc 2
nop 4
crap 3
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 8
    public function __construct(EntityManager $em, $class)
35
    {
36 8
        $classMetaData = $class;
37 8
        if (is_string($classMetaData)) {
38
            $classMetaData = $em->getClassMetadata($classMetaData);
39
        }
40 8
        parent::__construct($em, $classMetaData);
41 8
        $this->transactionManager = new TransactionManager($em);
42 8
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47
    public function findAllPaginated(int $page = 1, int $perPage = 50): Paginator
48
    {
49
        $queryBuilder = $this->createFindAllQueryBuilder();
50
        $queryBuilder->setFirstResult(($page - 1) * $perPage);
51
        $queryBuilder->setMaxResults($perPage);
52
53
        return new Paginator($queryBuilder);
54
    }
55
56
    protected function createFindAllQueryBuilder(): QueryBuilder
57
    {
58
        $queryBuilder = $this->createQueryBuilder('entity');
59
60
        return $queryBuilder;
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66
    public function create($entity)
67
    {
68
        $this->getEntityManager()->persist($entity);
69
        $this->getEntityManager()->flush($entity);
70
71
        return $entity;
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function update($entity)
78
    {
79
        $this->getEntityManager()->flush($entity);
80
81
        return $entity;
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function remove($entity)
88
    {
89
        $this->getEntityManager()->remove($entity);
90
        $this->getEntityManager()->flush($entity);
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96 6
    public function findAssociationPaginated($entity, string $fieldName, int $page = 1, $perPage = 50)
97
    {
98 6
        $classMetadata = $this->getEntityManager()->getClassMetadata(get_class($entity));
99 6
        $association = $classMetadata->associationMappings[$fieldName];
100 6
        $targetClass = $classMetadata->getAssociationTargetClass($fieldName);
101 6
        $inverseFieldName = $this->getInverseFieldName($fieldName, $classMetadata);
102
103 6
        $queryBuilder = $this->getEntityManager()->createQueryBuilder();
104 6
        $queryBuilder->select('association');
105 6
        $queryBuilder->from($targetClass, 'association');
106 6
        $queryBuilder->join('association.' . $inverseFieldName, 'entity');
107 6
        $queryBuilder->where('entity = :entity');
108
109 6
        if (array_key_exists('orderBy', $association)) {
110 4
            $orderBy = $association['orderBy'];
111 4
            foreach ($orderBy as $fieldName => $order) {
112 4
                $queryBuilder->addOrderBy('association.' . $fieldName, $order);
113
            }
114
        }
115
116 6
        $queryBuilder->setParameter('entity', $entity);
117
118 6
        $queryBuilder->setFirstResult(($page - 1) * $perPage);
119 6
        $queryBuilder->setMaxResults($perPage);
120
121 6
        return new Paginator($queryBuilder);
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function createAssociation($entity, string $fieldName)
128
    {
129
        $classMetadata = $this->getEntityManager()->getClassMetadata(get_class($entity));
130
        $targetClass = $classMetadata->getAssociationTargetClass($fieldName);
131
        $child = new $targetClass;
132
133
        $inverseFieldName = $this->getInverseFieldName($fieldName, $classMetadata);
134
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
135
        $propertyAccessor->setValue($child, $inverseFieldName, $entity);
136
137
        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...
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143 2
    public function addAssociation($entity, string $fieldName, $id)
144
    {
145 2
        $classMetadata = $this->getEntityManager()->getClassMetadata(get_class($entity));
146 2
        $collection = $classMetadata->isCollectionValuedAssociation($fieldName);
147 2
        $targetClass = $classMetadata->getAssociationTargetClass($fieldName);
148 2
        $inverse = $classMetadata->isAssociationInverseSide($fieldName);
149 2
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
150 2
        $reference = $this->getEntityManager()->getReference($targetClass, $id);
151
152 2
        if (!$inverse) {
153 2
            if ($collection) {
154
                /** @var Collection $collection */
155 2
                $collection = $propertyAccessor->getValue($entity, $fieldName);
156 2
                $collection->add($reference);
157
            } else {
158
                $propertyAccessor->setValue($entity, $fieldName, $reference);
159
            }
160 2
            $this->getEntityManager()->flush($entity);
161
        } else {
162
            $inverseClassMetadata = $this->getEntityManager()->getClassMetadata($targetClass);
163
            $association = $classMetadata->getAssociationMapping($fieldName);
164
            $inverseFieldName = $association['mappedBy'];
165
            $inverseCollection = $inverseClassMetadata->isCollectionValuedAssociation($inverseFieldName);
166
            if ($inverseCollection) {
167
                /** @var Collection $collection */
168
                $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 150 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...
169
                $collection->add($entity);
170
            } else {
171
                $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 150 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...
172
            }
173
            $this->getEntityManager()->flush($reference);
174
        }
175 2
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function removeAssociation($entity, string $fieldName, $id = null)
181
    {
182
        $classMetadata = $this->getEntityManager()->getClassMetadata(get_class($entity));
183
        $collection = $classMetadata->isCollectionValuedAssociation($fieldName);
184
        $targetClass = $classMetadata->getAssociationTargetClass($fieldName);
185
        $inverse = $classMetadata->isAssociationInverseSide($fieldName);
186
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
187
188
        if ($inverse) {
189
            $reference = $this->getEntityManager()->getReference($targetClass, $id);
190
            $inverseClassMetadata = $this->getEntityManager()->getClassMetadata($targetClass);
191
            $association = $classMetadata->getAssociationMapping($fieldName);
192
            $inverseFieldName = $association['mappedBy'];
193
            $inverseCollection = $inverseClassMetadata->isCollectionValuedAssociation($inverseFieldName);
194
            if ($inverseCollection) {
195
                /** @var Collection $collection */
196
                $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 189 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...
197
                $collection->removeElement($entity);
198
            } else {
199
                $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 189 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...
200
            }
201
            $this->getEntityManager()->flush($reference);
202
        } else {
203
            if ($collection) {
204
                $reference = $this->getEntityManager()->getReference($targetClass, $id);
205
                /** @var Collection $collection */
206
                $collection = $propertyAccessor->getValue($entity, $fieldName);
207
                $collection->removeElement($reference);
208
            } else {
209
                $propertyAccessor->setValue($entity, $fieldName, null);
210
            }
211
            $this->getEntityManager()->flush($entity);
212
        }
213
    }
214
215
    /**
216
     * @param string        $fieldName
217
     * @param ClassMetadata $classMetadata
218
     *
219
     * @return string
220
     */
221 6
    protected function getInverseFieldName(string $fieldName, ClassMetadata $classMetadata)
222
    {
223 6
        $association = $classMetadata->getAssociationMapping($fieldName);
224 6
        if ($classMetadata->isAssociationInverseSide($fieldName)) {
225 2
            return $association['mappedBy'];
226
        } else {
227 4
            return $association['inversedBy'];
228
        }
229
    }
230
231
    /**
232
     * @param LoggerInterface $logger
233
     */
234
    public function setLogger(LoggerInterface $logger): void
235
    {
236
        $this->logger = $logger;
237
    }
238
239
    /**
240
     * @return LoggerInterface
241
     */
242
    public function getLogger(): LoggerInterface
243
    {
244
        if (null === $this->logger) {
245
            $this->logger = new NullLogger();
246
        }
247
248
        return $this->logger;
249
    }
250
}
251