Total Complexity | 69 |
Total Lines | 579 |
Duplicated Lines | 0 % |
Changes | 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 | if (!isset($date['time'])) { |
||
212 | $date['time'] = '00:00:00'; |
||
213 | $timezone = new DateTimeZone('UTC'); |
||
214 | } elseif (null === $timezone) { |
||
215 | $timezone = new DateTimeZone('UTC'); |
||
216 | } |
||
217 | try { |
||
218 | return new DateTime( |
||
219 | $date['year'] . |
||
220 | '-' . ucfirst($date['mon']) . |
||
221 | "-{$date['day']} {$date['time']}", |
||
222 | $timezone |
||
223 | ); |
||
224 | } catch (E $e) { |
||
225 | $previous = $e; |
||
226 | } |
||
227 | } |
||
228 | throw new ParserException( |
||
229 | 'The supplied value can not be converted to a DateTime', |
||
230 | ParserException::CODE_DATETIME, |
||
231 | $previous |
||
232 | ); |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Parses a RouterOS value into a PHP DateInterval. |
||
237 | * |
||
238 | * Parses a RouterOS value into a PHP DateInterval. |
||
239 | * |
||
240 | * @param string $value The value to be parsed. Must be a literal of a |
||
241 | * value, e.g. what {@link static::escapeValue()} will give you. |
||
242 | * |
||
243 | * @return DateInterval The value as a DateInterval object. |
||
244 | * |
||
245 | * @throws ParserException When the value is not of a recognized type. |
||
246 | */ |
||
247 | public static function parseValueToDateInterval($value) |
||
326 | ); |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * Parses a RouterOS value into a PHP array. |
||
331 | * |
||
332 | * Parses a RouterOS value into a PHP array. |
||
333 | * |
||
334 | * @param string $value The value to be parsed. |
||
335 | * Must be a literal of a value, |
||
336 | * e.g. what {@link static::escapeValue()} will give you. |
||
337 | * @param DateTimeZone|null $timezone The timezone which any resulting |
||
338 | * DateTime object within the array will use. Defaults to UTC. |
||
339 | * |
||
340 | * @return array An array, with the keys and values processed recursively, |
||
341 | * the keys with {@link static::parseValueToSimple()}, |
||
342 | * and the values with {@link static::parseValue()}. |
||
343 | * |
||
344 | * @throws ParserException When the value is not of a recognized type. |
||
345 | */ |
||
346 | public static function parseValueToArray( |
||
401 | ); |
||
402 | } |
||
403 | |||
404 | /** |
||
405 | * Prepares a script. |
||
406 | * |
||
407 | * Prepares a script for eventual execution by prepending parameters as |
||
408 | * variables to it. |
||
409 | * |
||
410 | * This is particularly useful when you're creating scripts that you don't |
||
411 | * want to execute right now (as with {@link Util::exec()}, but instead |
||
412 | * you want to store it for later execution, perhaps by supplying it to |
||
413 | * "/system scheduler". |
||
414 | * |
||
415 | * @param string|resource $source The source of the script, |
||
416 | * as a string or stream. If a stream is provided, reading starts from |
||
417 | * the current position to the end of the stream, and the pointer stays |
||
418 | * at the end after reading is done. |
||
419 | * @param array<string|int,mixed> $params An array of parameters to make |
||
420 | * available in the script as local variables. |
||
421 | * Variable names are array keys, and variable values are array values. |
||
422 | * Array values are automatically processed with |
||
423 | * {@link static::escapeValue()}. Streams are also supported, and are |
||
424 | * processed in chunks, each with |
||
425 | * {@link static::escapeString()} with all bytes being escaped. |
||
426 | * Processing starts from the current position to the end of the stream, |
||
427 | * and the stream's pointer is left untouched after the reading is done. |
||
428 | * Variables with a value of type "nothing" can be declared with a |
||
429 | * numeric array key and the variable name as the array value |
||
430 | * (that is casted to a string). |
||
431 | * |
||
432 | * @return resource A new PHP temporary stream with the script as contents, |
||
433 | * with the pointer back at the start. |
||
434 | * |
||
435 | * @see static::append() |
||
436 | */ |
||
437 | public static function prepare( |
||
438 | $source, |
||
439 | array $params = array() |
||
440 | ) { |
||
441 | $resultStream = fopen('php://temp', 'r+b'); |
||
442 | static::append($resultStream, $source, $params); |
||
443 | rewind($resultStream); |
||
444 | return $resultStream; |
||
445 | } |
||
446 | |||
447 | /** |
||
448 | * Appends a script. |
||
449 | * |
||
450 | * Appends a script to an existing stream. |
||
451 | * |
||
452 | * @param resource $stream An existing stream to write the |
||
453 | * resulting script to. |
||
454 | * @param string|resource $source The source of the script, |
||
455 | * as a string or stream. If a stream is provided, reading starts from |
||
456 | * the current position to the end of the stream, and the pointer stays |
||
457 | * at the end after reading is done. |
||
458 | * @param array<string|int,mixed> $params An array of parameters to make |
||
459 | * available in the script as local variables. |
||
460 | * Variable names are array keys, and variable values are array values. |
||
461 | * Array values are automatically processed with |
||
462 | * {@link static::escapeValue()}. Streams are also supported, and are |
||
463 | * processed in chunks, each with |
||
464 | * {@link static::escapeString()} with all bytes being escaped. |
||
465 | * Processing starts from the current position to the end of the stream, |
||
466 | * and the stream's pointer is left untouched after the reading is done. |
||
467 | * Variables with a value of type "nothing" can be declared with a |
||
468 | * numeric array key and the variable name as the array value |
||
469 | * (that is casted to a string). |
||
470 | * |
||
471 | * @return int The number of bytes written to $stream is returned, |
||
472 | * and the pointer remains where it was after the write |
||
473 | * (i.e. it is not seeked back, even if seeking is supported). |
||
474 | */ |
||
475 | public static function append( |
||
476 | $stream, |
||
477 | $source, |
||
478 | array $params = array() |
||
479 | ) { |
||
480 | $writer = new Stream($stream, false); |
||
481 | $bytes = 0; |
||
482 | |||
483 | foreach ($params as $pname => $pvalue) { |
||
484 | if (is_int($pname)) { |
||
485 | $pvalue = static::escapeString((string)$pvalue); |
||
486 | $bytes += $writer->send(":local \"{$pvalue}\";\n"); |
||
487 | continue; |
||
488 | } |
||
489 | $pname = static::escapeString($pname); |
||
490 | $bytes += $writer->send(":local \"{$pname}\" "); |
||
491 | if (Stream::isStream($pvalue)) { |
||
492 | $reader = new Stream($pvalue, false); |
||
493 | $chunkSize = $reader->getChunk(Stream::DIRECTION_RECEIVE); |
||
494 | $bytes += $writer->send('"'); |
||
495 | while ($reader->isAvailable() && $reader->isDataAwaiting()) { |
||
496 | $bytes += $writer->send( |
||
497 | static::escapeString(fread($pvalue, $chunkSize), true) |
||
498 | ); |
||
499 | } |
||
500 | $bytes += $writer->send("\";\n"); |
||
501 | } else { |
||
502 | $bytes += $writer->send(static::escapeValue($pvalue) . ";\n"); |
||
503 | } |
||
504 | } |
||
505 | |||
506 | $bytes += $writer->send($source); |
||
507 | return $bytes; |
||
508 | } |
||
509 | |||
510 | /** |
||
511 | * Escapes a value for a RouterOS scripting context. |
||
512 | * |
||
513 | * Turns any native PHP value into an equivalent whole value that can be |
||
514 | * inserted as part of a RouterOS script. |
||
515 | * |
||
516 | * DateInterval objects will be casted to RouterOS' "time" type. |
||
517 | * |
||
518 | * DateTime objects will be casted to a string following the "M/d/Y H:i:s" |
||
519 | * format. If the time is exactly midnight (including microseconds), and |
||
520 | * the timezone is UTC, the string will include only the "M/d/Y" date. |
||
521 | * |
||
522 | * Unrecognized types (i.e. resources and other objects) are casted to |
||
523 | * strings, and those strings are then escaped. |
||
524 | * |
||
525 | * @param mixed $value The value to be escaped. |
||
526 | * |
||
527 | * @return string A string representation that can be directly inserted in a |
||
528 | * script as a whole value. |
||
529 | */ |
||
530 | public static function escapeValue($value) |
||
531 | { |
||
532 | switch(gettype($value)) { |
||
533 | case 'NULL': |
||
534 | $value = '[]'; |
||
535 | break; |
||
536 | case 'integer': |
||
537 | $value = (string)$value; |
||
538 | break; |
||
539 | case 'boolean': |
||
540 | $value = $value ? 'true' : 'false'; |
||
541 | break; |
||
542 | case 'array': |
||
543 | if (0 === count($value)) { |
||
544 | $value = '({})'; |
||
545 | break; |
||
546 | } |
||
547 | $result = ''; |
||
548 | foreach ($value as $key => $val) { |
||
549 | $result .= ';'; |
||
550 | if (!is_int($key)) { |
||
551 | $result .= static::escapeValue($key) . '='; |
||
552 | } |
||
553 | $result .= static::escapeValue($val); |
||
554 | } |
||
555 | $value = '{' . substr($result, 1) . '}'; |
||
556 | break; |
||
557 | case 'object': |
||
558 | if ($value instanceof DateTime) { |
||
559 | $usec = $value->format('u'); |
||
560 | $usec = '000000' === $usec ? '' : '.' . $usec; |
||
561 | $value = '00:00:00.000000 UTC' === $value->format('H:i:s.u e') |
||
562 | ? $value->format('M/d/Y') |
||
563 | : $value->format('M/d/Y H:i:s') . $usec; |
||
564 | } |
||
565 | if ($value instanceof DateInterval) { |
||
566 | if (false === $value->days || $value->days < 0) { |
||
567 | $value = $value->format('%r%dd%H:%I:%S'); |
||
568 | } else { |
||
569 | $value = $value->format('%r%ad%H:%I:%S'); |
||
570 | } |
||
571 | break; |
||
572 | } |
||
573 | //break; intentionally omitted |
||
574 | default: |
||
575 | $value = '"' . static::escapeString((string)$value) . '"'; |
||
576 | break; |
||
577 | } |
||
578 | return $value; |
||
579 | } |
||
580 | |||
581 | /** |
||
582 | * Escapes a string for a RouterOS scripting context. |
||
583 | * |
||
584 | * Escapes a string for a RouterOS scripting context. The value can then be |
||
585 | * surrounded with quotes at a RouterOS script (or concatenated onto a |
||
586 | * larger string first), and you can be sure there won't be any code |
||
587 | * injections coming from it. |
||
588 | * |
||
589 | * By default, for the sake of brevity of the output, ASCII alphanumeric |
||
590 | * characters and underscores are left untouched. And for the sake of |
||
591 | * character conversion, bytes above 0x7F are also left untouched. |
||
592 | * |
||
593 | * @param string $value Value to be escaped. |
||
594 | * @param bool $full Whether to escape all bytes in the string, including |
||
595 | * ASCII alphanumeric characters, underscores and bytes above 0x7F. |
||
596 | * |
||
597 | * @return string The escaped value. |
||
598 | * |
||
599 | * @internal Why leave ONLY those ASCII characters and not also others? |
||
600 | * Because those can't in any way be mistaken for language constructs, |
||
601 | * unlike many other "safe inside strings, but not outside" ASCII |
||
602 | * characters, like ",", ".", "+", "-", "~", etc. |
||
603 | */ |
||
604 | public static function escapeString($value, $full = false) |
||
605 | { |
||
606 | if ($full) { |
||
607 | return self::_escapeCharacters(array($value)); |
||
608 | } |
||
609 | return preg_replace_callback( |
||
610 | '/[^\\_A-Za-z0-9\\x80-\\xFF]+/S', |
||
611 | array(__CLASS__, '_escapeCharacters'), |
||
612 | $value |
||
613 | ); |
||
614 | } |
||
615 | |||
616 | /** |
||
617 | * Escapes a character for a RouterOS scripting context. |
||
618 | * |
||
619 | * Escapes a character for a RouterOS scripting context. |
||
620 | * Intended to only be called by {@link self::escapeString()} for the |
||
621 | * matching strings. |
||
622 | * |
||
623 | * @param array $chars The matches array, expected to contain exactly one |
||
624 | * member, in which is the whole string to be escaped. |
||
625 | * |
||
626 | * @return string The escaped characters. |
||
627 | */ |
||
628 | private static function _escapeCharacters(array $chars) |
||
640 | } |
||
641 | } |
||
642 |