| Total Complexity | 69 |
| Total Lines | 583 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like Script often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Script, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 61 | class Script |
||
| 62 | { |
||
| 63 | /** |
||
| 64 | * Parses a value from a RouterOS scripting context. |
||
| 65 | * |
||
| 66 | * Turns a value from RouterOS into an equivalent PHP value, based on |
||
| 67 | * determining the type in the same way RouterOS would determine it for a |
||
| 68 | * literal. |
||
| 69 | * |
||
| 70 | * This method is intended to be the very opposite of |
||
| 71 | * {@link static::escapeValue()}. That is, results from that method, if |
||
| 72 | * given to this method, should produce equivalent results. |
||
| 73 | * |
||
| 74 | * @param string $value The value to be parsed. |
||
| 75 | * Must be a literal of a value, |
||
| 76 | * e.g. what {@link static::escapeValue()} will give you. |
||
| 77 | * @param DateTimeZone|null $timezone The timezone which any resulting |
||
| 78 | * DateTime object (either the main value, or values within an array) |
||
| 79 | * will use. Defaults to UTC. |
||
| 80 | * |
||
| 81 | * @return mixed Depending on RouterOS type detected: |
||
| 82 | * - "nil" (the string "[]") or "nothing" (empty string) - NULL. |
||
| 83 | * - "num" - int or double for large values. |
||
| 84 | * - "bool" - a boolean. |
||
| 85 | * - "array" - an array, with the keys and values processed recursively. |
||
| 86 | * - "time" - a {@link DateInterval} object. |
||
| 87 | * - "date" (pseudo type; string in the form "M/j/Y") - a DateTime |
||
| 88 | * object with the specified date, at midnight. |
||
| 89 | * - "datetime" (pseudo type; string in the form "M/j/Y H:i:s") - a |
||
| 90 | * DateTime object with the specified date and time. |
||
| 91 | * - "str" (a quoted string) - a string, with the contents escaped. |
||
| 92 | * - Unrecognized type - casted to a string, unmodified. |
||
| 93 | */ |
||
| 94 | public static function parseValue($value, DateTimeZone $timezone = null) |
||
| 95 | { |
||
| 96 | $value = static::parseValueToSimple($value); |
||
| 97 | if (!is_string($value)) { |
||
| 98 | return $value; |
||
| 99 | } |
||
| 100 | |||
| 101 | try { |
||
| 102 | return static::parseValueToArray($value, $timezone); |
||
| 103 | } catch (ParserException $e) { |
||
| 104 | try { |
||
| 105 | return static::parseValueToDateInterval($value); |
||
| 106 | } catch (ParserException $e) { |
||
| 107 | try { |
||
| 108 | return static::parseValueToDateTime($value, $timezone); |
||
| 109 | } catch (ParserException $e) { |
||
| 110 | return static::parseValueToString($value); |
||
| 111 | } |
||
| 112 | } |
||
| 113 | } |
||
| 114 | } |
||
| 115 | |||
| 116 | /** |
||
| 117 | * Parses a RouterOS value into a PHP string. |
||
| 118 | * |
||
| 119 | * @param string $value The value to be parsed. |
||
| 120 | * Must be a literal of a value, |
||
| 121 | * e.g. what {@link static::escapeValue()} will give you. |
||
| 122 | * |
||
| 123 | * @return string If a quoted string is provided, it would be parsed. |
||
| 124 | * Otherwise, the value is casted to a string, and returned unmodified. |
||
| 125 | */ |
||
| 126 | public static function parseValueToString($value) |
||
| 137 | } |
||
| 138 | |||
| 139 | /** |
||
| 140 | * Parses a RouterOS value into a PHP simple type. |
||
| 141 | * |
||
| 142 | * Parses a RouterOS value into a PHP simple type. "Simple" types being |
||
| 143 | * scalar types, plus NULL. |
||
| 144 | * |
||
| 145 | * @param string $value The value to be parsed. Must be a literal of a |
||
| 146 | * value, e.g. what {@link static::escapeValue()} will give you. |
||
| 147 | * |
||
| 148 | * @return string|bool|int|double|null Depending on RouterOS type detected: |
||
| 149 | * - "nil" (the string "[]") or "nothing" (empty string) - NULL. |
||
| 150 | * - "num" - int or double for large values. |
||
| 151 | * - "bool" - a boolean. |
||
| 152 | * - Unrecognized type - casted to a string, unmodified. |
||
| 153 | */ |
||
| 154 | public static function parseValueToSimple($value) |
||
| 168 | } |
||
| 169 | |||
| 170 | /** |
||
| 171 | * Parses a RouterOS value into a PHP DateTime object |
||
| 172 | * |
||
| 173 | * Parses a RouterOS value into a PHP DateTime object. |
||
| 174 | * |
||
| 175 | * @param string $value The value to be parsed. |
||
| 176 | * Must be a literal of a value, |
||
| 177 | * e.g. what {@link static::escapeValue()} will give you. |
||
| 178 | * @param DateTimeZone|null $timezone The timezone which the resulting |
||
| 179 | * DateTime object will use. Defaults to UTC. |
||
| 180 | * |
||
| 181 | * @return DateTime Depending on RouterOS type detected: |
||
| 182 | * - "date" (pseudo type; string in the form "M/j/Y") - a DateTime |
||
| 183 | * object with the specified date, at midnight UTC time (regardless |
||
| 184 | * of timezone provided). |
||
| 185 | * - "datetime" (pseudo type; string in the form "M/j/Y H:i:s") - a |
||
| 186 | * DateTime object with the specified date and time, |
||
| 187 | * with the specified timezone. |
||
| 188 | * |
||
| 189 | * @throws ParserException When the value is not of a recognized type. |
||
| 190 | */ |
||
| 191 | public static function parseValueToDateTime( |
||
| 192 | $value, |
||
| 193 | DateTimeZone $timezone = null |
||
| 194 | ) { |
||
| 195 | $previous = null; |
||
| 196 | $value = (string)$value; |
||
| 197 | if ('' !== $value && preg_match( |
||
| 198 | '#^ |
||
| 199 | (?<mon>jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) |
||
| 200 | / |
||
| 201 | (?<day>\d\d?) |
||
| 202 | / |
||
| 203 | (?<year>\d{4}) |
||
| 204 | (?: |
||
| 205 | \s+(?<time>\d{2}\:\d{2}:\d{2}) |
||
| 206 | )? |
||
| 207 | $#uix', |
||
| 208 | $value, |
||
| 209 | $date |
||
| 210 | ) |
||
| 211 | ) { |
||
| 212 | if (!isset($date['time'])) { |
||
| 213 | $date['time'] = '00:00:00'; |
||
| 214 | $timezone = new DateTimeZone('UTC'); |
||
| 215 | } elseif (null === $timezone) { |
||
| 216 | $timezone = new DateTimeZone('UTC'); |
||
| 217 | } |
||
| 218 | try { |
||
| 219 | return new DateTime( |
||
| 220 | $date['year'] . |
||
| 221 | '-' . ucfirst($date['mon']) . |
||
| 222 | "-{$date['day']} {$date['time']}", |
||
| 223 | $timezone |
||
| 224 | ); |
||
| 225 | } catch (E $e) { |
||
| 226 | $previous = $e; |
||
| 227 | } |
||
| 228 | } |
||
| 229 | throw new ParserException( |
||
| 230 | 'The supplied value can not be converted to a DateTime', |
||
| 231 | ParserException::CODE_DATETIME, |
||
| 232 | $previous |
||
| 233 | ); |
||
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * Parses a RouterOS value into a PHP DateInterval. |
||
| 238 | * |
||
| 239 | * Parses a RouterOS value into a PHP DateInterval. |
||
| 240 | * |
||
| 241 | * @param string $value The value to be parsed. Must be a literal of a |
||
| 242 | * value, e.g. what {@link static::escapeValue()} will give you. |
||
| 243 | * |
||
| 244 | * @return DateInterval The value as a DateInterval object. |
||
| 245 | * |
||
| 246 | * @throws ParserException When the value is not of a recognized type. |
||
| 247 | */ |
||
| 248 | public static function parseValueToDateInterval($value) |
||
| 329 | ); |
||
| 330 | } |
||
| 331 | |||
| 332 | /** |
||
| 333 | * Parses a RouterOS value into a PHP array. |
||
| 334 | * |
||
| 335 | * Parses a RouterOS value into a PHP array. |
||
| 336 | * |
||
| 337 | * @param string $value The value to be parsed. |
||
| 338 | * Must be a literal of a value, |
||
| 339 | * e.g. what {@link static::escapeValue()} will give you. |
||
| 340 | * @param DateTimeZone|null $timezone The timezone which any resulting |
||
| 341 | * DateTime object within the array will use. Defaults to UTC. |
||
| 342 | * |
||
| 343 | * @return array An array, with the keys and values processed recursively, |
||
| 344 | * the keys with {@link static::parseValueToSimple()}, |
||
| 345 | * and the values with {@link static::parseValue()}. |
||
| 346 | * |
||
| 347 | * @throws ParserException When the value is not of a recognized type. |
||
| 348 | */ |
||
| 349 | public static function parseValueToArray( |
||
| 404 | ); |
||
| 405 | } |
||
| 406 | |||
| 407 | /** |
||
| 408 | * Prepares a script. |
||
| 409 | * |
||
| 410 | * Prepares a script for eventual execution by prepending parameters as |
||
| 411 | * variables to it. |
||
| 412 | * |
||
| 413 | * This is particularly useful when you're creating scripts that you don't |
||
| 414 | * want to execute right now (as with {@link Util::exec()}, but instead |
||
| 415 | * you want to store it for later execution, perhaps by supplying it to |
||
| 416 | * "/system scheduler". |
||
| 417 | * |
||
| 418 | * @param string|resource $source The source of the script, |
||
| 419 | * as a string or stream. If a stream is provided, reading starts from |
||
| 420 | * the current position to the end of the stream, and the pointer stays |
||
| 421 | * at the end after reading is done. |
||
| 422 | * @param array<string|int,mixed> $params An array of parameters to make |
||
| 423 | * available in the script as local variables. |
||
| 424 | * Variable names are array keys, and variable values are array values. |
||
| 425 | * Array values are automatically processed with |
||
| 426 | * {@link static::escapeValue()}. Streams are also supported, and are |
||
| 427 | * processed in chunks, each with |
||
| 428 | * {@link static::escapeString()} with all bytes being escaped. |
||
| 429 | * Processing starts from the current position to the end of the stream, |
||
| 430 | * and the stream's pointer is left untouched after the reading is done. |
||
| 431 | * Variables with a value of type "nothing" can be declared with a |
||
| 432 | * numeric array key and the variable name as the array value |
||
| 433 | * (that is casted to a string). |
||
| 434 | * |
||
| 435 | * @return resource A new PHP temporary stream with the script as contents, |
||
| 436 | * with the pointer back at the start. |
||
| 437 | * |
||
| 438 | * @see static::append() |
||
| 439 | */ |
||
| 440 | public static function prepare( |
||
| 448 | } |
||
| 449 | |||
| 450 | /** |
||
| 451 | * Appends a script. |
||
| 452 | * |
||
| 453 | * Appends a script to an existing stream. |
||
| 454 | * |
||
| 455 | * @param resource $stream An existing stream to write the |
||
| 456 | * resulting script to. |
||
| 457 | * @param string|resource $source The source of the script, |
||
| 458 | * as a string or stream. If a stream is provided, reading starts from |
||
| 459 | * the current position to the end of the stream, and the pointer stays |
||
| 460 | * at the end after reading is done. |
||
| 461 | * @param array<string|int,mixed> $params An array of parameters to make |
||
| 462 | * available in the script as local variables. |
||
| 463 | * Variable names are array keys, and variable values are array values. |
||
| 464 | * Array values are automatically processed with |
||
| 465 | * {@link static::escapeValue()}. Streams are also supported, and are |
||
| 466 | * processed in chunks, each with |
||
| 467 | * {@link static::escapeString()} with all bytes being escaped. |
||
| 468 | * Processing starts from the current position to the end of the stream, |
||
| 469 | * and the stream's pointer is left untouched after the reading is done. |
||
| 470 | * Variables with a value of type "nothing" can be declared with a |
||
| 471 | * numeric array key and the variable name as the array value |
||
| 472 | * (that is casted to a string). |
||
| 473 | * |
||
| 474 | * @return int The number of bytes written to $stream is returned, |
||
| 475 | * and the pointer remains where it was after the write |
||
| 476 | * (i.e. it is not seeked back, even if seeking is supported). |
||
| 477 | */ |
||
| 478 | public static function append( |
||
| 511 | } |
||
| 512 | |||
| 513 | /** |
||
| 514 | * Escapes a value for a RouterOS scripting context. |
||
| 515 | * |
||
| 516 | * Turns any native PHP value into an equivalent whole value that can be |
||
| 517 | * inserted as part of a RouterOS script. |
||
| 518 | * |
||
| 519 | * DateInterval objects will be casted to RouterOS' "time" type. |
||
| 520 | * |
||
| 521 | * DateTime objects will be casted to a string following the "M/d/Y H:i:s" |
||
| 522 | * format. If the time is exactly midnight (including microseconds), and |
||
| 523 | * the timezone is UTC, the string will include only the "M/d/Y" date. |
||
| 524 | * |
||
| 525 | * Unrecognized types (i.e. resources and other objects) are casted to |
||
| 526 | * strings, and those strings are then escaped. |
||
| 527 | * |
||
| 528 | * @param mixed $value The value to be escaped. |
||
| 529 | * |
||
| 530 | * @return string A string representation that can be directly inserted in a |
||
| 531 | * script as a whole value. |
||
| 532 | */ |
||
| 533 | public static function escapeValue($value) |
||
| 534 | { |
||
| 535 | switch (gettype($value)) { |
||
| 536 | case 'NULL': |
||
| 537 | $value = '[]'; |
||
| 538 | break; |
||
| 539 | case 'integer': |
||
| 540 | $value = (string)$value; |
||
| 541 | break; |
||
| 542 | case 'boolean': |
||
| 543 | $value = $value ? 'true' : 'false'; |
||
| 544 | break; |
||
| 545 | case 'array': |
||
| 546 | if (0 === count($value)) { |
||
| 547 | $value = '({})'; |
||
| 548 | break; |
||
| 549 | } |
||
| 550 | $result = ''; |
||
| 551 | foreach ($value as $key => $val) { |
||
| 552 | $result .= ';'; |
||
| 553 | if (!is_int($key)) { |
||
| 554 | $result .= static::escapeValue($key) . '='; |
||
| 555 | } |
||
| 556 | $result .= static::escapeValue($val); |
||
| 557 | } |
||
| 558 | $value = '{' . substr($result, 1) . '}'; |
||
| 559 | break; |
||
| 560 | /** @noinspection PhpMissingBreakStatementInspection */ |
||
| 561 | case 'object': |
||
| 562 | if ($value instanceof DateTime) { |
||
| 563 | $usec = $value->format('u'); |
||
| 564 | $usec = '000000' === $usec ? '' : '.' . $usec; |
||
| 565 | $value = '00:00:00.000000 UTC' === $value->format('H:i:s.u e') |
||
| 566 | ? $value->format('M/d/Y') |
||
| 567 | : $value->format('M/d/Y H:i:s') . $usec; |
||
| 568 | } |
||
| 569 | if ($value instanceof DateInterval) { |
||
| 570 | if (false === $value->days || $value->days < 0) { |
||
| 571 | $value = $value->format('%r%dd%H:%I:%S'); |
||
| 572 | } else { |
||
| 573 | $value = $value->format('%r%ad%H:%I:%S'); |
||
| 574 | } |
||
| 575 | break; |
||
| 576 | } |
||
| 577 | //break; intentionally omitted |
||
| 578 | default: |
||
| 579 | $value = '"' . static::escapeString((string)$value) . '"'; |
||
| 580 | break; |
||
| 581 | } |
||
| 582 | return $value; |
||
| 583 | } |
||
| 584 | |||
| 585 | /** |
||
| 586 | * Escapes a string for a RouterOS scripting context. |
||
| 587 | * |
||
| 588 | * Escapes a string for a RouterOS scripting context. The value can then be |
||
| 589 | * surrounded with quotes at a RouterOS script (or concatenated onto a |
||
| 590 | * larger string first), and you can be sure there won't be any code |
||
| 591 | * injections coming from it. |
||
| 592 | * |
||
| 593 | * By default, for the sake of brevity of the output, ASCII alphanumeric |
||
| 594 | * characters and underscores are left untouched. And for the sake of |
||
| 595 | * character conversion, bytes above 0x7F are also left untouched. |
||
| 596 | * |
||
| 597 | * @param string $value Value to be escaped. |
||
| 598 | * @param bool $full Whether to escape all bytes in the string, including |
||
| 599 | * ASCII alphanumeric characters, underscores and bytes above 0x7F. |
||
| 600 | * |
||
| 601 | * @return string The escaped value. |
||
| 602 | * |
||
| 603 | * @internal Why leave ONLY those ASCII characters and not also others? |
||
| 604 | * Because those can't in any way be mistaken for language constructs, |
||
| 605 | * unlike many other "safe inside strings, but not outside" ASCII |
||
| 606 | * characters, like ",", ".", "+", "-", "~", etc. |
||
| 607 | */ |
||
| 608 | public static function escapeString($value, $full = false) |
||
| 609 | { |
||
| 610 | if ($full) { |
||
| 611 | return self::_escapeCharacters(array($value)); |
||
| 612 | } |
||
| 613 | return preg_replace_callback( |
||
| 614 | '/[^\\_A-Za-z0-9\\x80-\\xFF]+/S', |
||
| 615 | array(__CLASS__, '_escapeCharacters'), |
||
| 616 | $value |
||
| 617 | ); |
||
| 618 | } |
||
| 619 | |||
| 620 | /** |
||
| 621 | * Escapes a character for a RouterOS scripting context. |
||
| 622 | * |
||
| 623 | * Escapes a character for a RouterOS scripting context. |
||
| 624 | * Intended to only be called by {@link self::escapeString()} for the |
||
| 625 | * matching strings. |
||
| 626 | * |
||
| 627 | * @param array $chars The matches array, expected to contain exactly one |
||
| 628 | * member, in which is the whole string to be escaped. |
||
| 629 | * |
||
| 630 | * @return string The escaped characters. |
||
| 631 | */ |
||
| 632 | private static function _escapeCharacters(array $chars) |
||
| 644 | } |
||
| 645 | } |
||
| 646 |