Issues (3877)

Business/QueryString/SpecificationBuilder.php (1 issue)

Severity
1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace Spryker\Zed\Discount\Business\QueryString;
9
10
use Generated\Shared\Transfer\ClauseTransfer;
11
use Spryker\Zed\Discount\Business\Exception\QueryStringException;
12
use Spryker\Zed\Discount\Business\QueryString\Specification\MetaData\MetaDataProviderInterface;
13
use Spryker\Zed\Discount\Business\QueryString\Specification\SpecificationProviderInterface;
14
15
class SpecificationBuilder implements SpecificationBuilderInterface
16
{
17
    /**
18
     * @var string
19
     */
20
    public const OPEN_PARENTHESIS = '(';
21
22
    /**
23
     * @var string
24
     */
25
    public const CLOSE_PARENTHESIS = ')';
26
27
    /**
28
     * @var \Spryker\Zed\Discount\Business\QueryString\TokenizerInterface
29
     */
30
    protected $tokenizer;
31
32
    /**
33
     * @var \Spryker\Zed\Discount\Business\QueryString\Specification\SpecificationProviderInterface
34
     */
35
    protected $specificationProvider;
36
37
    /**
38
     * @var \Spryker\Zed\Discount\Business\QueryString\ComparatorOperatorsInterface
39
     */
40
    protected $comparatorOperators;
41
42
    /**
43
     * @var array<string>
44
     */
45
    protected $compoundComparatorExpressions = [];
46
47
    /**
48
     * @var \Spryker\Zed\Discount\Business\QueryString\ClauseValidatorInterface
49
     */
50
    protected $clauseValidator;
51
52
    /**
53
     * @var \Spryker\Zed\Discount\Business\QueryString\Specification\MetaData\MetaDataProviderInterface
54
     */
55
    protected $metaDataProvider;
56
57
    /**
58
     * @var array<string>
59
     */
60
    protected $availableFields;
61
62
    /**
63
     * @param \Spryker\Zed\Discount\Business\QueryString\TokenizerInterface $tokenizer
64
     * @param \Spryker\Zed\Discount\Business\QueryString\Specification\SpecificationProviderInterface $specificationProvider
65
     * @param \Spryker\Zed\Discount\Business\QueryString\ComparatorOperatorsInterface $comparatorOperators
66
     * @param \Spryker\Zed\Discount\Business\QueryString\ClauseValidatorInterface $clauseValidator
67
     * @param \Spryker\Zed\Discount\Business\QueryString\Specification\MetaData\MetaDataProviderInterface $metaDataProvider
68
     */
69
    public function __construct(
70
        TokenizerInterface $tokenizer,
71
        SpecificationProviderInterface $specificationProvider,
72
        ComparatorOperatorsInterface $comparatorOperators,
73
        ClauseValidatorInterface $clauseValidator,
74
        MetaDataProviderInterface $metaDataProvider
75
    ) {
76
        $this->tokenizer = $tokenizer;
77
        $this->specificationProvider = $specificationProvider;
78
        $this->comparatorOperators = $comparatorOperators;
79
        $this->clauseValidator = $clauseValidator;
80
        $this->metaDataProvider = $metaDataProvider;
81
    }
82
83
    /**
84
     * @param string $queryString
85
     *
86
     * @return \Spryker\Zed\Discount\Business\QueryString\Specification\CollectorSpecification\CollectorSpecificationInterface|\Spryker\Zed\Discount\Business\QueryString\Specification\DecisionRuleSpecification\DecisionRuleSpecificationInterface
87
     */
88
    public function buildFromQueryString($queryString)
89
    {
90
        $tokens = $this->tokenizer->tokenizeQueryString($queryString);
91
92
        return $this->buildTree($tokens);
93
    }
94
95
    /**
96
     * @param array<string> $tokens
97
     * @param int $currentTokenIndex
98
     * @param int $parenthesisDepth
99
     *
100
     * @throws \Spryker\Zed\Discount\Business\Exception\QueryStringException
101
     *
102
     * @return \Spryker\Zed\Discount\Business\QueryString\Specification\CollectorSpecification\CollectorSpecificationInterface|\Spryker\Zed\Discount\Business\QueryString\Specification\DecisionRuleSpecification\DecisionRuleSpecificationInterface
103
     */
104
    protected function buildTree(array $tokens, &$currentTokenIndex = 0, &$parenthesisDepth = 0)
105
    {
106
        $leftNode = null;
107
        $compositeNode = null;
108
        $lastLogicalComparator = null;
109
        $clauseTransfer = new ClauseTransfer();
110
111
        $countTokens = count($tokens);
112
113
        while ($countTokens > $currentTokenIndex) {
114
            $token = $this->cleanToken($tokens[$currentTokenIndex]);
115
116
            switch (true) {
117
                case $token === static::OPEN_PARENTHESIS:
118
                    $parenthesisDepth++;
119
                    $currentTokenIndex++;
120
                    $childTree = $this->buildTree($tokens, $currentTokenIndex, $parenthesisDepth);
121
122
                    if ($leftNode === null) {
123
                        $leftNode = $childTree;
124
                    } else {
125
                        $compositeNode = $this->createCompositeNode($lastLogicalComparator, $leftNode, $childTree, $compositeNode);
126
                    }
127
128
                    break;
129
                case $token === static::CLOSE_PARENTHESIS:
130
                    $parenthesisDepth--;
131
132
                    if ($compositeNode == null) {
133
                        return $leftNode;
134
                    }
135
136
                    return $compositeNode;
137
                case $this->isLogicalComparator($token):
138
                    $lastLogicalComparator = $token;
139
140
                    break;
141
                case $this->isField($token):
142
                    $clauseTransfer = new ClauseTransfer();
143
                    $this->setClauseField($token, $clauseTransfer);
144
145
                    break;
146
                case $this->isComparator($token):
147
                    if ($clauseTransfer->getOperator()) {
148
                        $token = $clauseTransfer->getOperator() . ' ' . $token;
149
                    }
150
151
                    $clauseTransfer->setOperator($token);
152
153
                    break;
154
                case $this->isValue($token):
155
                     $value = $this->clearQuotes($token);
156
                     $clauseTransfer->setValue($value);
157
158
                     $this->clauseValidator->validateClause($clauseTransfer);
159
160
                    if ($leftNode === null) {
161
                        $leftNode = $this->specificationProvider->getSpecificationContext($clauseTransfer);
162
                    } else {
163
                        $rightNode = $this->specificationProvider->getSpecificationContext($clauseTransfer);
164
                        $compositeNode = $this->createCompositeNode($lastLogicalComparator, $leftNode, $rightNode, $compositeNode);
165
                    }
166
167
                    break;
168
                default:
169
                    throw new QueryStringException(
170
                        sprintf(
171
                            "Token '%s' could not be identified by specification builder.",
172
                            $token,
173
                        ),
174
                    );
175
            }
176
177
            $currentTokenIndex++;
178
        }
179
180
        if ($parenthesisDepth !== 0) {
181
            throw new QueryStringException('Parenthesis not matching.');
182
        }
183
184
        if ($compositeNode == null) {
0 ignored issues
show
The condition $compositeNode == null is always true.
Loading history...
185
            return $leftNode;
186
        }
187
188
        return $compositeNode;
189
    }
190
191
    /**
192
     * @return array<string>
193
     */
194
    protected function getCompoundComparatorExpressions()
195
    {
196
        if (!$this->compoundComparatorExpressions) {
197
            $this->compoundComparatorExpressions = $this->comparatorOperators->getCompoundComparatorExpressions();
198
        }
199
200
        return $this->compoundComparatorExpressions;
201
    }
202
203
    /**
204
     * @param string $logicalComparator
205
     * @param \Spryker\Zed\Discount\Business\QueryString\Specification\CollectorSpecification\CollectorSpecificationInterface|\Spryker\Zed\Discount\Business\QueryString\Specification\DecisionRuleSpecification\DecisionRuleSpecificationInterface $leftNode
206
     * @param \Spryker\Zed\Discount\Business\QueryString\Specification\CollectorSpecification\CollectorSpecificationInterface|\Spryker\Zed\Discount\Business\QueryString\Specification\DecisionRuleSpecification\DecisionRuleSpecificationInterface $rightNode
207
     * @param \Spryker\Zed\Discount\Business\QueryString\Specification\CollectorSpecification\CollectorSpecificationInterface|\Spryker\Zed\Discount\Business\QueryString\Specification\DecisionRuleSpecification\DecisionRuleSpecificationInterface $compositeNode
208
     *
209
     * @return \Spryker\Zed\Discount\Business\QueryString\Specification\CollectorSpecification\CollectorSpecificationInterface|\Spryker\Zed\Discount\Business\QueryString\Specification\DecisionRuleSpecification\DecisionRuleSpecificationInterface
210
     */
211
    protected function createCompositeNode(
212
        $logicalComparator,
213
        $leftNode,
214
        $rightNode,
215
        $compositeNode
216
    ) {
217
        if ($compositeNode !== null) {
218
            $leftNode = $compositeNode;
219
        }
220
221
        if ($logicalComparator === LogicalComparators::COMPARATOR_AND) {
222
            $compositeNode = $this->specificationProvider->createAnd($leftNode, $rightNode);
223
        } elseif ($logicalComparator === LogicalComparators::COMPARATOR_OR) {
224
            $compositeNode = $this->specificationProvider->createOr($leftNode, $rightNode);
225
        }
226
227
        return $compositeNode;
228
    }
229
230
    /**
231
     * @param string $token
232
     *
233
     * @return string
234
     */
235
    protected function cleanToken($token)
236
    {
237
        return strtolower($token);
238
    }
239
240
    /**
241
     * @param string $fieldName
242
     * @param \Generated\Shared\Transfer\ClauseTransfer $clauseTransfer
243
     *
244
     * @return void
245
     */
246
    protected function setClauseField($fieldName, ClauseTransfer $clauseTransfer)
247
    {
248
        if (strpos($fieldName, '.') !== false) {
249
            [$fieldName, $attribute] = explode('.', $fieldName);
250
            $clauseTransfer->setAttribute($attribute);
251
        }
252
253
        $clauseTransfer->setField(trim($fieldName));
254
    }
255
256
    /**
257
     * @param string $value
258
     *
259
     * @return string
260
     */
261
    protected function clearQuotes($value)
262
    {
263
        return preg_replace('/["\']/', '', $value);
264
    }
265
266
    /**
267
     * @param string $token
268
     *
269
     * @return bool
270
     */
271
    protected function isField($token)
272
    {
273
        return $this->metaDataProvider->isFieldAvailable($token);
274
    }
275
276
    /**
277
     * @param string $token
278
     *
279
     * @return bool
280
     */
281
    protected function isLogicalComparator($token)
282
    {
283
        return in_array($token, [LogicalComparators::COMPARATOR_AND, LogicalComparators::COMPARATOR_OR], true);
284
    }
285
286
    /**
287
     * @param string $token
288
     *
289
     * @return bool
290
     */
291
    protected function isComparator($token)
292
    {
293
        if (in_array($token, $this->getCompoundComparatorExpressions(), true)) {
294
            return true;
295
        }
296
297
        $clauseTransfer = new ClauseTransfer();
298
        $clauseTransfer->setOperator($token);
299
300
        return $this->comparatorOperators->isExistingComparator($clauseTransfer);
301
    }
302
303
    /**
304
     * @param string $token
305
     *
306
     * @return bool
307
     */
308
    protected function isValue($token)
309
    {
310
        $first = substr($token, 0, 1);
311
        $last = substr($token, -1);
312
313
        if ($first === '"' && $last === '"') {
314
            return true;
315
        }
316
317
        if ($first === "'" && $last === "'") {
318
            return true;
319
        }
320
321
        return false;
322
    }
323
}
324