Completed
Push — master ( e05c46...93167f )
by Dan
03:50
created

JoinKeyword::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 4
crap 1
1
<?php
2
3
/**
4
 * `JOIN` keyword parser.
5
 */
6
7
namespace PhpMyAdmin\SqlParser\Components;
8
9
use PhpMyAdmin\SqlParser\Component;
10
use PhpMyAdmin\SqlParser\Parser;
11
use PhpMyAdmin\SqlParser\Token;
12
use PhpMyAdmin\SqlParser\TokensList;
13
14
/**
15
 * `JOIN` keyword parser.
16
 *
17
 * @category   Keywords
18
 *
19
 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
20
 */
21
class JoinKeyword extends Component
22
{
23
    /**
24
     * Types of join.
25
     *
26
     * @var array
27
     */
28
    public static $JOINS = array(
29
        'CROSS JOIN' => 'CROSS',
30
        'FULL JOIN' => 'FULL',
31
        'FULL OUTER JOIN' => 'FULL',
32
        'INNER JOIN' => 'INNER',
33
        'JOIN' => 'JOIN',
34
        'LEFT JOIN' => 'LEFT',
35
        'LEFT OUTER JOIN' => 'LEFT',
36
        'RIGHT JOIN' => 'RIGHT',
37
        'RIGHT OUTER JOIN' => 'RIGHT',
38
        'NATURAL JOIN' => 'NATURAL',
39
        'NATURAL LEFT JOIN' => 'NATURAL LEFT',
40
        'NATURAL RIGHT JOIN' => 'NATURAL RIGHT',
41
        'NATURAL LEFT OUTER JOIN' => 'NATURAL LEFT OUTER',
42
        'NATURAL RIGHT OUTER JOIN' => 'NATURAL RIGHT OUTER',
43
        'STRAIGHT_JOIN' => 'STRAIGHT',
44
    );
45
46
    /**
47
     * Type of this join.
48
     *
49
     * @see static::$JOINS
50
     *
51
     * @var string
52
     */
53
    public $type;
54
55
    /**
56
     * Join expression.
57
     *
58
     * @var Expression
59
     */
60
    public $expr;
61
62
    /**
63
     * Join conditions.
64
     *
65
     * @var Condition[]
66
     */
67
    public $on;
68
69
    /**
70
     * Columns in Using clause.
71
     *
72
     * @var ArrayObj
73
     */
74
    public $using;
75
76
    /**
77
     * Constructor.
78
     *
79
     * @param string $type Join type
0 ignored issues
show
Documentation introduced by
Should the type for parameter $type not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
80
     * @param Expression $expr Join expression.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $expr not be Expression|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
81
     * @param Condition[] $on Join conditions.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $on not be Condition[]|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
82
     * @param ArrayObj $using Columns joined.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $using not be ArrayObj|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
83
     *
84
     * @see JoinKeyword::$JOINS
85
     */
86 22
    public function __construct($type = null, $expr = null, $on = null, $using = null)
87
    {
88 22
        $this->type = $type;
89 22
        $this->expr = $expr;
90 22
        $this->on = $on;
0 ignored issues
show
Documentation Bug introduced by
It seems like $on can be null. However, the property $on is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

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

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
91 22
        $this->using = $using;
92 22
    }
93
94
    /**
95
     * @param Parser     $parser  the parser that serves as context
96
     * @param TokensList $list    the list of tokens that are being parsed
97
     * @param array      $options parameters for parsing
98
     *
99
     * @return JoinKeyword[]
100
     */
101 22
    public static function parse(Parser $parser, TokensList $list, array $options = array())
102
    {
103 22
        $ret = array();
104
105 22
        $expr = new self();
106
107
        /**
108
         * The state of the parser.
109
         *
110
         * Below are the states of the parser.
111
         *
112
         *      0 -----------------------[ JOIN ]----------------------> 1
113
         *
114
         *      1 -----------------------[ expr ]----------------------> 2
115
         *
116
         *      2 ------------------------[ ON ]-----------------------> 3
117
         *      2 -----------------------[ USING ]---------------------> 4
118
         *
119
         *      3 --------------------[ conditions ]-------------------> 0
120
         *
121
         *      4 ----------------------[ columns ]--------------------> 0
122
         *
123
         * @var int
124
         */
125 22
        $state = 0;
126
127
        // By design, the parser will parse first token after the keyword.
128
        // In this case, the keyword must be analyzed too, in order to determine
129
        // the type of this join.
130 22
        if ($list->idx > 0) {
131 19
            --$list->idx;
132
        }
133
134 22
        for (; $list->idx < $list->count; ++$list->idx) {
135
            /**
136
             * Token parsed at this moment.
137
             *
138
             * @var Token
139
             */
140 22
            $token = $list->tokens[$list->idx];
141
142
            // End of statement.
143 22
            if ($token->type === Token::TYPE_DELIMITER) {
144 15
                break;
145
            }
146
147
            // Skipping whitespaces and comments.
148 22
            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
149 22
                continue;
150
            }
151
152 22
            if ($state === 0) {
153 22
                if (($token->type === Token::TYPE_KEYWORD)
154 22
                    && (!empty(static::$JOINS[$token->keyword]))
155
                ) {
156 22
                    $expr->type = static::$JOINS[$token->keyword];
157 22
                    $state = 1;
158
                } else {
159 22
                    break;
160
                }
161 22
            } elseif ($state === 1) {
162 22
                $expr->expr = Expression::parse($parser, $list, array('field' => 'table'));
163 22
                $state = 2;
164 15
            } elseif ($state === 2) {
165 15
                if ($token->type === Token::TYPE_KEYWORD) {
166 15
                    if ($token->keyword === 'ON') {
167 10
                        $state = 3;
168 6
                    } elseif ($token->keyword === 'USING') {
169 1
                        $state = 4;
170
                    } else {
171 5
                        if (($token->type === Token::TYPE_KEYWORD)
172 5
                            && (!empty(static::$JOINS[$token->keyword]))
173
                        ) {
174 4
                            $ret[] = $expr;
175 4
                            $expr = new self();
176 4
                            $expr->type = static::$JOINS[$token->keyword];
177 4
                            $state = 1;
178
                        } else {
179
                            /* Next clause is starting */
180 15
                            break;
181
                        }
182
                    }
183
                }
184 11
            } elseif ($state === 3) {
185 10
                $expr->on = Condition::parse($parser, $list);
186 10
                $ret[] = $expr;
187 10
                $expr = new self();
188 10
                $state = 0;
189 1
            } elseif ($state === 4) {
190 1
                $expr->using = ArrayObj::parse($parser, $list);
0 ignored issues
show
Documentation Bug introduced by
It seems like \PhpMyAdmin\SqlParser\Co...::parse($parser, $list) can also be of type array. However, the property $using is declared as type object<PhpMyAdmin\SqlParser\Components\ArrayObj>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
191 1
                $ret[] = $expr;
192 1
                $expr = new self();
193 1
                $state = 0;
194
            }
195
        }
196
197 22
        if (!empty($expr->type)) {
198 12
            $ret[] = $expr;
199
        }
200
201 22
        --$list->idx;
202
203 22
        return $ret;
204
    }
205
206
    /**
207
     * @param JoinKeyword[] $component the component to be built
208
     * @param array         $options   parameters for building
209
     *
210
     * @return string
211
     */
212 4
    public static function build($component, array $options = array())
213
    {
214 4
        $ret = array();
215 4
        foreach ($component as $c) {
216 4
            $ret[] = array_search($c->type, static::$JOINS) . ' ' . $c->expr
217 4
                . (!empty($c->on)
218 4
                    ? ' ON ' . Condition::build($c->on) : '')
219 4
                . (!empty($c->using)
220 4
                    ? ' USING ' . ArrayObj::build($c->using) : '');
221
        }
222
223 4
        return implode(' ', $ret);
224
    }
225
}
226