Completed
Branch conditions-refactoring (4b1dba)
by Romain
03:17
created

ConditionParserOld::getGroupNode()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 20
nc 8
nop 1
1
<?php
2
/*
3
 * 2016 Romain CANON <[email protected]>
4
 *
5
 * This file is part of the TYPO3 Formz project.
6
 * It is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU General Public License, either
8
 * version 3 of the License, or any later version.
9
 *
10
 * For the full copyright and license information, see:
11
 * http://www.gnu.org/licenses/gpl-3.0.html
12
 */
13
14
namespace Romm\Formz\Condition\Parser;
15
16
use Romm\Formz\Condition\ConditionTree;
17
use Romm\Formz\Condition\Node\BooleanNode;
18
use Romm\Formz\Condition\Node\ConditionNode;
19
use Romm\Formz\Condition\Node\NodeInterface;
20
use Romm\Formz\Condition\Node\NullNode;
21
use Romm\Formz\Configuration\Form\Condition\Activation\ActivationInterface;
22
use TYPO3\CMS\Core\SingletonInterface;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
use TYPO3\CMS\Extbase\Error\Error;
25
use TYPO3\CMS\Extbase\Error\Result;
26
27
/**
28
 * @todo
29
 *
30
 * A parser capable of parsing a validation condition string from a field
31
 * configuration, by creating a tree containing nodes which represents the
32
 * logical operations.
33
 *
34
 * Parsing errors are handled, and stored in `$this->result`. When a condition
35
 * has been parser, it is highly recommended to check if the result contains
36
 * errors before using the tree.
37
 *
38
 * Below is a list of what is currently supported by the parser:
39
 *  - Logical AND: defined by `&&`, it is no more than a logical "and" operator.
40
 *  - Logical OR: defined by `||`, same as above.
41
 *  - Operation groups: you can group several operation between parenthesis:
42
 *    `(...)`.
43
 *  - Condition names: represented by the items names in the `Activation`
44
 *    instance, there real meaning is a boolean result.
45
 */
