(PHP 5 <= 5.0.4)
php_check_syntax — Check the PHP syntax of (and execute) the specified file
Performs a syntax (lint) check on the specified filename testing for scripting errors.
This is similar to using php -l from the commandline except that this function will execute (but not output) the checked filename.
For example, if a function is defined in filename, this defined function will be available to the file that executed php_check_syntax(), but output from filename will be suppressed.
Hinweis:
For technical reasons, this function is deprecated and removed from PHP. Instead, use php -l somefile.php from the commandline.
The name of the file being checked.
If the error_message parameter is used, it will contain the error message generated by the syntax check. error_message is passed by reference.
Returns TRUE if the lint check passed, and FALSE if the link check failed or if filename cannot be opened.
| Version | Beschreibung | 
|---|---|
| 5.0.5 | This function was removed from PHP. | 
| 5.0.3 | Calling exit() after php_check_syntax() resulted in a Segfault. | 
| 5.0.1 | error_message is passed by reference. | 
php -l somefile.php
Das oben gezeigte Beispiel erzeugt eine ähnliche Ausgabe wie:
PHP Parse error: unexpected T_STRING in /tmp/somefile.php on line 81
I've given it some thought and rewritten my function to take full advantage of the CLI -l option (that's lower L). It requires that you enable error reporting via your own php.ini file (which you should edit the function to apply) otherwise the return result is a worthless "Error parsing".
Anyway, I hope this is useful for someone. I'm sure it could use improvement, so use at your own risk. Demo here:
http://kevinpeno.com/projects/php_syntax_check.php
<?php
/**
*    Check Syntax
*    Performs a Syntax check within a php script, without killing the parser (hopefully)
*    Do not use this with PHP 5 <= PHP 5.0.4, or rename this function.
*
*    @params    string    PHP to be evaluated
*    @return    array    Parse error info or true for success
**/
function php_check_syntax( $php, $isFile=false )
{
    # Get the string tokens
    $tokens = token_get_all( '<?php '.trim( $php  ));
    
    # Drop our manually entered opening tag
    array_shift( $tokens );
    token_fix( $tokens );
    # Check to see how we need to proceed
    # prepare the string for parsing
    if( isset( $tokens[0][0] ) && $tokens[0][0] === T_OPEN_TAG )
       $evalStr = $php;
    else
        $evalStr = "<?php\n{$php}?>";
    if( $isFile OR ( $tf = tempnam( NULL, 'parse-' ) AND file_put_contents( $tf, $php ) !== FALSE ) AND $tf = $php )
    {
        # Prevent output
        ob_start();
        system( 'C:\inetpub\PHP\5.2.6\php -c "'.dirname(__FILE__).'/php.ini" -l < '.$php, $ret );
        $output = ob_get_clean();
        if( $ret !== 0 )
        {
            # Parse error to report?
            if( (bool)preg_match( '/Parse error:\s*syntax error,(.+?)\s+in\s+.+?\s*line\s+(\d+)/', $output, $match ) )
            {
                return array(
                    'line'    =>    (int)$match[2],
                    'msg'    =>    $match[1]
                );
            }
        }
        return true;
    }
    return false;
}
//fixes related bugs: 29761, 34782 => token_get_all returns <?php NOT as T_OPEN_TAG
function token_fix( &$tokens ) {
    if (!is_array($tokens) || (count($tokens)<2)) {
        return;
    }
   //return of no fixing needed
    if (is_array($tokens[0]) && (($tokens[0][0]==T_OPEN_TAG) || ($tokens[0][0]==T_OPEN_TAG_WITH_ECHO)) ) {
        return;
    }
    //continue
    $p1 = (is_array($tokens[0])?$tokens[0][1]:$tokens[0]);
    $p2 = (is_array($tokens[1])?$tokens[1][1]:$tokens[1]);
    $p3 = '';
    if (($p1.$p2 == '<?') || ($p1.$p2 == '<%')) {
        $type = ($p2=='?')?T_OPEN_TAG:T_OPEN_TAG_WITH_ECHO;
        $del = 2;
        //update token type for 3rd part?
        if (count($tokens)>2) {
            $p3 = is_array($tokens[2])?$tokens[2][1]:$tokens[2];
            $del = (($p3=='php') || ($p3=='='))?3:2;
            $type = ($p3=='=')?T_OPEN_TAG_WITH_ECHO:$type;
        }
        //rebuild erroneous token
        $temp = array($type, $p1.$p2.$p3);
        if (version_compare(phpversion(), '5.2.2', '<' )===false)
            $temp[] = isset($tokens[0][2])?$tokens[0][2]:'unknown';
        //rebuild
        $tokens[1] = '';
        if ($del==3) $tokens[2]='';
        $tokens[0] = $temp;
    }
    return;
}
?>
Hi again, here is my last contribution to the subject : this php_syntax_error() function returns false if there is no syntax error in $code, or an array($message, $line) if there is one (idea borrowed from kevin's code) .
For exemple, php_syntax_error(' DELIBERTE PHP ERROR; ') returns array('unexpected T_STRING', 1) ;)
Please note that the dead code sandbox IS important. A "return" at the beginning of the evaluated string can easily be broken: try eval('return; function strlen(){}') versus eval('if(0){function strlen(){}}').
<?php
function php_syntax_error($code)
{
    $braces = 0;
    $inString = 0;
    // First of all, we need to know if braces are correctly balanced.
    // This is not trivial due to variable interpolation which
    // occurs in heredoc, backticked and double quoted strings
    foreach (token_get_all('<?php ' . $code) as $token)
    {
        if (is_array($token))
        {
            switch ($token[0])
            {
            case T_CURLY_OPEN:
            case T_DOLLAR_OPEN_CURLY_BRACES:
            case T_START_HEREDOC: ++$inString; break;
            case T_END_HEREDOC:   --$inString; break;
            }
        }
        else if ($inString & 1)
        {
            switch ($token)
            {
            case '`':
            case '"': --$inString; break;
            }
        }
        else
        {
            switch ($token)
            {
            case '`':
            case '"': ++$inString; break;
            case '{': ++$braces; break;
            case '}':
                if ($inString) --$inString;
                else
                {
                    --$braces;
                    if ($braces < 0) break 2;
                }
                break;
            }
        }
    }
    // Display parse error messages and use output buffering to catch them
    $inString = @ini_set('log_errors', false);
    $token = @ini_set('display_errors', true);
    ob_start();
    // If $braces is not zero, then we are sure that $code is broken.
    // We run it anyway in order to catch the error message and line number.
    // Else, if $braces are correctly balanced, then we can safely put
    // $code in a dead code sandbox to prevent its execution.
    // Note that without this sandbox, a function or class declaration inside
    // $code could throw a "Cannot redeclare" fatal error.
    $braces || $code = "if(0){{$code}\n}";
    if (false === eval($code))
    {
        if ($braces) $braces = PHP_INT_MAX;
        else
        {
            // Get the maximum number of lines in $code to fix a border case
            false !== strpos($code, "\r") && $code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n");
            $braces = substr_count($code, "\n");
        }
        $code = ob_get_clean();
        $code = strip_tags($code);
        // Get the error message and line number
        if (preg_match("'syntax error, (.+) in .+ on line (\d+)$'s", $code, $code))
        {
            $code[2] = (int) $code[2];
            $code = $code[2] <= $braces
                ? array($code[1], $code[2])
                : array('unexpected $end' . substr($code[1], 14), $braces);
        }
        else $code = array('syntax error', 0);
    }
    else
    {
        ob_end_clean();
        $code = false;
    }
    @ini_set('display_errors', $token);
    @ini_set('log_errors', $inString);
    return $code;
}
?>
While developing an app where I have to include PHP files written by a user, I came across the following problem:
I used "php -l somefile.php" to check the syntax of the file I was about to include and if it passed, I would include it - so far so good. But in some test cases, the file I was including would have other includes/requires inside it. If one of these was invalid, then I would still get the parse error that I was trying to avoid.
I got round it using this:
<?php
    function CheckSyntax($fileName, $checkIncludes = true)
    {
        // If it is not a file or we can't read it throw an exception
        if(!is_file($fileName) || !is_readable($fileName))
            throw new Exception("Cannot read file ".$fileName);
        
        // Sort out the formatting of the filename
        $fileName = realpath($fileName);
        
        // Get the shell output from the syntax check command
        $output = shell_exec('php -l "'.$fileName.'"');
        
        // Try to find the parse error text and chop it off
        $syntaxError = preg_replace("/Errors parsing.*$/", "", $output, -1, $count);
        
        // If the error text above was matched, throw an exception containing the syntax error
        if($count > 0)
            throw new Exception(trim($syntaxError));
        
        // If we are going to check the files includes
        if($checkIncludes)
        {
            foreach(GetIncludes($fileName) as $include)
            {
                // Check the syntax for each include
                CheckSyntax($include);
            }
        }
    }
    
    function GetIncludes($fileName)
    {
        // NOTE that any file coming into this function has already passed the syntax check, so
        // we can assume things like proper line terminations
            
        $includes = array();
        // Get the directory name of the file so we can prepend it to relative paths
        $dir = dirname($fileName);
        
        // Split the contents of $fileName about requires and includes
        // We need to slice off the first element since that is the text up to the first include/require
        $requireSplit = array_slice(preg_split('/require|include/i', file_get_contents($fileName)), 1);
        
        // For each match
        foreach($requireSplit as $string)
        {
            // Substring up to the end of the first line, i.e. the line that the require is on
            $string = substr($string, 0, strpos($string, ";"));
            
            // If the line contains a reference to a variable, then we cannot analyse it
            // so skip this iteration
            if(strpos($string, "$") !== false)
                continue;
            
            // Split the string about single and double quotes
            $quoteSplit = preg_split('/[\'"]/', $string);
            
            // The value of the include is the second element of the array
            // Putting this in an if statement enforces the presence of '' or "" somewhere in the include
            // includes with any kind of run-time variable in have been excluded earlier
            // this just leaves includes with constants in, which we can't do much about
            if($include = $quoteSplit[1])
            {
                // If the path is not absolute, add the dir and separator
                // Then call realpath to chop out extra separators
                if(strpos($include, ':') === FALSE)
                    $include = realpath($dir.DIRECTORY_SEPARATOR.$include);
            
                array_push($includes, $include);
            }
        }
        
        return $includes;
    }
