Completed
Pull Request — master (#309)
by Leny
10:12 queued 04:08
created

QueryHelper::buildWithSubQuery()   D

Complexity

Conditions 20
Paths 109

Size

Total Lines 78
Code Lines 41

Duplication

Lines 10
Ratio 12.82 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 10
loc 78
rs 4.948
cc 20
eloc 41
nc 109
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Victoire\Bundle\QueryBundle\Helper;
4
5
use Doctrine\Common\Annotations\Reader;
6
use Doctrine\ORM\EntityManager;
7
use Doctrine\ORM\Mapping\ManyToMany;
8
use Doctrine\ORM\Mapping\ManyToOne;
9
use Doctrine\ORM\Mapping\OneToMany;
10
use Doctrine\ORM\Mapping\OneToOne;
11
use Doctrine\ORM\QueryBuilder;
12
use FOS\UserBundle\Model\UserInterface;
13
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
14
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
15
use Victoire\Bundle\BusinessEntityBundle\Helper\BusinessEntityHelper;
16
use Victoire\Bundle\BusinessPageBundle\Entity\BusinessPage;
17
use Victoire\Bundle\CoreBundle\Helper\CurrentViewHelper;
18
19
/**
20
 * The QueryHelper helps to build query in Victoire's components
21
 * ref: victoire_query.query_helper.
22
 */
23
class QueryHelper
24
{
25
    protected $businessEntityHelper = null;
26
    protected $currentView;
27
    protected $reader;
28
    protected $tokenStorage;
29
30
    /**
31
     * Constructor.
32
     *
33
     * @param BusinessEntityHelper $businessEntityHelper
34
     * @param CurrentViewHelper    $currentView
35
     * @param Reader               $reader
36
     */
37
    public function __construct(BusinessEntityHelper $businessEntityHelper, CurrentViewHelper $currentView, Reader $reader, TokenStorage $tokenStorage)
38
    {
39
        $this->businessEntityHelper = $businessEntityHelper;
40
        $this->currentView = $currentView;
41
        $this->reader = $reader;
42
        $this->tokenStorage = $tokenStorage;
43
    }
44
45
    /**
46
     * Get the query builder base. This makes a "select  from item XXX"
47
     * use the item for doing the left join or where dql.
48
     *
49
     * @param \Victoire\Bundle\BusinessPageBundle\Entity\BusinessTemplate $containerEntity
50
     *
51
     * @throws \Exception
52
     *
53
     * @return QueryBuilder
54
     */
55
    public function getQueryBuilder($containerEntity, EntityManager $em)
56
    {
57
        if ($containerEntity === null) {
58
            throw new \Exception('The container entity parameter must not be null.');
59
        }
60
61
        //verify that the object has the query trait
62
        $this->checkObjectHasQueryTrait($containerEntity);
63
64
        //the business name of the container entity
65
        $businessEntityId = $containerEntity->getBusinessEntityId();
66
67
        //test that there is a business entity name
68
        if ($businessEntityId === null || $businessEntityId === '') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $businessEntityId (integer) and '' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
69
            $containerId = $containerEntity->getId();
70
            throw new \Exception('The container entity ['.$containerId.'] does not have any businessEntityId.');
71
        }
72
73
        //the business class of the container entity
74
        $businessEntity = $this->businessEntityHelper->findById(strtolower($businessEntityId));
75
76
        //test that there was a businessEntity
77
        if ($businessEntity === null) {
78
            throw new \Exception('The business entity was not found for the id:['.$businessEntityId.']');
79
        }
80
81
        $businessClass = $businessEntity->getClass();
82
83
        $itemsQueryBuilder = $em
84
            ->createQueryBuilder()
85
            ->select('main_item')
86
            ->from($businessClass, 'main_item');
87
88
        $refClass = new $businessClass();
89
        if (method_exists($refClass, 'getDeletedAt')) {
90
            $itemsQueryBuilder->where('main_item.deletedAt IS NULL');
91
        }
92
93
        return $itemsQueryBuilder;
94
    }
95
96
    /**
97
     * Check that the object is not null and has the query trait.
98
     *
99
     * @param \Victoire\Bundle\BusinessPageBundle\Entity\BusinessTemplate $containerEntity
100
     *
101
     * @throws \Exception
102
     */
103
    protected function checkObjectHasQueryTrait($containerEntity)
104
    {
105
        if ($containerEntity === null) {
106
            throw new \Exception('The container entity parameter must not be null.');
107
        }
108
109
        //test that the containerEntity has the trait
110
        if (!method_exists($containerEntity, 'getQuery') || !method_exists($containerEntity, 'getBusinessEntityId')) {
111
            throw new \Exception('The object '.get_class($containerEntity).' does not have the QueryTrait.');
112
        }
113
    }
114
115
    /**
116
     * Get the results from the sql after adding the.
117
     *
118
     * @param mixed        $containerEntity
119
     * @param QueryBuilder $itemsQueryBuilder
120
     *
121
     * @throws \Exception
122
     *
123
     * @return QueryBuilder The QB to list of objects
124
     */
125
    public function buildWithSubQuery($containerEntity, QueryBuilder $itemsQueryBuilder, EntityManager $em)
126
    {
127
        //test the container entity
128
        if ($containerEntity === null) {
129
            throw new \Exception('The container entity parameter must not be null.');
130
        }
131
132
        //verify that the object has the query trait
133
        //@todo please use an interface and cast with it in the method signature
134
        $this->checkObjectHasQueryTrait($containerEntity);
135
136
        //get the query of the container entity
137
        $query = $containerEntity->getQuery();
138
        if (method_exists($containerEntity, 'additionnalQueryPart')) {
139
            $query = $containerEntity->additionnalQueryPart();
140
        }
141
142
        if ($query !== '' && $query !== null) {
143
            $subQuery = $em->createQueryBuilder()
144
                                ->select('item.id')
145
                                ->from($itemsQueryBuilder->getRootEntities()[0], 'item');
146
147
            $itemsQueryBuilder
148
                ->andWhere('main_item.id IN ('.$subQuery->getQuery()->getDql().' '.$query.')');
149
        }
150
151
        //Add ORDER BY if set
152
        if (method_exists($containerEntity, 'getOrderBy')) {
153
            $orderBy = json_decode($containerEntity->getOrderBy(), true);
154
            if ($orderBy) {
155
                foreach ($orderBy as $addOrderBy) {
156
                    $reflectionClass = new \ReflectionClass($itemsQueryBuilder->getRootEntities()[0]);
157
                    $reflectionProperty = $reflectionClass->getProperty($addOrderBy['by']);
158
159
                    //If ordering field is an association, treat it as a boolean
160
                    if ($this->isAssociationField($reflectionProperty)) {
161
                        $itemsQueryBuilder->addSelect('CASE WHEN main_item.'.$addOrderBy['by'].' IS NULL THEN 0 ELSE 1 END AS HIDDEN caseOrder');
162
                        $itemsQueryBuilder->addOrderBy('caseOrder', $addOrderBy['order']);
163
                    } else {
164
                        $itemsQueryBuilder->addOrderBy('main_item.'.$addOrderBy['by'], $addOrderBy['order']);
165
                    }
166
                }
167
            }
168
        }
169
170
        $currentView = $this->currentView;
171
172
        // If the current page is a BEP, we parse all its properties and inject them as query parameters
173
        if ($currentView() && $currentView() instanceof BusinessPage && null !== $currentEntity = $currentView()->getBusinessEntity()) {
174
175
            // NEW
176
            $metadatas = $em->getClassMetadata(get_class($currentEntity));
177 View Code Duplication
            foreach ($metadatas->fieldMappings as $fieldName => $field) {
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...
178
                if (strpos($query, ':'.$fieldName) !== false) {
179
                    $itemsQueryBuilder->setParameter($fieldName, $metadatas->getFieldValue($currentEntity, $fieldName));
180
                }
181
            }
182 View Code Duplication
            foreach ($metadatas->associationMappings as $fieldName => $field) {
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...
183
                if (strpos($query, ':'.$fieldName) !== false) {
184
                    $itemsQueryBuilder->setParameter($fieldName, $metadatas->getFieldValue($currentEntity, $fieldName)->getId());
185
                }
186
            }
187
188
            if (strpos($query, ':currentEntity') !== false) {
189
                $itemsQueryBuilder->setParameter('currentEntity', $currentEntity->getId());
190
            }
191
        }
192
193
        if (strpos($query, ':currentUser') !== false && is_object($this->getCurrentUser())) {
194
            if (is_object($this->getCurrentUser())) {
195
                $itemsQueryBuilder->setParameter('currentUser', $this->getCurrentUser()->getId());
196
            } else {
197
                throw new AccessDeniedException();
198
            }
199
        }
200
201
        return $itemsQueryBuilder;
202
    }
203
204
    /**
205
     * Check if field is a OneToOne, OneToMany, ManyToOne or ManyToMany association.
206
     *
207
     * @param \ReflectionProperty $field
208
     *
209
     * @return bool
210
     */
211
    private function isAssociationField(\ReflectionProperty $field)
212
    {
213
        $annotations = $this->reader->getPropertyAnnotations($field);
214
        foreach ($annotations as $key => $annotationObj) {
215
            if ($annotationObj instanceof OneToOne || $annotationObj instanceof OneToMany || $annotationObj instanceof ManyToOne || $annotationObj instanceof ManyToMany) {
216
                return true;
217
            }
218
        }
219
220
        return false;
221
    }
222
223
    /**
224
     * @return UserInterface|string
225
     */
226
    public function getCurrentUser()
227
    {
228
        return $this->tokenStorage->getToken()->getUser();
229
    }
230
}
231