LogicalOpFactory::combineUsingLogicalOp()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 3
nc 2
nop 3
1
<?php
2
namespace Consolidation\Filter;
3
4
use Dflydev\DotAccessData\Data;
5
6
use Consolidation\Filter\Operators\LogicalAndOp;
7
use Consolidation\Filter\Operators\LogicalOrOp;
8
9
/**
10
 * Convert an expression with logical operators into an Operator.
11
 */
12
class LogicalOpFactory implements FactoryInterface
13
{
14
    protected $factory;
15
16
    /**
17
     * Factory constructor
18
     * @param FactoryInterface|null $factory
19
     * @return FactoryInterface
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
20
     */
21
    public function __construct($factory = null)
22
    {
23
        $this->factory = $factory ?: new OperatorFactory();
24
    }
25
26
    /**
27
     * Factory factory
28
     * @return FactoryInterface
29
     */
30
    public static function get()
31
    {
32
        return new self();
33
    }
34
35
    /**
36
     * Create an operator or a set of operators from the expression.
37
     *
38
     * @param string $expression
39
     * @return OperatorInterface
40
     */
41
    public function evaluate($expression, $default_field = false)
42
    {
43
        $exprSet = $this->splitByLogicOp($expression);
44
        $result = false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression false; of type false adds false to the return on line 53 which is incompatible with the return type declared by the interface Consolidation\Filter\FactoryInterface::evaluate of type Consolidation\Filter\OperatorInterface. It seems like you forgot to handle an error condition.
Loading history...
45
46
        foreach ($exprSet as $exprWithLogicOp) {
47
            $logicOp = $exprWithLogicOp[1];
48
            $expr = $exprWithLogicOp[2];
49
            $rhs = $this->factory->evaluate($expr, $default_field);
50
            $result = $this->combineUsingLogicalOp($result, $logicOp, $rhs);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->combineUsingLogic...result, $logicOp, $rhs) on line 50 can also be of type object<Consolidation\Filter\OperatorInterface>; however, Consolidation\Filter\Log...combineUsingLogicalOp() does only seem to accept object<Consolidation\Filter\Operator>|false, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
51
        }
52
53
        return $result;
54
    }
55
56
    /**
57
     * Given an expression in a form similar to 'a=b&c=d|x=y',
58
     * produce a result as :
59
     *
60
     * [
61
     *   [
62
     *     0 => 'a=b',
63
     *     1 => '',
64
     *     2 => 'a=b',
65
     *   ],
66
     *   [
67
     *     0 => '&&c=d',
68
     *     1 => '&&',
69
     *     2 => 'c=d',
70
     *   ],
71
     *   [
72
     *     0 => '||x=y',
73
     *     1 => '||',
74
     *     2 => 'x=y',
75
     *   ],
76
     * ]
77
     *
78
     * This is the data structure returned by the former preg_match_all call
79
     * used, which was:
80
     *
81
     * preg_match_all('#([&|]*)([^&|]+)#', $expression, $exprSet, PREG_SET_ORDER)
82
     *
83
     * The new algorithm splices the expressions together manually, as it was
84
     * difficult to get preg_match_all to match && and || reliably.
85
     *
86
     * @param string $expression
87
     * @return array
88
     */
89
    protected function splitByLogicOp($expression)
90
    {
91
        if (!preg_match_all('#(&&|\|\|)#', $expression, $matches, PREG_OFFSET_CAPTURE)) {
92
            return [ [$expression, '', $expression] ];
93
        }
94
        $exprSet = [];
95
        $i = 0;
96
        foreach ($matches as $opWithOffset) {
97
            list($op, $offset) = $opWithOffset[0];
98
            $expr = substr($expression, $i, $i + $offset);
99
            $i = $i + $offset + strlen($op);
100
            $exprSet[] = [ "$op$expr", $op, $expr, ];
101
        }
102
        return $exprSet;
103
    }
104
105
    /**
106
     * Given the left-hand-side operator, a logical operator, and a
107
     * string expression, create the right-hand-side operator and combine
108
     * it with the provided lhs operator.
109
     *
110
     * @param Operator|false $lhs Left-hand-side operator
111
     * @param string $logicOp '&' or '|'
112
     * @param OperatorInterface $rhs Right-hand-side operator
113
     * @return Operator
114
     */
115
    protected function combineUsingLogicalOp($lhs, $logicOp, OperatorInterface $rhs)
116
    {
117
        // If this is the first term, just return the $rhs.
118
        // At this point, $logicOp is always empty.
119
        if (!$lhs || empty($logicOp)) {
120
            return $rhs;
121
        }
122
123
        // At this point, $logicOp is never empty.
124
        return $this->createLogicalOp($lhs, $logicOp, $rhs);
125
    }
126
127
    /**
128
     * Given the left-hand-side operator, a logical operator, and a
129
     * string expression, create the right-hand-side operator and combine
130
     * it with the provided lhs operator.
131
     *
132
     * @param Operator|false $lhs Left-hand-side operator
133
     * @param string $logicOp '&' or '|'
134
     * @param OperatorInterface $rhs Right-hand-side operator
135
     * @return Operator
136
     */
137
    protected function createLogicalOp(OperatorInterface $lhs, $logicOp, OperatorInterface $rhs)
138
    {
139
        switch ($logicOp) {
140
            case '&&':
141
                return new LogicalAndOp($lhs, $rhs);
142
            case '||':
143
                return new LogicalOrOp($lhs, $rhs);
144
        }
145
        throw new \Exception('Impossible logicOp received: ' . $logicOp);
146
    }
147
}
148