Completed
Pull Request — master (#33)
by Daniel
05:18
created

QueryBuilderConverter::getQuery()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 63
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 63
rs 6.8825
cc 8
eloc 36
nc 12
nop 1

How to fix   Long Method   

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
/*
4
 * This file is part of Sulu.
5
 *
6
 * (c) MASSIVE ART WebServices GmbH
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Sulu\Component\DocumentManager\Query;
13
14
use Doctrine\ODM\PHPCR\Query\Builder\AbstractNode as QBConstants;
15
use Doctrine\ODM\PHPCR\Query\Builder\ConverterPhpcr;
16
use Doctrine\ODM\PHPCR\Query\Builder\OperandDynamicField;
17
use Doctrine\ODM\PHPCR\Query\Builder\QueryBuilder;
18
use Doctrine\ODM\PHPCR\Query\Builder\SourceDocument;
19
use PHPCR\SessionInterface;
20
use Sulu\Component\DocumentManager\DocumentStrategyInterface;
21
use Sulu\Component\DocumentManager\MetadataFactoryInterface;
22
use Sulu\Component\DocumentManager\PropertyEncoder;
23
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
24
25
/**
26
 * Class which converts a Builder tree to a PHPCR Query.
27
 */
28
class QueryBuilderConverter extends ConverterPhpcr
29
{
30
    /**
31
     * @var EventDispatcherInterface
32
     */
33
    private $eventDispatcher;
34
35
    /**
36
     * @var Metadata[]
37
     */
38
    protected $documentMetadata = [];
39
40
    /**
41
     * @param PropertyEncoder
42
     */
43
    protected $encoder;
44
45
    /**
46
     * @param DocumentStrategyInterface
47
     */
48
    private $strategy;
49
50
    /**
51
     * @param SessionInterface $session
52
     * @param EventDispatcherInterface $eventDispatcher
53
     * @param MetadataFactoryInterface $metadataFactory
54
     * @param PropertyEncoder $encoder
55
     */
56
    public function __construct(
57
        SessionInterface $session,
58
        EventDispatcherInterface $eventDispatcher,
59
        MetadataFactoryInterface $metadataFactory,
60
        PropertyEncoder $encoder,
61
        DocumentStrategyInterface $strategy
62
    ) {
63
        $this->eventDispatcher = $eventDispatcher;
64
        $this->qomf = $session->getWorkspace()->getQueryManager()->getQOMFactory();
65
        $this->metadataFactory = $metadataFactory;
0 ignored issues
show
Bug introduced by
The property metadataFactory does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
66
        $this->encoder = $encoder;
67
        $this->strategy = $strategy;
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function getQuery(QueryBuilder $builder)
74
    {
75
        $this->documentMetadata = [];
76
        $this->sourceDocumentNodes = [];
77
        $this->constraint = null;
78
79
        $this->locale = $builder->getLocale();
80
81
        if (!$this->locale) {
82
            throw new \InvalidArgumentException(sprintf(
83
                'No locale specified'
84
            ));
85
        }
86
87
        $from = $builder->getChildrenOfType(
88
            QBConstants::NT_FROM
89
        );
90
91
        if (!$from) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $from of type Doctrine\ODM\PHPCR\Query\Builder\AbstractNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
92
            throw new RuntimeException(
93
                'No From (source) node in query'
94
            );
95
        }
96
97
        $dispatches = [
98
            QBConstants::NT_FROM,
99
            QBConstants::NT_SELECT,
100
            QBConstants::NT_WHERE,
101
            QBConstants::NT_ORDER_BY,
102
        ];
103
104
        foreach ($dispatches as $dispatchType) {
105
            $this->dispatchMany($builder->getChildrenOfType($dispatchType));
106
        }
107
108
        if (count($this->sourceDocumentNodes) > 1 && null === $builder->getPrimaryAlias()) {
109
            throw new \InvalidArgumentException(
110
                'You must specify a primary alias when selecting from multiple document sources ' .
111
                'e.g. $qb->from(\'a\') ...'
112
            );
113
        }
114
115
        $this->applySourceConstraints($builder);
116
117
        $phpcrQuery = $this->qomf->createQuery(
118
            $this->from,
119
            $this->constraint,
120
            $this->orderings,
121
            $this->columns
122
        );
123
124
        $query = new Query($phpcrQuery, $this->eventDispatcher, $this->locale, [], $builder->getPrimaryAlias());
125
126
        if ($firstResult = $builder->getFirstResult()) {
127
            $query->setFirstResult($firstResult);
128
        }
129
130
        if ($maxResults = $builder->getMaxResults()) {
131
            $query->setMaxResults($maxResults);
132
        }
133
134
        return $query;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $query; (Sulu\Component\DocumentManager\Query\Query) is incompatible with the return type declared by the interface Doctrine\ODM\PHPCR\Query...rterInterface::getQuery of type Doctrine\ODM\PHPCR\Query\Builder\Query.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    protected function applySourceConstraints(QueryBuilder $builder)
0 ignored issues
show
Unused Code introduced by
The parameter $builder is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
141
    {
142
        foreach ($this->sourceDocumentNodes as $sourceDocumentNode) {
143
            $metadata = $this->getMetadata($sourceDocumentNode->getDocumentFqn());
144
            $constraint = $this->strategy->createSourceConstraint(
145
                $this->qomf,
146
                $sourceDocumentNode->getAlias(),
147
                $metadata->getClass()
148
            );
149
150
            if (null === $constraint) {
151
                continue;
152
            }
153
154
            if ($this->constraint) {
155
                $this->constraint = $this->qomf->andConstraint(
156
                    $this->constraint,
157
                    $constraint
158
                );
159
                continue;
160
            }
161
162
            $this->constraint = $constraint;
0 ignored issues
show
Documentation Bug introduced by
It seems like $constraint of type object<Sulu\Component\Do...OM\ConstraintInterface> is incompatible with the declared type object<PHPCR\Query\QOM\ConstraintInterface> of property $constraint.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
163
        }
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169
    protected function walkSourceDocument(SourceDocument $node)
170
    {
171
        $alias = $node->getAlias();
172
        $documentFqn = $node->getDocumentFqn();
173
174
        $this->sourceDocumentNodes[$alias] = $node;
175
176
        $this->documentMetadata[$alias] = $this->getMetadata($documentFqn);
177
178
        $alias = $this->qomf->selector(
179
            $alias,
180
            $this->strategy->getPrimaryNodeType($documentFqn)
181
        );
182
183
        return $alias;
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189
    protected function walkOperandDynamicField(OperandDynamicField $node)
190
    {
191
        $alias = $node->getAlias();
192
        $field = $node->getField();
193
194
        list($alias, $phpcrProperty) = $this->getPhpcrProperty(
195
            $alias,
196
            $field
197
        );
198
199
        $operand = $this->qomf->propertyValue(
200
            $alias,
201
            $phpcrProperty
202
        );
203
204
        return $operand;
205
    }
206
207
    /**
208
     * Return the PHPCR property name and alias for the given document
209
     * field name and query alias.
210
     *
211
     * @param string $alias As specified in the query source.
212
     * @param string $field Name of the document field
213
     *
214
     * @return array {
215
     *
216
     *     @var string Element is the real alias to use, second element is
217
     *     @var string the property name
218
     * }
219
     */
220
    protected function getPhpcrProperty($alias, $field)
221
    {
222
        if (!isset($this->documentMetadata[$alias])) {
223
            throw new \InvalidArgumentException(sprintf(
224
                'Unknown document alias "%s". Known aliases: "%s"',
225
                $alias,
226
                implode('", "', array_keys($this->documentMetadata))
227
            ));
228
        }
229
230
        $metadata = $this->documentMetadata[$alias];
231
        $fieldMapping = $metadata->getFieldMapping($field);
232
        $phpcrName = $this->encoder->encode(
233
            $fieldMapping['encoding'],
234
            $fieldMapping['property'],
235
            $this->locale
236
        );
237
238
        return [$alias, $phpcrName];
239
    }
240
241
    /**
242
     * Return either the metadata for the fqn of the document, or the alias.
243
     *
244
     * @param string $documentFqn Document FQN or alias
245
     *
246
     * @return Metadata
247
     */
248
    protected function getMetadata($documentFqn)
249
    {
250
        if ($this->metadataFactory->hasAlias($documentFqn)) {
251
            return $this->metadataFactory->getMetadataForAlias($documentFqn);
252
        }
253
254
        return $this->metadataFactory->getMetadataForClass($documentFqn);
255
    }
256
}
257