PluginProbe
Code Snippets / 3.6.3
Code Snippets v3.6.3
3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 3.0.1 All 64 releases
code-snippets / php / class-validator.php

class-validator.php in Code Snippets 3.6.3, at php/class-validator.php

267 lines 7.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Code_Snippets;
4
5 /**
6 * Validates code prior to execution.
7 *
8 * @package Code_Snippets
9 */
10 class Validator {
11
12 /**
13 * Code to validate.
14 *
15 * @var string
16 */
17 private $code;
18
19 /**
20 * List of tokens.
21 *
22 * @var array<string>
23 */
24 private $tokens;
25
26 /**
27 * The index of the token currently being examined.
28 *
29 * @var integer
30 */
31 private $current;
32
33 /**
34 * The total number of tokens.
35 *
36 * @var integer
37 */
38 private $length;
39
40 /**
41 * Array to keep track of the various function, class and interface identifiers which have been defined.
42 *
43 * @var array<string, string[]>
44 */
45 private $defined_identifiers = [];
46
47 /**
48 * Exclude certain tokens from being checked.
49 *
50 * @var array<string, string[]>
51 */
52 private $exceptions = [];
53
54 /**
55 * Class constructor.
56 *
57 * @param string $code Snippet code for parsing.
58 */
59 public function __construct( string $code ) {
60 $this->code = $code;
61 $this->tokens = token_get_all( "<?php\n" . $this->code );
62 $this->length = count( $this->tokens );
63 $this->current = 0;
64 }
65
66 /**
67 * Determine whether the parser has reached the end of the list of tokens.
68 *
69 * @return bool
70 */
71 private function end(): bool {
72 return $this->current === $this->length;
73 }
74
75 /**
76 * Retrieve the next token without moving the pointer
77 *
78 * @return string|array<string|int>|null The current token if the list has not been expended, null otherwise.
79 */
80 private function peek() {
81 return $this->end() ? null : $this->tokens[ $this->current ];
82 }
83
84 /**
85 * Move the pointer to the next token, if there is one.
86 *
87 * If the first argument is provided, only move the pointer if the tokens match.
88 */
89 private function next() {
90 if ( ! $this->end() ) {
91 ++$this->current;
92 }
93 }
94
95 /**
96 * Check whether a particular identifier has been used previously.
97 *
98 * @param string $type Which type of identifier this is. Supports T_FUNCTION, T_CLASS and T_INTERFACE.
99 * @param string $identifier The name of the identifier itself.
100 *
101 * @return bool true if the identifier is not unique.
102 */
103 private function check_duplicate_identifier( string $type, string $identifier ): bool {
104
105 if ( ! isset( $this->defined_identifiers[ $type ] ) ) {
106 switch ( $type ) {
107 case T_FUNCTION:
108 $defined_functions = get_defined_functions();
109 $this->defined_identifiers[ T_FUNCTION ] = array_merge( $defined_functions['internal'], $defined_functions['user'] );
110 break;
111
112 case T_CLASS:
113 $this->defined_identifiers[ T_CLASS ] = get_declared_classes();
114 break;
115
116 case T_INTERFACE:
117 $this->defined_identifiers[ T_INTERFACE ] = get_declared_interfaces();
118 break;
119
120 default:
121 return false;
122 }
123 }
124
125 $duplicate = in_array( $identifier, $this->defined_identifiers[ $type ], true );
126 array_unshift( $this->defined_identifiers[ $type ], $identifier );
127
128 return $duplicate && ! ( isset( $this->exceptions[ $type ] ) && in_array( $identifier, $this->exceptions[ $type ], true ) );
129 }
130
131 /**
132 * Validate the given PHP code and return the result.
133 *
134 * @return array<string, mixed>|false Array containing message if an error was encountered, false if validation was successful.
135 */
136 public function validate() {
137
138 while ( ! $this->end() ) {
139 $token = $this->peek();
140 $this->next();
141
142 if ( ! is_array( $token ) ) {
143 continue;
144 }
145
146 // If this is a function or class exists check, then allow this function or class to be defined.
147 if ( T_STRING === $token[0] && 'function_exists' === $token[1] || 'class_exists' === $token[1] ) {
148 $type = 'function_exists' === $token[1] ? T_FUNCTION : T_CLASS;
149
150 // Eat tokens until we find the function or class name.
151 while ( ! $this->end() && T_CONSTANT_ENCAPSED_STRING !== $token[0] ) {
152 $token = $this->peek();
153 $this->next();
154 }
155
156 // Add the identifier to the list of exceptions.
157 $this->exceptions[ $type ] = $this->exceptions[ $type ] ?? [];
158 $this->exceptions[ $type ][] = trim( $token[1], '\'"' );
159 continue;
160 }
161
162 // If we have a double colon, followed by a class, then consume it before the next section.
163 if ( T_DOUBLE_COLON === $token[0] ) {
164 $token = $this->peek();
165 $this->next();
166
167 if ( T_CLASS === $token[0] ) {
168 $this->next();
169 $token = $this->peek();
170 }
171 }
172
173 // Only look for class and function declaration tokens.
174 if ( T_CLASS !== $token[0] && T_FUNCTION !== $token[0] ) {
175 continue;
176 }
177
178 /**
179 * Ensure the type of $token is inferred correctly.
180 *
181 * @var string|array<string|int> $token
182 */
183 $structure_type = $token[0];
184
185 // Continue eating tokens until we find the name of the class or function.
186 while ( ! $this->end() && T_STRING !== $token[0] &&
187 ( T_FUNCTION !== $structure_type || '(' !== $token ) && ( T_CLASS !== $structure_type || '{' !== $token ) ) {
188 $token = $this->peek();
189 $this->next();
190 }
191
192 // If we've eaten all the tokens without discovering a name, then there must be a syntax error, so return appropriately.
193 if ( $this->end() ) {
194 return array(
195 'message' => __( 'Parse error: syntax error, unexpected end of snippet.', 'code-snippets' ),
196 'line' => $token[2],
197 );
198 }
199
200 // If the function or class is anonymous, with no name, then no need to check.
201 if ( ! ( T_FUNCTION === $structure_type && '(' === $token ) && ! ( T_CLASS === $structure_type && '{' === $token ) ) {
202
203 // Check whether the name has already been defined.
204 if ( $this->check_duplicate_identifier( $structure_type, $token[1] ) ) {
205 switch ( $structure_type ) {
206 case T_FUNCTION:
207 /* translators: %s: PHP function name */
208 $message = __( 'Cannot redeclare function %s.', 'code-snippets' );
209 break;
210 case T_CLASS:
211 /* translators: %s: PHP class name */
212 $message = __( 'Cannot redeclare class %s.', 'code-snippets' );
213 break;
214 case T_INTERFACE:
215 /* translators: %s: PHP interface name */
216 $message = __( 'Cannot redeclare interface %s.', 'code-snippets' );
217 break;
218 default:
219 /* translators: %s: PHP identifier name*/
220 $message = __( 'Cannot redeclare %s.', 'code-snippets' );
221 }
222
223 return array(
224 'message' => sprintf( $message, $token[1] ),
225 'line' => $token[2],
226 );
227 }
228 }
229
230 // If we have entered into a class, eat tokens until we find the closing brace.
231 if ( T_CLASS !== $structure_type ) {
232 continue;
233 }
234
235 // Find the opening brace for the class.
236 while ( ! $this->end() && '{' !== $token ) {
237 $token = $this->peek();
238 $this->next();
239 }
240
241 // Continue traversing the class tokens until we have found the class closing brace.
242 $depth = 1;
243 while ( ! $this->end() && $depth > 0 ) {
244 $token = $this->peek();
245
246 if ( '{' === $token ) {
247 ++$depth;
248 } elseif ( '}' === $token ) {
249 --$depth;
250 }
251
252 $this->next();
253 }
254
255 // If we did not make it out of the class, then there's a problem.
256 if ( $depth > 0 ) {
257 return array(
258 'message' => __( 'Parse error: syntax error, unexpected end of snippet', 'code-snippets' ),
259 'line' => $token[2],
260 );
261 }
262 }
263
264 return false;
265 }
266 }
267