GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (23)

src/Parse/TableOptions.php (2 issues)

Labels
Severity
1
<?php
2
namespace Graze\Morphism\Parse;
3
4
use RuntimeException;
5
6
/**
7
 * Represents a set of table options - MIN_ROWS, PACK_KEYS, COMMENT, etc.
8
 */
9
class TableOptions
10
{
11
    /** @var string|null */
12
    public $engine = null;
13
14
    /** @var CollationInfo */
15
    public $collation = null;
16
17
    /**
18
     * @var array
19
     * maps option names to values (string|int|null)
20
     */
21
    public $options = [];
22
23
    /** @var string */
24
    private $defaultEngine = null;
25
    /** @var CollationInfo|null */
26
    private $defaultCollation = null;
27
    /** @var array */
28
    private $defaultOptions = [
29
        'AUTO_INCREMENT'  => null,
30
        'MIN_ROWS'        => 0,
31
        'MAX_ROWS'        => 0,
32
        'AVG_ROW_LENGTH'  => 0,
33
        'PACK_KEYS'       => 'DEFAULT',
34
        'CHECKSUM'        => '0',
35
        'DELAY_KEY_WRITE' => '0',
36
        'ROW_FORMAT'      => 'DEFAULT',
37
        'KEY_BLOCK_SIZE'  => 0,
38
        'COMMENT'         => '',
39
        'CONNECTION'      => '',
40
    ];
41
42
    /**
43
     * Constructor
44
     * @param CollationInfo $databaseCollation
45
     */
46 204
    public function __construct(CollationInfo $databaseCollation)
47
    {
48 204
        $this->collation = new CollationInfo;
49 204
        $this->defaultCollation = clone $databaseCollation;
50 204
    }
51
52
    /**
53
     * Set the default storage engine for the table to use in case the
54
     * ENGINE option is not supplied.
55
     *
56
     * @param string $engine e.g. 'InnoDB"
57
     */
58 201
    public function setDefaultEngine($engine)
59
    {
60 201
        $this->defaultEngine = self::normaliseEngine($engine);
61 201
    }
62
63
    /**
64
     * Parses table options from $stream.
65
     * @param TokenStream $stream
66
     */
67 184
    public function parse(TokenStream $stream)
68
    {
69 184
        $this->engine = $this->defaultEngine;
70 184
        $this->options = $this->defaultOptions;
71
72 184
        while (true) {
73 184
            $mark = $stream->getMark();
74 184
            $token = $stream->nextToken();
75 184
            if ($token->type !== Token::IDENTIFIER) {
76 169
                $stream->rewind($mark);
77 169
                break;
78
            }
79
80 102
            if ($token->eq(Token::IDENTIFIER, 'DEFAULT')) {
81 4
                $token = $stream->nextToken();
82 4
                if (!($token->type === Token::IDENTIFIER &&
83 4
                      in_array(strtoupper($token->text), ['CHARSET', 'CHARACTER', 'COLLATE']))
84
                ) {
85 1
                    throw new RuntimeException("Expected CHARSET, CHARACTER SET or COLLATE");
86
                }
87
            }
88
89 101
            $this->parseOption($stream, strtoupper($token->text));
90
        }
91
92 169
        if (!$this->collation->isSpecified()) {
93 161
            $this->collation = clone $this->defaultCollation;
94
        }
95 169
    }
96
97
    /**
98
     * @param TokenStream $stream
99
     * @param string $option
100
     */
101 101
    private function parseOption(TokenStream $stream, $option)
102
    {
103 101
        switch ($option) {
104 101
            case 'ENGINE':
105 92
            case 'COLLATE':
106 86
            case 'CHARSET':
107 22
                $this->parseIdentifier($stream, $option);
108 20
                break;
109
110 79
            case 'CHARACTER':
111 2
                $stream->expect(Token::IDENTIFIER, 'SET');
112 2
                $this->parseIdentifier($stream, 'CHARSET');
113 2
                break;
114
115 77
            case 'AUTO_INCREMENT':
116 72
            case 'AVG_ROW_LENGTH':
117 67
            case 'KEY_BLOCK_SIZE':
118 62
            case 'MAX_ROWS':
119 57
            case 'MIN_ROWS':
120 25
                $this->parseNumber($stream, $option);
121 25
                break;
122
123 52
            case 'CHECKSUM':
124 47
            case 'DELAY_KEY_WRITE':
125 10
                $this->parseEnum($stream, $option, ['0', '1']);
126 10
                break;
127
128 42
            case 'PACK_KEYS':
129 6
                $this->parseEnum($stream, $option, ['DEFAULT', '0', '1']);
130 6
                break;
131
132 36
            case 'DATA':
133 35
            case 'INDEX':
134 2
                $stream->expect(Token::IDENTIFIER, 'DIRECTORY');
135
                // fall through //
136 34
            case 'COMMENT':
137 28
            case 'CONNECTION':
138 22
            case 'PASSWORD':
139 15
                $this->parseString($stream, $option);
140 15
                break;
141
142 21
            case 'INSERT_METHOD':
143 4
                $this->parseEnum($stream, $option, ['NO', 'FIRST', 'LAST']);
144 3
                throw new RuntimeException("$option is not currently supported by this tool");
145
146 17
            case 'ROW_FORMAT':
147 10
                $this->parseEnum($stream, $option, ['DEFAULT', 'DYNAMIC', 'FIXED', 'COMPRESSED', 'REDUNDANT', 'COMPACT']);
148 9
                break;
149
150 7
            case 'PARTITION':
151 6
            case 'STATS_AUTO_RECALC':
152 5
            case 'STATS_PERSISTENT':
153 4
            case 'STATS_SAMPLE_PAGES':
154 3
            case 'TABLESPACE':
155 2
            case 'UNION':
156 6
                throw new RuntimeException("$option is not currently supported by this tool");
157
158
            default:
159 1
                throw new RuntimeException("Unknown table option: $option");
160
        }
161 87
    }
162
163
    /**
164
     * @param string $engine
165
     * @return string
166
     */
167 202
    private static function normaliseEngine($engine)
168
    {
169 202
        $engine = strtoupper($engine);
170 202
        switch ($engine) {
171 202
            case 'INNODB':
172 200
                return 'InnoDB';
173 9
            case 'MYISAM':
174 7
                return 'MyISAM';
175
            default:
176 3
                return $engine;
177
        }
178
    }
179
180
    /**
181
     * @param string $option
182
     * @param string $value
183
     */
184 90
    private function setOption($option, $value)
185
    {
186 90
        switch ($option) {
187 90
            case 'ENGINE':
188 7
                $this->engine = self::normaliseEngine($value);
189 7
                break;
190
191 83
            case 'CHARSET':
192 9
                if (strtoupper($value) === 'DEFAULT') {
193 2
                    $this->collation = new CollationInfo();
194
                } else {
195 8
                    $this->collation->setCharset($value);
196
                }
197 9
                break;
198
199 74
            case 'COLLATE':
200 6
                if (strtoupper($value) === 'DEFAULT') {
201 1
                    $this->collation = new CollationInfo();
202
                } else {
203 5
                    $this->collation->setCollation($value);
204
                }
205 6
                break;
206
207
            default:
208 68
                $this->options[$option] = $value;
209 68
                break;
210
        }
211 90
    }
212
213
    /**
214
     * @param TokenStream $stream
215
     * @param string $option
216
     */
217 24
    private function parseIdentifier(TokenStream $stream, $option)
218
    {
219 24
        $stream->consume([[Token::SYMBOL, '=']]);
220 24
        $token = $stream->nextToken();
221 24
        if ($token->isEof()) {
222 1
            throw new RuntimeException("Unexpected end-of-file");
223
        }
224 23
        if (!in_array($token->type, [Token::IDENTIFIER, Token::STRING])) {
225 1
            throw new RuntimeException("Bad table option value: '$token->text'");
226
        }
227 22
        $this->setOption($option, strtolower($token->text));
228 22
    }
229
230
    /**
231
     * @param TokenStream $stream
232
     * @param string $option
233
     */
234 25
    private function parseNumber(TokenStream $stream, $option)
235
    {
236 25
        $stream->consume([[Token::SYMBOL, '=']]);
237 25
        $this->setOption($option, $stream->expectNumber());
238 25
    }
239
240
    /**
241
     * @param TokenStream $stream
242
     * @param string $option
243
     * @param array $enums
244
     */
245 30
    private function parseEnum(TokenStream $stream, $option, array $enums)
246
    {
247 30
        $stream->consume([[Token::SYMBOL, '=']]);
248 30
        $token = $stream->nextToken();
249 30
        if (!in_array($token->type, [Token::IDENTIFIER, Token::NUMBER])) {
250 1
            throw new RuntimeException("Bad table option value");
251
        }
252 29
        $value = strtoupper($token->text);
253 29
        if (!in_array($value, $enums)) {
254 1
            throw new RuntimeException("Invalid option value, expected " . implode(' | ', $enums));
255
        }
256 28
        $this->setOption($option, $value);
257 28
    }
258
259
    /**
260
     * @param TokenStream $stream
261
     * @param string $option
262
     */
263 15
    private function parseString(TokenStream $stream, $option)
264
    {
265 15
        $stream->consume([[Token::SYMBOL, '=']]);
266 15
        $this->setOption($option, $stream->expectString());
267 15
    }
268
269
    /**
270
     * Returns an SQL fragment to set the options as part of a CREATE TABLE statement.
271
     * Note that the AUTO_INCREMENT option is explicitly *not* included in the output.
272
     */
273 98
    public function toString()
274
    {
275 98
        $items = [];
276
277 98
        $items[] = "ENGINE=" . $this->engine;
278
279
        // (omit AUTO_INCREMENT)
280
281 98
        $collation = $this->collation;
282 98
        if ($collation->isSpecified()) {
283 7
            $items[] = "DEFAULT CHARSET=" . $collation->getCharset();
284 7
            if (!$collation->isDefaultCollation()) {
285 2
                $items[] = "COLLATE=" . $collation->getCollation();
286
            }
287
        }
288
289
        foreach ([
290 98
            'MIN_ROWS',
291
            'MAX_ROWS',
292
            'AVG_ROW_LENGTH',
293
            'PACK_KEYS',
294
            'CHECKSUM',
295
            'DELAY_KEY_WRITE',
296
            'ROW_FORMAT',
297
            'KEY_BLOCK_SIZE',
298
            'COMMENT',
299
            'CONNECTION',
300
        ] as $option) {
301 98
            if ($this->options[$option] !== $this->defaultOptions[$option]) {
302 17
                $value = $this->options[$option];
303 17
                if (in_array($option, ['COMMENT', 'CONNECTION'])) {
304 4
                    $value = Token::escapeString($value);
305
                }
306 17
                $items[] = "$option=$value";
307
            }
308
        }
309
310 98
        return implode(' ', $items);
311
    }
312
313
    /**
314
     * Returns an SQL fragment to transform these table options into those
315
     * specified by $that as part of an ALTER TABLE statement.
316
     *
317
     * The empty string is returned if nothing needs to be done.
318
     *
319
     * $flags           |
320
     * :----------------|
321
     * 'alterEngine'    | (bool) include 'ALTER TABLE ... ENGINE=' [default: true]
322
     *
323
     * @param TableOptions $that
324
     * @param array $flags
325
     * @return string
326
     */
327 71
    public function diff(TableOptions $that, array $flags = [])
328
    {
329
        $flags += [
330 71
            'alterEngine' => true,
331
        ];
332
333 71
        $alters = [];
334 71
        if ($flags['alterEngine']) {
335 69
            if (strcasecmp($this->engine, $that->engine) !== 0) {
0 ignored issues
show
It seems like $this->engine can also be of type null; however, parameter $string1 of strcasecmp() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

335
            if (strcasecmp(/** @scrutinizer ignore-type */ $this->engine, $that->engine) !== 0) {
Loading history...
It seems like $that->engine can also be of type null; however, parameter $string2 of strcasecmp() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

335
            if (strcasecmp($this->engine, /** @scrutinizer ignore-type */ $that->engine) !== 0) {
Loading history...
336 4
                $alters[] = "ENGINE=" . $that->engine;
337
            }
338
        }
339
340 71
        $thisCollation = $this->collation->isSpecified()
341 3
            ? $this->collation->getCollation()
342 71
            : null;
343 71
        $thatCollation = $that->collation->isSpecified()
344 4
            ? $that->collation->getCollation()
345 71
            : null;
346 71
        if ($thisCollation !== $thatCollation) {
347
            // TODO - what if !$that->collation->isSpecified()
348 6
            if (!is_null($thatCollation)) {
349 4
                $alters[] = "DEFAULT CHARSET=" . $that->collation->getCharset();
350 4
                if (!$that->collation->isDefaultCollation()) {
351 2
                    $alters[] = "COLLATE=" . $thatCollation;
352
                }
353
            }
354
        }
355
356
        foreach ([
357 71
            'MIN_ROWS',
358
            'MAX_ROWS',
359
            'AVG_ROW_LENGTH',
360
            'PACK_KEYS',
361
            'CHECKSUM',
362
            'DELAY_KEY_WRITE',
363
364
            // The storage engine may pick a different row format when
365
            // ROW_FORMAT=DEFAULT (or no ROW_FORMAT)/ is specified, depending
366
            // on whether any variable length columns are present. Since we
367
            // don't (currently) explicitly specify ROW_FORMAT in any of our
368
            // tables, I'm choosing to ignore it for the time being...
369
        //  'ROW_FORMAT',
370
            'KEY_BLOCK_SIZE',
371
            'COMMENT',
372
            'CONNECTION',
373
        ] as $option) {
374 71
            $thisValue = $this->options[$option];
375 71
            $thatValue = $that->options[$option];
376 71
            if (in_array($option, ['COMMENT', 'CONNECTION'])) {
377 71
                $thisValue = Token::escapeString($thisValue);
378 71
                $thatValue = Token::escapeString($thatValue);
379
            }
380
381 71
            if ($thisValue !== $thatValue) {
382 27
                $alters[] = "$option=$thatValue";
383
            }
384
        }
385
386 71
        return implode(' ', $alters);
387
    }
388
}
389