?>
This checks as many of the includes inside the file as it possibly can without executing anything.
My previous code was buggy sorry, here is an update (thanks phprockstheworld). I can't find a way to break the dead code sandbox. Who can ?
<?php
function eval_syntax($code)
{
    $braces = 0;
    $inString = 0;
    // We need to know if braces are correctly balanced.
    // This is not trivial due to variable interpolation
    // which occurs in heredoc, backticked and double quoted strings
    foreach (token_get_all('<?php ' . $code) as $token)
    {
        if (is_array($token))
        {
            switch ($token[0])
            {
            case T_CURLY_OPEN:
            case T_DOLLAR_OPEN_CURLY_BRACES:
            case T_START_HEREDOC: ++$inString; break;
            case T_END_HEREDOC:   --$inString; break;
            }
        }
        else if ($inString & 1)
        {
            switch ($token)
            {
            case '`':
            case '"': --$inString; break;
            }
        }
        else
        {
            switch ($token)
            {
            case '`':
            case '"': ++$inString; break;
            case '{': ++$braces; break;
            case '}':
                if ($inString) --$inString;
                else
                {
                    --$braces;
                    if ($braces < 0) return false;
                }
                break;
            }
        }
    }
    if ($braces) return false; // Unbalanced braces would break the eval below
    else
    {
        ob_start(); // Catch potential parse error messages
        $code = eval('if(0){' . $code . '}'); // Put $code in a dead code sandbox to prevent its execution
        ob_end_clean();
        return false !== $code;
    }
}
<?PHP
// Think about shell-command escaping if you`re using user-input
function php_check_syntax($file,&$error) {
  exec("php -l $file",$error,$code);
  if($code==0)
    return true;
  return false;
}
?>
Note: This is UNIX
Note: If your environment-variable PATH is not set correctly, you will need to insert the path to php (like /usr/local/bin/php)