GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 46e57d...ec86a4 )
by Christian
01:49
created

SortableListener   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 162
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 27
dl 0
loc 162
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A preUpdate() 0 10 3
A prePersist() 0 4 2
A getSubscribedEvents() 0 7 1
A loadClassMetadata() 0 16 4
B movePosition() 0 30 6
A preRemove() 0 6 2
B uniquePosition() 0 10 5
B getNextPosition() 0 23 4
1
<?php
2
3
/*
4
 * (c) Christian Gripp <[email protected]>
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Core23\DoctrineExtensions\EventListener\ORM;
11
12
use Core23\DoctrineExtensions\Model\PositionAwareInterface;
13
use Core23\DoctrineExtensions\Model\Traits\SortableTrait;
14
use Doctrine\ORM\EntityManager;
15
use Doctrine\ORM\Event\LifecycleEventArgs;
16
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
17
use Doctrine\ORM\Event\PreUpdateEventArgs;
18
use Doctrine\ORM\Events;
19
use Doctrine\ORM\Mapping\ClassMetadata;
20
use Doctrine\ORM\Mapping\MappingException;
21
use Doctrine\ORM\NonUniqueResultException;
22
use Symfony\Component\PropertyAccess\PropertyAccess;
23
24
final class SortableListener extends AbstractListener
25
{
26
    /**
27
     * {@inheritdoc}
28
     */
29
    public function getSubscribedEvents()
30
    {
31
        return array(
32
            Events::prePersist,
33
            Events::preUpdate,
34
            Events::preRemove,
35
            Events::loadClassMetadata,
36
        );
37
    }
38
39
    /**
40
     * @param LifecycleEventArgs $args
41
     */
42
    public function prePersist(LifecycleEventArgs $args): void
43
    {
44
        if ($args->getEntity() instanceof PositionAwareInterface) {
45
            $this->uniquePosition($args);
46
        }
47
    }
48
49
    /**
50
     * @param PreUpdateEventArgs $args
51
     */
52
    public function preUpdate(PreUpdateEventArgs $args): void
53
    {
54
        if ($args->getEntity() instanceof PositionAwareInterface) {
55
            $position = $args->getEntity()->getPosition();
56
57
            if ($args->hasChangedField('position')) {
58
                $position = $args->getOldValue('position');
59
            }
60
61
            $this->uniquePosition($args, $position);
62
        }
63
    }
64
65
    /**
66
     * @param LifecycleEventArgs $args
67
     */
68
    public function preRemove(LifecycleEventArgs $args): void
69
    {
70
        $entity = $args->getEntity();
71
72
        if ($entity instanceof PositionAwareInterface) {
73
            $this->movePosition($args->getEntityManager(), $entity, -1);
74
        }
75
    }
76
77
    /**
78
     * @param LoadClassMetadataEventArgs $eventArgs
79
     *
80
     * @throws MappingException
81
     */
82
    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs): void
83
    {
84
        $meta = $eventArgs->getClassMetadata();
85
86
        if (!$meta instanceof ClassMetadata) {
87
            throw new \LogicException(sprintf('Class metadata was no ORM but %s', get_class($meta)));
88
        }
89
90
        if (!$this->containsTrait($meta->getReflectionClass(), SortableTrait::class)) {
91
            return;
92
        }
93
94
        if (!$meta->hasField('position')) {
95
            $meta->mapField(array(
96
                'type'      => 'integer',
97
                'fieldName' => 'position',
98
            ));
99
        }
100
    }
101
102
    /**
103
     * @param LifecycleEventArgs $args
104
     * @param int|null           $oldPosition
105
     */
106
    private function uniquePosition(LifecycleEventArgs $args, ? int $oldPosition = null): void
107
    {
108
        $entity = $args->getEntity();
109
110
        if ($entity instanceof PositionAwareInterface) {
111
            if (null === $entity->getPosition()) {
112
                $position = $this->getNextPosition($args->getEntityManager(), $entity);
113
                $entity->setPosition($position);
114
            } elseif ($oldPosition && $oldPosition !== $entity->getPosition()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $oldPosition of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
115
                $this->movePosition($args->getEntityManager(), $entity);
116
            }
117
        }
118
    }
119
120
    /**
121
     * @param EntityManager          $em
122
     * @param PositionAwareInterface $entity
123
     * @param int                    $direction
124
     */
125
    private function movePosition(EntityManager $em, PositionAwareInterface $entity, int $direction = 1): void
126
    {
127
        $uow   = $em->getUnitOfWork();
128
        $meta  = $em->getClassMetadata(get_class($entity));
129
130
        $qb = $em->createQueryBuilder()
131
            ->update($meta->getName(), 'e')
132
            ->set('e.position', 'e.position + '.$direction);
133
134
        if ($direction > 0) {
135
            $qb->andWhere('e.position <= :position')->setParameter('position', $entity->getPosition());
136
        } elseif ($direction < 0) {
137
            $qb->andWhere('e.position >= :position')->setParameter('position', $entity->getPosition());
138
        } else {
139
            return;
140
        }
141
142
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
143
144
        foreach ($entity->getPositionGroup() as $field) {
145
            $value = $propertyAccessor->getValue($entity, $field);
146
147
            if (is_object($value) && null === $uow->getSingleIdentifierValue($value)) {
148
                continue;
149
            }
150
151
            $qb->andWhere('e.'.$field.' = :'.$field)->setParameter($field, $value);
152
        }
153
154
        $qb->getQuery()->execute();
155
    }
156
157
    /**
158
     * @param EntityManager          $em
159
     * @param PositionAwareInterface $entity
160
     *
161
     * @return int
162
     */
163
    private function getNextPosition(EntityManager $em, PositionAwareInterface $entity): int
164
    {
165
        $meta = $em->getClassMetadata(get_class($entity));
166
167
        $qb = $em->createQueryBuilder()
168
            ->select('e')
169
            ->from($meta->getName(), 'e')
170
            ->addOrderBy('e.position', 'DESC')
171
            ->setMaxResults(1);
172
173
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
174
175
        foreach ($entity->getPositionGroup() as $field) {
176
            $value = $propertyAccessor->getValue($entity, $field);
177
            $qb->andWhere('e.'.$field.' = :'.$field)->setParameter($field, $value);
178
        }
179
180
        /* @var PositionAwareInterface $result */
181
        try {
182
            $result = $qb->getQuery()->getOneOrNullResult();
183
184
            return ($result ? $result->getPosition() : 0) + 1;
185
        } catch (NonUniqueResultException $ignored) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
186
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return integer. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
187
    }
188
}
189