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

IntoKeyword::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 13
nc 1
nop 6
crap 1
1
<?php
2
3
/**
4
 * `INTO` 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
 * `INTO` keyword parser.
16
 *
17
 * @category   Keywords
18
 *
19
 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
20
 */
21
class IntoKeyword extends Component
22
{
23
    /**
24
     * FIELDS/COLUMNS Options for `SELECT...INTO` statements.
25
     *
26
     * @var array
27
     */
28
    public static $FIELDS_OPTIONS = array(
29
        'TERMINATED BY' => array(1, 'expr'),
30
        'OPTIONALLY' => 2,
31
        'ENCLOSED BY' => array(3, 'expr'),
32
        'ESCAPED BY' => array(4, 'expr'),
33
    );
34
35
    /**
36
     * LINES Options for `SELECT...INTO` statements.
37
     *
38
     * @var array
39
     */
40
    public static $LINES_OPTIONS = array(
41
        'STARTING BY' => array(1, 'expr'),
42
        'TERMINATED BY' => array(2, 'expr'),
43
    );
44
45
    /**
46
     * Type of target (OUTFILE or SYMBOL).
47
     *
48
     * @var string
49
     */
50
    public $type;
51
52
    /**
53
     * The destination, which can be a table or a file.
54
     *
55
     * @var string|Expression
56
     */
57
    public $dest;
58
59
    /**
60
     * The name of the columns.
61
     *
62
     * @var array
63
     */
64
    public $columns;
65
66
    /**
67
     * The values to be selected into (SELECT .. INTO @var1).
68
     *
69
     * @var Expression[]
70
     */
71
    public $values;
72
73
    /**
74
     * Options for FIELDS/COLUMNS keyword.
75
     *
76
     * @var OptionsArray
77
     *
78
     * @see static::$FIELDS_OPTIONS
79
     */
80
    public $fields_options;
81
82
    /**
83
     * Whether to use `FIELDS` or `COLUMNS` while building.
84
     *
85
     * @var bool
86
     */
87
    public $fields_keyword;
88
89
    /**
90
     * Options for OPTIONS keyword.
91
     *
92
     * @var OptionsArray
93
     *
94
     * @see static::$LINES_OPTIONS
95
     */
96
    public $lines_options;
97
98
    /**
99
     * Constructor.
100
     *
101
     * @param string $type           Type of destination (may be OUTFILE).
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...
102
     * @param string|Expression $dest           Actual destination.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $dest not be string|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...
103
     * @param array $columns        Column list of destination.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $columns not be array|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...
104
     * @param array $values         Selected fields.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $values not be array|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...
105
     * @param OptionsArray $fields_options Options for FIELDS/COLUMNS keyword.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $fields_options not be OptionsArray|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...
106
     * @param OptionsArray $fields_keyword Options for OPTINOS keyword.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $fields_keyword not be OptionsArray|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...
107
     */
108 36
    public function __construct(
0 ignored issues
show
Coding Style Naming introduced by
The parameter $fields_options is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style Naming introduced by
The parameter $fields_keyword is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
109
        $type = null,
110
        $dest = null,
111
        $columns = null,
112
        $values = null,
113
        $fields_options = null,
114
        $fields_keyword = null
115
    ) {
116 36
        $this->type = $type;
117 36
        $this->dest = $dest;
118 36
        $this->columns = $columns;
0 ignored issues
show
Documentation Bug introduced by
It seems like $columns can be null. However, the property $columns 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...
119 36
        $this->values = $values;
0 ignored issues
show
Documentation Bug introduced by
It seems like $values can be null. However, the property $values 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...
120 36
        $this->fields_options = $fields_options;
121 36
        $this->fields_keyword = $fields_keyword;
0 ignored issues
show
Documentation Bug introduced by
It seems like $fields_keyword can also be of type object<PhpMyAdmin\SqlPar...omponents\OptionsArray>. However, the property $fields_keyword is declared as type boolean. 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...
122 36
    }
123
124
    /**
125
     * @param Parser     $parser  the parser that serves as context
126
     * @param TokensList $list    the list of tokens that are being parsed
127
     * @param array      $options parameters for parsing
128
     *
129
     * @return IntoKeyword
130
     */
131 36
    public static function parse(Parser $parser, TokensList $list, array $options = array())
132
    {
133 36
        $ret = new self();
134
135
        /**
136
         * The state of the parser.
137
         *
138
         * Below are the states of the parser.
139
         *
140
         *      0 -----------------------[ name ]----------------------> 1
141
         *      0 ---------------------[ OUTFILE ]---------------------> 2
142
         *
143
         *      1 ------------------------[ ( ]------------------------> (END)
144
         *
145
         *      2 ---------------------[ filename ]--------------------> 1
146
         *
147
         * @var int
148
         */
149 36
        $state = 0;
150
151 36
        for (; $list->idx < $list->count; ++$list->idx) {
152
            /**
153
             * Token parsed at this moment.
154
             *
155
             * @var Token
156
             */
157 36
            $token = $list->tokens[$list->idx];
158
159
            // End of statement.
160 36
            if ($token->type === Token::TYPE_DELIMITER) {
161 7
                break;
162
            }
163
164
            // Skipping whitespaces and comments.
165 36
            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
166 33
                continue;
167
            }
168
169 36
            if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
170 17
                if (($state === 0) && ($token->keyword === 'OUTFILE')) {
171 10
                    $ret->type = 'OUTFILE';
172 10
                    $state = 2;
173 10
                    continue;
174
                }
175
176
                // No other keyword is expected except for $state = 4, which expects `LINES`
177 12
                if ($state !== 4) {
178 11
                    break;
179
                }
180
            }
181
182 35
            if ($state === 0) {
183 26
                if ((isset($options['fromInsert'])
184 12
                    && $options['fromInsert'])
185 14
                    || (isset($options['fromReplace'])
186 26
                    && $options['fromReplace'])
187
                ) {
188 24
                    $ret->dest = Expression::parse(
189
                        $parser,
190
                        $list,
191
                        array(
192 24
                            'parseField' => 'table',
193
                            'breakOnAlias' => true,
194
                        )
195
                    );
196
                } else {
197 2
                    $ret->values = ExpressionArray::parse($parser, $list);
198
                }
199 26
                $state = 1;
200 27
            } elseif ($state === 1) {
201 18
                if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
202 16
                    $ret->columns = ArrayObj::parse($parser, $list)->values;
203 16
                    ++$list->idx;
204
                }
205 18
                break;
206 9
            } elseif ($state === 2) {
207 9
                $ret->dest = $token->value;
208
209 9
                $state = 3;
210 4
            } elseif ($state == 3) {
211 4
                $ret->parseFileOptions($parser, $list, $token->value);
212 4
                $state = 4;
213 4
            } elseif ($state == 4) {
214 4
                if ($token->type === Token::TYPE_KEYWORD && $token->keyword !== 'LINES') {
215 1
                    break;
216
                }
217
218 3
                $ret->parseFileOptions($parser, $list, $token->value);
219 3
                $state = 5;
220
            }
