2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 // +----------------------------------------------------------------------+
5 // | Akelos Framework - http://www.akelos.org |
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
8 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
9 // +----------------------------------------------------------------------+
11 require_once(AK_VENDOR_DIR
.DS
.'pear'.DS
.'PHP'.DS
.'Compat'.DS
.'Constant'.DS
.'T.php');
14 * @todo Avoid the ussage of globals in the PHP parser
18 * This is a modified version of the pear/PHP_Shell package by Jan Kneschke
19 * and is used for validating PHP in the interactive shell
20 * before terminating execution with fatal errors.
22 * @package ActiveSupport
24 * @author Jan Kneschke <jan@kneschke.de>
25 * @author Bermi Ferrer <bermi a.t akelos c.om>
26 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
27 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
31 var $errors = array();
34 function AkPhpParser($code)
36 $this->code
= trim($code);
41 * we parse before we eval() the code to
42 * - fetch fatal errors before they come up
43 * - know about where we have to wait for closing braces
45 * @return int 0 if a executable statement is in the code-buffer, non-zero otherwise
50 $this->code
= trim($this->code
);
51 if (empty($this->code
)){
55 $t = token_get_all('<?php '.$this->code
.' ?>');
57 $need_semicolon = 1; /* do we need a semicolon to complete the statement ? */
58 $need_return = 1; /* can we prepend a return to the eval-string ? */
59 $eval = ''; /* code to be eval()'ed later */
60 $braces = array(); /* to track if we need more closing braces */
62 $methods = array(); /* to track duplicate methods in a class declaration */
63 $ts = array(); /* tokens without whitespaces */
65 foreach ($t as $ndx => $token) {
66 if (is_array($token)) {
105 case T_OBJECT_OPERATOR
:
115 case T_CONSTANT_ENCAPSED_STRING
:
116 case T_ENCAPSED_AND_WHITESPACE
:
138 case T_IS_GREATER_OR_EQUAL
:
139 case T_IS_SMALLER_OR_EQUAL
:
163 /* debug unknown tags*/
164 error_log(sprintf("unknown tag: %d (%s): %s".PHP_EOL
, $token[0], token_name($token[0]), $token[1]));
169 $eval .= $token[1]." ";
170 $ts[] = array("token" => $token[0], "value" => $token[1]);
173 $ts[] = array("token" => $token, "value" => '');
175 $last = count($ts) - 1;
179 /* walk backwards through the tokens */
182 $ts[$last - 1]['token'] == T_STRING
&&
183 $ts[$last - 2]['token'] == T_OBJECT_OPERATOR
&&
184 $ts[$last - 3]['token'] == T_VARIABLE
) {
186 /* $object->method( */
188 /* $object has to exist and has to be a object */
189 $objname = $ts[$last - 3]['value'];
191 if (!isset($GLOBALS[ltrim($objname, '$')])) {
192 $this->addError(sprintf('Variable \'%s\' is not set', $objname));
195 $k = ltrim($objname, '$');
197 if(isset($GLOBALS[$k])){
198 $object = $GLOBALS[$k];
200 if (!is_object($object)) {
201 $this->addError(sprintf('Variable \'%s\' is not a class', $objname));
204 $method = $ts[$last - 1]['value'];
208 if (!method_exists($object, $method)) {
209 $this->addError(sprintf("Variable %s (Class '%s') doesn't have a method named '%s'",
210 $objname, get_class($object), $method));
213 } else if ($last >= 3 &&
214 $ts[$last - 1]['token'] == T_VARIABLE
&&
215 $ts[$last - 2]['token'] == T_OBJECT_OPERATOR
&&
216 $ts[$last - 3]['token'] == T_VARIABLE
) {
218 /* $object->$method( */
220 /* $object has to exist and has to be a object */
221 $objname = $ts[$last - 3]['value'];
223 if (!isset($GLOBALS[ltrim($objname, '$')])) {
224 $this->addError(sprintf('Variable \'%s\' is not set', $objname));
226 $object = $GLOBALS[ltrim($objname, '$')];
228 if (!is_object($object)) {
229 $this->addError(sprintf('Variable \'%s\' is not a class', $objname));
232 $methodname = $ts[$last - 1]['value'];
234 if (!isset($GLOBALS[ltrim($methodname, '$')])) {
235 $this->addError(sprintf('Variable \'%s\' is not set', $methodname));
237 $method = $GLOBALS[ltrim($methodname, '$')];
241 if (!method_exists($object, $method)) {
242 $this->addError(sprintf("Variable %s (Class '%s') doesn't have a method named '%s'",
243 $objname, get_class($object), $method));
246 } else if ($last >= 6 &&
247 $ts[$last - 1]['token'] == T_STRING
&&
248 $ts[$last - 2]['token'] == T_OBJECT_OPERATOR
&&
249 $ts[$last - 3]['token'] == ']' &&
250 /* might be anything as index */
251 $ts[$last - 5]['token'] == '[' &&
252 $ts[$last - 6]['token'] == T_VARIABLE
) {
254 /* $object[...]->method( */
256 /* $object has to exist and has to be a object */
257 $objname = $ts[$last - 6]['value'];
259 if (!isset($GLOBALS[ltrim($objname, '$')])) {
260 $this->addError(sprintf('Variable \'%s\' is not set', $objname));
262 $array = $GLOBALS[ltrim($objname, '$')];
264 if (!is_array($array)) {
265 $this->addError(sprintf('Variable \'%s\' is not a array', $objname));
268 $andx = $ts[$last - 4]['value'];
270 if (!isset($array[$andx])) {
271 $this->addError(sprintf('%s[\'%s\'] is not set', $objname, $andx));
274 $object = $array[$andx];
276 if (!is_object($object)) {
277 $this->addError(sprintf('Variable \'%s\' is not a class', $objname));
280 $method = $ts[$last - 1]['value'];
284 if (!method_exists($object, $method)) {
285 $this->addError(sprintf("Variable %s (Class '%s') doesn't have a method named '%s'",
286 $objname, get_class($object), $method));
289 } else if ($last >= 3 &&
290 $ts[$last - 1]['token'] == T_STRING
&&
291 $ts[$last - 2]['token'] == T_DOUBLE_COLON
&&
292 $ts[$last - 3]['token'] == T_STRING
) {
294 /* Class::method() */
296 /* $object has to exist and has to be a object */
297 $classname = $ts[$last - 3]['value'];
299 if (!class_exists($classname)) {
300 $this->addError(sprintf('Class \'%s\' doesn\'t exist', $classname));
303 $method = $ts[$last - 1]['value'];
305 if (!empty($method) && !in_array($method, (array)get_class_methods($classname))) {
306 $this->addError(sprintf("Class '%s' doesn't have a method named '%s'",
307 $classname, $method));
309 } else if ($last >= 3 &&
310 $ts[$last - 1]['token'] == T_VARIABLE
&&
311 $ts[$last - 2]['token'] == T_DOUBLE_COLON
&&
312 $ts[$last - 3]['token'] == T_STRING
) {
314 /* Class::method() */
316 /* $object has to exist and has to be a object */
317 $classname = $ts[$last - 3]['value'];
319 if (!class_exists($classname)) {
320 $this->addError(sprintf('Class \'%s\' doesn\'t exist', $classname));
323 $methodname = $ts[$last - 1]['value'];
325 if (!isset($GLOBALS[ltrim($methodname, '$')])) {
326 $this->addError(sprintf('Variable \'%s\' is not set', $methodname));
328 $method = $GLOBALS[ltrim($methodname, '$')];
330 if (!in_array($method, get_class_methods($classname))) {
331 $this->addError(sprintf("Class '%s' doesn't have a method named '%s'",
332 $classname, $method));
335 } else if ($last >= 2 &&
336 $ts[$last - 1]['token'] == T_STRING
&&
337 $ts[$last - 2]['token'] == T_NEW
) {
341 $classname = $ts[$last - 1]['value'];
343 if (!class_exists($classname)) {
344 $this->addError(sprintf('Class \'%s\' doesn\'t exist', $classname));
348 $r = new ReflectionClass($classname);
350 if ($r->isAbstract()) {
351 $this->addError(sprintf("Can't instantiate abstract Class '%s'", $classname));
354 if (!$r->isInstantiable()) {
355 $this->addError(sprintf('Class \'%s\' can\'t be instantiated. Is the class abstract ?', $classname));
359 } else if ($last >= 2 &&
360 $ts[0]['token'] != T_CLASS
&&
361 $ts[$last - 1]['token'] == T_STRING
&&
362 $ts[$last - 2]['token'] == T_FUNCTION
) {
364 /* make sure we are not a in class definition */
368 $func = $ts[$last - 1]['value'];
370 if (function_exists($func)) {
371 $this->addError(sprintf('Function \'%s\' is already defined', $func));
373 } else if ($last >= 4 &&
374 $ts[0]['token'] == T_CLASS
&&
375 $ts[1]['token'] == T_STRING
&&
376 $ts[$last - 1]['token'] == T_STRING
&&
377 $ts[$last - 2]['token'] == T_FUNCTION
) {
379 /* make sure we are not a in class definition */
381 /* class a { .. function a() ... } */
383 $func = $ts[$last - 1]['value'];
384 $classname = $ts[1]['value'];
386 if (isset($methods[$func])) {
387 $this->addError(sprintf("Can't redeclare method '%s' in Class '%s'", $func, $classname));
392 } else if ($last >= 1 &&
393 $ts[$last - 1]['token'] == T_STRING
) {
395 $funcname = $ts[$last - 1]['value'];
397 if (!function_exists($funcname)) {
398 $this->addError(sprintf("Function %s() doesn't exist", $funcname));
400 } else if ($last >= 1 &&
401 $ts[$last - 1]['token'] == T_VARIABLE
) {
403 /* $object has to exist and has to be a object */
404 $funcname = $ts[$last - 1]['value'];
406 if (!isset($GLOBALS[ltrim($funcname, '$')])) {
407 $this->addError(sprintf('Variable \'%s\' is not set', $funcname));
409 $k = ltrim($funcname, '$');
411 if(isset($GLOBALS[$k])){
412 $func = $GLOBALS[$k];
414 if (!function_exists($func)) {
415 $this->addError(sprintf("Function %s() doesn't exist", $func));
421 array_push($braces, $token);
427 $ts[$last - 1]['token'] == T_STRING
&&
428 $ts[$last - 2]['token'] == T_CLASS
) {
432 $classname = $ts[$last - 1]['value'];
434 if (class_exists($classname)) {
435 $this->addError(sprintf("Class '%s' can't be redeclared", $classname));
437 } else if ($last >= 4 &&
438 $ts[$last - 1]['token'] == T_STRING
&&
439 $ts[$last - 2]['token'] == T_EXTENDS
&&
440 $ts[$last - 3]['token'] == T_STRING
&&
441 $ts[$last - 4]['token'] == T_CLASS
) {
443 /* class classname extends classname { */
445 $classname = $ts[$last - 3]['value'];
446 $extendsname = $ts[$last - 1]['value'];
448 if (class_exists($classname, false)) {
449 $this->addError(sprintf("Class '%s' can't be redeclared",
452 if (!class_exists($extendsname, false)) {
453 $this->addError(sprintf("Can't extend '%s' from not existing Class '%s'",
454 $classname, $extendsname));
456 } else if ($last >= 4 &&
457 $ts[$last - 1]['token'] == T_STRING
&&
458 $ts[$last - 2]['token'] == T_IMPLEMENTS
&&
459 $ts[$last - 3]['token'] == T_STRING
&&
460 $ts[$last - 4]['token'] == T_CLASS
) {
462 /* class name implements interface { */
464 $classname = $ts[$last - 3]['value'];
465 $implements = $ts[$last - 1]['value'];
467 if (class_exists($classname, false)) {
468 $this->addError(sprintf("Class '%s' can't be redeclared",
471 if (!interface_exists($implements, false)) {
472 $this->addError(sprintf("Can't implement not existing Interface '%s' for Class '%s'",
473 $implements, $classname));
477 array_push($braces, $token);
490 $last = count($ts) - 1;
492 $ts[$last - 0]['token'] == T_STRING
&&
493 $ts[$last - 1]['token'] == T_DOUBLE_COLON
&&
494 $ts[$last - 2]['token'] == T_STRING
) {
496 /* Class::constant */
498 /* $object has to exist and has to be a object */
499 $classname = $ts[$last - 2]['value'];
501 if (!class_exists($classname)) {
502 $this->addError(sprintf('Class \'%s\' doesn\'t exist', $classname));
505 $constname = $ts[$last - 0]['value'];
508 $c = new ReflectionClass($classname);
509 if (!$c->hasConstant($constname)) {
510 $this->addError(sprintf("Class '%s' doesn't have a constant named '%s'",
511 $classname, $constname));
514 } else if ($last == 0 &&
515 $ts[$last - 0]['token'] == T_VARIABLE
) {
519 $varname = $ts[$last - 0]['value'];
521 if (!isset($GLOBALS[ltrim($varname, '$')])) {
522 $this->addError(sprintf('Variable \'%s\' is not set', $varname));
527 $need_more = count($braces);
529 if ($need_more ||
';' === $token) {
534 $eval = "return ".$eval;
537 /* add a traling ; if necessary */
538 if ($need_semicolon){
549 function addError($error)
551 $this->errors
[$error] = '';
556 return !empty($this->errors
);
561 return array_keys($this->errors
);