Completed
Push — master ( 3460d4...501fc3 )
by Paweł
76:02 queued 29:07
created

MenuItemRepository::findChildrenAsTree()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 10
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Superdesk Web Publisher Menu Bundle.
7
 *
8
 * Copyright 2016 Sourcefabric z.ú. and contributors.
9
 *
10
 * For the full copyright and license information, please see the
11
 * AUTHORS and LICENSE files distributed with this source code.
12
 *
13
 * @copyright 2016 Sourcefabric z.ú
14
 * @license http://www.superdesk.org/license
15
 */
16
17
namespace SWP\Bundle\MenuBundle\Doctrine\ORM;
18
19
use Doctrine\ORM\EntityManager;
20
use Doctrine\ORM\Mapping\ClassMetadata;
21
use Gedmo\Exception\UnexpectedValueException;
22
use Gedmo\Tool\Wrapper\EntityWrapper;
23
use Gedmo\Tree\TreeListener;
24
use SWP\Bundle\MenuBundle\Doctrine\MenuItemRepositoryInterface;
25
use SWP\Bundle\MenuBundle\Model\MenuItemInterface;
26
use SWP\Bundle\StorageBundle\Doctrine\ORM\EntityRepository;
27
use SWP\Component\Common\Pagination\PaginationData;
28
29
class MenuItemRepository extends EntityRepository implements MenuItemRepositoryInterface
30
{
31
    /**
32
     * Tree listener on event manager.
33
     *
34
     * @var TreeListener
35
     */
36
    protected $treeListener;
37
38
    /**
39
     * MenuItemRepository constructor.
40
     *
41
     * @param EntityManager $em
42
     * @param ClassMetadata $class
43
     */
44 81
    public function __construct(EntityManager $em, ClassMetadata $class)
45
    {
46 81
        parent::__construct($em, $class);
47 81
        $treeListener = null;
48 81
        foreach ($em->getEventManager()->getListeners() as $listeners) {
49 81
            foreach ($listeners as $listener) {
50 81
                if ($listener instanceof TreeListener) {
51 81
                    $treeListener = $listener;
52 81
                    break;
53
                }
54
            }
55 81
            if ($treeListener) {
56 81
                break;
57
            }
58
        }
59
60 81
        if (is_null($treeListener)) {
61
            throw new \Gedmo\Exception\InvalidMappingException('Tree listener was not found on your entity manager, it must be hooked into the event manager');
62
        }
63
64 81
        $this->treeListener = $treeListener;
65 81
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function getOneMenuItemByName(string $name)
71
    {
72
        return $this->createQueryBuilder('m')
73
            ->where('m.name = :name')
74
            ->setParameter('name', $name)
75
            ->getQuery()
76
            ->getOneOrNullResult();
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82 11
    public function getOneMenuItemById(int $id)
83
    {
84 11
        return $this->createQueryBuilder('m')
85 11
            ->where('m.id = :id')
86 11
            ->setParameter('id', $id)
87 11
            ->getQuery()
88 11
            ->getOneOrNullResult();
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 3
    public function findChildrenAsTree(MenuItemInterface $menuItem)
95
    {
96 3
        $queryBuilder = $this->createQueryBuilder('m');
97
        $queryBuilder
98 3
            ->addSelect('children')
99 3
            ->leftJoin('m.children', 'children')
100 3
            ->where('m.parent = :parent')
101 3
            ->addOrderBy('m.root')
102 3
            ->setParameter('parent', $menuItem)
103 3
            ->orderBy('m.lft', 'asc')
104
        ;
105
106 3
        return $this->getPaginator($queryBuilder, new PaginationData());
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112 2
    public function findRootNodes()
113
    {
114 2
        $queryBuilder = $this->createQueryBuilder('m');
115
        $queryBuilder
116 2
            ->addSelect('children')
117 2
            ->leftJoin('m.children', 'children')
118 2
            ->where($queryBuilder->expr()->isNull('m.parent'))
119 2
            ->orderBy('m.id', 'asc');
120
121 2
        return $this->getPaginator($queryBuilder, new PaginationData());
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127 3
    public function findChildByParentAndPosition(MenuItemInterface $parent, int $position)
128
    {
129 3
        return $this->createQueryBuilder('m')
130 3
            ->where('m.parent = :id')
131 3
            ->andWhere('m.position = :position')
132 3
            ->setParameters([
133 3
                'id' => $parent->getId(),
134 3
                'position' => $position,
135
            ])
136 3
            ->getQuery()
137 3
            ->getOneOrNullResult();
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143 1
    public function persistAsFirstChildOf(MenuItemInterface $node, MenuItemInterface $parent)
144
    {
145 1
        $wrapped = new EntityWrapper($node, $this->_em);
146 1
        $meta = $this->getClassMetadata();
147 1
        $config = $this->treeListener->getConfiguration($this->_em, $meta->name);
148
149 1
        $wrapped->setPropertyValue($config['parent'], $parent);
150
151 1
        $wrapped->setPropertyValue($config['left'], 0);
152 1
        $oid = spl_object_hash($node);
153 1
        $this->treeListener
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Gedmo\Tree\Strategy as the method setNodePosition() does only exist in the following implementations of said interface: Gedmo\Tree\Strategy\ORM\Nested.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
154 1
            ->getStrategy($this->_em, $meta->name)
155 1
            ->setNodePosition($oid, 'FirstChild')
156
        ;
157
158 1
        $this->_em->persist($node);
159 1
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 2
    public function persistAsNextSiblingOf(MenuItemInterface $node, MenuItemInterface $sibling)
165
    {
166 2
        $wrapped = new EntityWrapper($node, $this->_em);
167 2
        $meta = $this->getClassMetadata();
168 2
        $config = $this->treeListener->getConfiguration($this->_em, $meta->name);
169
170 2
        $wrappedSibling = new EntityWrapper($sibling, $this->_em);
171 2
        $newParent = $wrappedSibling->getPropertyValue($config['parent']);
172 2
        if (null === $newParent && isset($config['root'])) {
173
            throw new UnexpectedValueException('Cannot persist sibling for a root node, tree operation is not possible');
174
        }
175
176 2
        $node->sibling = $sibling;
0 ignored issues
show
Bug introduced by
Accessing sibling on the interface SWP\Bundle\MenuBundle\Model\MenuItemInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
177 2
        $sibling = $newParent;
178
179 2
        $wrapped->setPropertyValue($config['parent'], $sibling);
180
181 2
        $wrapped->setPropertyValue($config['left'], 0);
182 2
        $oid = spl_object_hash($node);
183 2
        $this->treeListener
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Gedmo\Tree\Strategy as the method setNodePosition() does only exist in the following implementations of said interface: Gedmo\Tree\Strategy\ORM\Nested.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
184 2
            ->getStrategy($this->_em, $meta->name)
185 2
            ->setNodePosition($oid, 'NextSibling')
186
        ;
187
188 2
        $this->_em->persist($node);
189
190 2
        return $this;
191
    }
192
}
193