221
        }
222
223 36
        --$list->idx;
224
225 36
        return $ret;
226
    }
227
228 4
    public function parseFileOptions(Parser $parser, TokensList $list, $keyword = 'FIELDS')
229
    {
230 4
        ++$list->idx;
231
232 4
        if ($keyword === 'FIELDS' || $keyword === 'COLUMNS') {
233
            // parse field options
234 4
            $this->fields_options = OptionsArray::parse(
235
                $parser,
236
                $list,
237 4
                static::$FIELDS_OPTIONS
238
            );
239
240 4
            if ($keyword === 'FIELDS') {
241 1
                $this->fields_keyword = true;
242
            } else {
243 4
                $this->fields_keyword = false;
244
            }
245
        } else {
246
            // parse line options
247 3
            $this->lines_options = OptionsArray::parse(
248
                $parser,
249
                $list,
250 3
                static::$LINES_OPTIONS
251
            );
252
        }
253 4
    }
254
255
    /**
256
     * @param IntoKeyword $component the component to be built
257
     * @param array       $options   parameters for building
258
     *
259
     * @return string
260
     */
261 8
    public static function build($component, array $options = array())
262
    {
263 8
        if ($component->dest instanceof Expression) {
264 4
            $columns = !empty($component->columns) ? '(`' . implode('`, `', $component->columns) . '`)' : '';
265
266 4
            return $component->dest . $columns;
267
        } elseif (isset($component->values)) {
268 2
            return ExpressionArray::build($component->values);
0 ignored issues
show
Documentation introduced by
$component->values is of type array<integer,object<Php...Components\Expression>>, but the function expects a array<integer,object<Php...nents\ExpressionArray>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
269
        }
270
271 2
        $ret = 'OUTFILE "' . $component->dest . '"';
272
273 2
        $fields_options_str = OptionsArray::build($component->fields_options);
274 2
        if (trim($fields_options_str) !== '') {
275 1
            $ret .= ($component->fields_keyword) ? ' FIELDS' : ' COLUMNS';
276 1
            $ret .= ' ' . $fields_options_str;
277
        }
278
279 2
        $lines_options_str = OptionsArray::build($component->lines_options, array('expr' => true));
280 2
        if (trim($lines_options_str) !== '') {
281 1
            $ret .= ' LINES ' . $lines_options_str;
282
        }
283
284 2
        return $ret;
285
    }
286
}
287