46
class ConditionParserOld implements SingletonInterface
47
{
48
    const LOGICAL_AND = '&&';
49
    const LOGICAL_OR = '||';
50
51
    /**
52
     * @var Result
53
     */
54
    private $result;
55
56
    /**
57
     * @var ActivationInterface
58
     */
59
    private $condition;
60
61
    /**
62
     * @return ConditionParser
63
     */
64
    public static function get()
65
    {
66
        return GeneralUtility::makeInstance(self::class);
67
    }
68
69
    /**
70
     * @param ActivationInterface $condition
71
     * @return ConditionTree
72
     */
73
    public function parse(ActivationInterface $condition)
74
    {
75
        $this->condition = $condition;
76
        $this->result = GeneralUtility::makeInstance(Result::class);
77
78
        $splitCondition = $this->splitConditionExpression($condition->getCondition());
79
80
        $rootNode = $this->getNodeRecursive($splitCondition);
81
        $rootNode = $rootNode ?: NullNode::get();
82
83
        return GeneralUtility::makeInstance(ConditionTree::class, $rootNode, $this->result);
84
    }
85
86
    /**
87
     * Recursive function to convert an array of condition data to a tree of
88
     * nodes.
89
     *
90
     * @param array $splitCondition
91
     * @return NodeInterface|null
92
     */
93
    protected function getNodeRecursive(array &$splitCondition)
94
    {
95
        $node = null;
96
        $leftNode = null;
97
        $operator = null;
98
        $expression = $splitCondition;
99
        $lastOr = null;
100
101
        while (false === empty($expression)) {
102
            if ($this->result->hasErrors()) {
103
                break;
104
            }
105
106
            switch ($expression[0]) {
107
                case ')':
108
                    $this->result->addError(new Error('Parenthesis closes invalid group.',
109
                        1457969163));
110
                    break;
111
                case '(':
112
                    $groupNode = $this->getGroupNode($expression);
113
                    $expression = array_slice($expression,
114
                        count($groupNode) + 2);
115
                    $node = $this->getNodeRecursive($groupNode);
116
                    break;
117
                case ConditionParser::LOGICAL_OR:
118
                case ConditionParser::LOGICAL_AND:
119
                    if (null === $node) {
120
                        $this->result->addError(new Error('Logical operator must be preceded by a valid operation.',
121
                            1457544986));
122
                    } else {
123
                        $operator = $expression[0];
124
125
                        if (ConditionParser::LOGICAL_OR === $operator) {
126
                            if (null !== $lastOr) {
127
                                $node = $this->getNode(
128
                                    BooleanNode::class,
129
                                    [$lastOr, $node, $operator]
130
                                );
131
                            }
132
                            $lastOr = $node;
133
                        } else {
134
                            $leftNode = $node;
135
                            $node = null;
136
                        }
137
138
                        array_shift($expression);
139
                    }
140
                    break;
141
                default:
142
                    $conditionName = $expression[0];
143
                    if (false === $this->condition->hasItem($conditionName)) {
144
                        $this->result->addError(new Error('The condition "' . $conditionName . '" does not exist.',
145
                            1457628378));
146
                    } else {
147
                        $node = $this->getNode(
148
                            ConditionNode::class,
149
                            [
150
                                $conditionName,
151
                                $this->condition->getItem($conditionName)
152
                            ]
153
                        );
154
                        array_shift($expression);
155
                    }
156
                    break;
157
            }
158
159
            if (null !== $leftNode
160
                && null !== $node
161
            ) {
162
                $node = $this->getNode(
163
                    BooleanNode::class,
164
                    [$leftNode, $node, $operator]
165
                );
166
167
                $leftNode = null;
168
                $operator = null;
169
            }
170
        }
171
172
        if (null !== $leftNode) {
173
            $this->result->addError(new Error('Logical operator must be followed by a valid operation.',
174
                1457545071));
175
        } elseif (null !== $lastOr) {
176
            $node = $this->getNode(
177
                BooleanNode::class,
178
                [$lastOr, $node, ConditionParser::LOGICAL_OR]
179
            );
180
        }
181
182
        return $node;
183
    }
184
185
    /**
186
     * @param string $nodeClassName
187
     * @param array  $arguments
188
     * @return NodeInterface
189
     */
190
    protected function getNode($nodeClassName, array $arguments)
191
    {
192
        return call_user_func_array(
193
            [GeneralUtility::class, 'makeInstance'],
194
            array_merge([$nodeClassName], $arguments)
195
        );
196
    }
197
198
    /**
199
     * Will fetch for a group of operations in a given array: the first item
200
     * must be a parenthesis. If its closing parenthesis is found, then the
201
     * inner part of the group is returned. Example:
202
     *
203
     * Input: (cond1 && (cond2 || cond3)) && cond4
204
     * Output: cond1 && (cond2 || cond3)
205
     *
206
     * @param array $splitCondition
207
     * @return array
208
     */
209
    protected function getGroupNode(array $splitCondition)
210
    {
211
        $parenthesis = 1;
212
        $index = 0;
213
        while ($parenthesis > 0) {
214
            $index++;
215
            if ($index > count($splitCondition)) {
216
                $parenthesis = -1;
217
                break;
218
            }
219
220
            if ('(' === $splitCondition[$index]) {
221
                $parenthesis++;
222
            }
223
            if (')' === $splitCondition[$index]) {
224
                $parenthesis--;
225
            }
226
        }
227
228
        $finalSplitCondition = [];
229
        if (-1 === $parenthesis) {
230
            $this->result->addError(new Error('Parenthesis not correctly closed.',
231
                1457544856));
232
        } else {
233
            for ($i = 1; $i < $index; $i++) {
234
                $finalSplitCondition[] = $splitCondition[$i];
235
            }
236
        }
237
238
        return $finalSplitCondition;
239
    }
240
241
    /**
242
     * Will split a condition expression string in an exploded array where each
243
     * entry represents an operation.
244
     *
245
     * @param string $condition
246
     * @return array
247
     */
248
    protected function splitConditionExpression($condition)
249
    {
250
        preg_match_all('/(\w+|\(|\)|\&\&|\|\|)/', trim($condition), $result);
251
252
        return $result[0];
253
    }
254
}
255