InlineSqlMinimalConverter   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 168
Duplicated Lines 7.74 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 87.38%

Importance

Changes 0
Metric Value
dl 13
loc 168
ccs 90
cts 103
cp 0.8738
rs 9.84
c 0
b 0
f 0
wmc 32
lcom 1
cbo 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A addParameter() 0 16 3
A convert() 0 11 2
A onOpenOr() 0 4 1
A onCloseOr() 0 5 1
D onAndPossibility() 13 86 23
A getLastOrOperandKey() 0 5 1
A appendToLastOrOperandKey() 0 5 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * InlineSqlMinimalConverter
4
 *
5
 * @package php-logical-filter
6
 * @author  Jean Claveau
7
 */
8
namespace JClaveau\LogicalFilter\Converter;
9
10
use       JClaveau\LogicalFilter\LogicalFilter;
11
use       JClaveau\LogicalFilter\Rule\EqualRule;
12
use       JClaveau\LogicalFilter\Rule\NotEqualRule;
13
use       JClaveau\LogicalFilter\Rule\AboveRule;
14
use       JClaveau\LogicalFilter\Rule\BelowRule;
15
use       JClaveau\LogicalFilter\Rule\RegexpRule;
16
use       JClaveau\LogicalFilter\Rule\InRule;
17
use       JClaveau\LogicalFilter\Rule\NotInRule;
18
use       JClaveau\LogicalFilter\Rule\AboveOrEqualRule;
19
use       JClaveau\LogicalFilter\Rule\BelowOrEqualRule;
20
21
/**
22
 * This class implements a converter for MySQL.
23
 */
24
class InlineSqlMinimalConverter extends MinimalConverter
25
{
26
    /** @var array $output */
27
    protected $output = [];
28
29
    /** @var array $parameters */
30
    protected $parameters = [];
31
32
    /**
33
     * @return string parameter id
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
34
     */
35 1
    public function addParameter($value)
36
    {
37 1
        if (is_numeric($value)) {
38 1
            return $value;
39
        }
40
41 1
        $uid = 'param_'.hash('crc32b', serialize($value));
42
43 1
        if (isset($this->parameters[$uid])) {
44 1
            return ':'.$uid;
45
        }
46
47 1
        $this->parameters[$uid] = $value;
48
49 1
        return ':'.$uid;
50
    }
51
52
    /**
53
     * @param LogicalFilter $filter
54
     */
55 2
    public function convert( LogicalFilter $filter )
56
    {
57 2
        $this->output = [];
58 2
        parent::convert($filter);
59
        return [
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('sql' => !$... => $this->parameters); (array<string,string|array>) is incompatible with the return type of the parent method JClaveau\LogicalFilter\C...nimalConverter::convert of type JClaveau\LogicalFilter\C...r\MinimalConverter|null.

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...
60 2
            'sql' => ! $this->output
61 2
                   ? '1' // True
62 2
                   : '('.implode(') OR (', $this->output).')',
63 2
            'parameters' => $this->parameters,
64 2
        ];
65
    }
66
67
    /**
68
     */
69 1
    public function onOpenOr()
70
    {
71 1
        $this->output[] = [];
72 1
    }
73
74
    /**
75
     */
76 1
    public function onCloseOr()
77
    {
78 1
        $last_key                  = $this->getLastOrOperandKey();
79 1
        $this->output[ $last_key ] = implode(' AND ', $this->output[ $last_key ]);
80 1
    }
81
82
    /**
83
     * Pseudo-event called while for each And operand of the root Or.
84
     * These operands must be only atomic Rules.
85
     */
86 1
    public function onAndPossibility($field, $operator, $rule, array $allOperandsByField)
87
    {
88 1
        if ($rule instanceof EqualRule) {
89 1
            $value = $rule->getValue();
90 1
        }
91 1 View Code Duplication
        elseif ($rule instanceof InRule) {
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...
92 1
            $value = $rule->getPossibilities();
93 1
            if (is_object($value) && method_exists('toArray', $value)) {
94
                $value = $value->toArray();
95
            }
96 1
        }
97 1 View Code Duplication
        elseif ($rule instanceof NotInRule) {
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...
98 1
            $operator = 'NOT IN';
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $operator. This often makes code more readable.
Loading history...
99 1
            $value    = $rule->getPossibilities();
100 1
            if (is_object($value) && method_exists('toArray', $value)) {
101
                $value = $value->toArray();
102
            }
103 1
        }
104 1
        elseif ($rule instanceof AboveRule) {
105 1
            $value = $rule->getLowerLimit();
106 1
        }
107 1
        elseif ($rule instanceof BelowRule) {
108 1
            $value = $rule->getUpperLimit();
109 1
        }
110 1
        elseif ($rule instanceof AboveOrEqualRule) {
111 1
            $value = $rule->getMinimum();
112 1
        }
113 1
        elseif ($rule instanceof BelowOrEqualRule) {
114 1
            $value = $rule->getMaximum();
115 1
        }
116 1
        elseif ($rule instanceof NotEqualRule) {
117 1
            $value = $rule->getValue();
118 1
        }
119 1
        elseif ($rule instanceof RegexpRule) {
120 1
            $value = RegexpRule::php2mariadbPCRE( $rule->getPattern() );
121 1
        }
122
        else {
123
            throw new \InvalidArgumentException(
124
                "Unhandled operator '$operator' during SQL query generation"
125
            );
126
        }
127
128 1
        if ('integer' == gettype($value)) {
129 1
        }
130 1
        elseif ('double' == gettype($value)) {
131
            // TODO disable locale to handle separators
132
        }
133 1
        elseif ($value instanceof \DateTime) {
134 1
            $value = "'" . $value->format('Y-m-d H:i:s') . "'";
135 1
        }
136 1
        elseif ('string' == gettype($value)) {
137 1
            $value = $this->addParameter($value);
138 1
        }
139 1
        elseif ('array' == gettype($value)) {
140 1
            $sql_part = [];
141 1
            foreach ($value as $possibility) {
142 1
                $sql_part[] = $this->addParameter($possibility);
143 1
            }
144 1
            $value = '(' . implode(', ', $sql_part) . ')';
145 1
        }
146 1
        elseif (null === $value) {
147 1
            $value = "NULL";
148 1
            if ($rule instanceof EqualRule) {
149 1
                $operator = 'IS';
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $operator. This often makes code more readable.
Loading history...
150 1
            }
151 1
            elseif ($rule instanceof NotEqualRule) {
152 1
                $operator = 'IS NOT';
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $operator. This often makes code more readable.
Loading history...
153 1
            }
154
            else {
155
                throw new \InvalidArgumentException(
156
                    "NULL is only handled for equality / difference"
157
                );
158
            }
159 1
        }
160
        else {
161
            throw new \InvalidArgumentException(
162
                "Unhandled type of value: ".gettype($value). ' | ' .var_export($value, true)
163
            );
164
        }
165
166 1
        $operator = strtoupper($operator);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $operator. This often makes code more readable.
Loading history...
167
168 1
        $new_rule = "$field $operator $value";
169
170 1
        $this->appendToLastOrOperandKey($new_rule);
171 1
    }
172
173
    /**
174
     */
175 1
    protected function getLastOrOperandKey()
176
    {
177 1
        end($this->output);
178 1
        return key($this->output);
179
    }
180
181
    /**
182
     * @param string $rule
183
     */
184 1
    protected function appendToLastOrOperandKey($rule)
185
    {
186 1
        $last_key                    = $this->getLastOrOperandKey();
187 1
        $this->output[ $last_key ][] = $rule;
188 1
    }
189
190
    /**/
191
}
192