PHPLint Reference Manual

PHP source parser and validator
with extended syntax






Version: 0.8_20090517
Copyright 2005-2009 by icosaedro.it di Umberto Salsi
phplint@icosaedro.it
www.icosaedro.it/phplint/





Index

Introduction
Basic features
Packages
Importing modules
Importing packages
Constants
Global variables
Types
Arrays
Control structures
Functions
Type compatibility
Typecasting
Predefined constants
Predefined superglobals
Classes
Classes (PHP 4)
Classes (PHP 5)
Recursive declarations
Exceptions
Documentator
Usage
An example
How To...
Reserved keywords
To-do list
Memorandum
License
The program
Changes
References
Syntax
 

Introduction

PHPLint is a parser and semantic validator for PHP programs. Moreover, PHPLint enhance the syntax of the PHP language with transparent extended syntax (or meta-code) that can drive the parser to even more strict checking. The PHPLint Documentator, a tool to generate automatically the documentation about a PHP source program, is described in a separate document (see www.icosaedro.it/phplint/documentator.html).

The main application of the PHP language is to provide dynamic contents to the WEB pages. Most of that pages are really simple, and typically they involve the parsing of the HTTP request, a connection to the data base to retrieve data, and then the presentation of the results to the user. These applications cannot really benefit from PHPLint. Rather, the applications where PHPLint may reveal to be really useful are the complexes one, where much code and included libraries are involved, and where it would be difficult to track down formal and conceptual errors. In the next chapters we will explain how PHPLint works and how you can get the best results programming with it.

A summary of the implemented features:

PHPLint comes in two forms: a stand-alone program you may execute on your computer, and a WEB version that allows you to try your code on-line. See the chapter The program for details.

 

Basic features

PHP is a great programming language suitable for rapid development of WEB applications and other programs. However, PHP is too "liberal", and lets the programmer to do many dangerous things. Some problems are detected by the interpreter itself in parse phase. Some other are detected only if the interpreter goes through the bugged code at run-time. Other errors are not detected at all, or reveal them only in particular circumstances.

Several problems the PHP parser and interpreter cannot detect can be detected by PHPLint. The list of the should give you an idea of all the checks that PHPLint can do.

The PHP language is weak-typed, that is the programmers can freely mix data of different type (numbers, strings of characters, etc.) and different structure (objects, arrays, etc.). The language takes care to perform automatically and at run-time any conversion that may be required based on the context were these data appears. This feature is also known as type juggling. Unfortunately, sometimes type juggling brings to unexpected results that arise only on particular combination of values, and it may be very difficult to discover were the problem originates.

So, one of the main goals PHPLint tries to achieve is to turn a weak-type language as PHP is into a strong-typed one, but with a minimal impact on the programming style of the typical PHP programmer. PHPLint detects automatically the type of any element of the language, be it a constant, a variable or a function, and then it ensures this element be handled according to it type: numbers go with numbers, arrays with arrays and so on. The following list should give an idea of the strategies PHPLint follows in order to detect the type of an expression:

Guessing the type from the expression. PHPLint can guess the type of an expression tracking the propagation of the literal constants through each operator and each variable. For example, it is evident from this chunk of code

class A {
    public $count = 0;  # $count is an int
}

function f()
{
    $a = 123;     # ==> $a is an int
    $b = 2*45;    # ==> $b is another int
    $c = $a + $b; # ==> $c is int
    $arr[$a] = "hello!";  # ==> $arr is an array of strings
                          # with int index
    return $c;    # this functions returns int
}

that the variables $count, $a and $b are of type integer and that the function f() does not take arguments and returns a value of type integer. The guess of the type of the variable $c requires to recall one interesting feature of PHP: when the intermediate result of an evaluation can't fit a 32-bit integer number, the value is automatically converted to a floating point number. That seems to be a limitation on any guess we can do simply parsing the source, since we can't know how big such a variable will become at run-time. However, we can nearly be sure that, in a strong-typed language, the variable $c would be declared of type integer by the programmer, and just so PHPLint will do. The operators - and * are treated in the same way.

Guessing the type from the typecast. PHP don't provide an explicit division operator between integer numbers, and the division operation / might result in a float value also if the arguments are both integer numbers. For example, $x/2 would give the integer value 8 for $x=16 and would give the floating point value 8.5 for $x=17. In general, since the value of the arguments are known only at runtime, PHPLint assumes that a division always returns a float number. If this is not the case, the programmer must to provide an explicit typecast:

$c = (int) ($a / $b);

Not only this typecast is required to drive PHPLint to the correct guess, but it is also a good programming practice if the required result is intended to be an integer number.

The values returned by the WEB client are collected into the superglobal arrays $_GET, $_POST and $_REQUEST. These arrays have a string as index and their elements are of type mixed. If the expected value is a string, a value conversion type-cast operator to string is required (see chapters Typecasting and Predefined superglobals for details):

$your_name = (string) $_POST['your_name'];

If the expected value is the result of the selection of multiple options in a pop-up menu, a formal type-cast to array is required:

$colors = /*.(array[int]string).*/ $_POST['colors'];

so that $colors results to be an array of strings with integer index.


WARNING We can't trust data coming from the client browser: there is no garanties that a parameter expected to be an array of strings actually be what we expect. The parameter might be missing, or it might be a string or even an array of arrays. The example above (multiple selections from a menu) should then be re-written in a safer way as follows:
$colors = /*. (array[int]string) .*/ array();
if( is_array( $_POST['colors'] ) )
    foreach(/*. (array[]mixed) .*/ $_POST['colors'] as $v)
        $colors[] = (string) $v;
Moreover, the resulting array $colors of the selected colors should now be checked for valid colors and possible duplicated values.

Guessing the type from the foreach statement. The foreach statement can set one or two variables, that is the key and the value of each element of the array we are scanning:

$a = array("a", "b", "c");
# $a is an array of strings with integer index 0, 1 and 2

foreach($a as $v){
    # $v results to be a string
}

foreach($a as $k => $v){
    # $k results to be an int
    # $v results to be a string
}

NOTE. Once the type of a variable has been guessed by PHPLint, this variable must be always used accordingly to its type. PHPLint does not allow for automatic type/value conversion: for example, numbers and strings cannot be mixed together. Any conversion between a value of one type to the value of another type must take place by an explicit type-cast operator. PHPLint provides type-cast operators that perform either an actual conversion of the value at run-time (for example, (int) applied to a string) and type-cast operators that only formally convert the type - see the chapter Typecasting for details.

 

Packages

In the PHPLint terminology, a package is simply any PHP file the PHPLint program can parse. From a package PHPLint gathers the informations that follow:

Packages can range from simple WEB pages containing only few PHP instructions, to complex library packages exporting several items toward the client packages. A project, or application, or WEB application, can consist of several packages. The typical structure of a packages might look something like this:

<?
/*.
    DOC   Skeleton of a typical package.

    <@package Skeleton>
    <@author Umberto Salsi>
    Some long description here.  Some long description here.  Some long
    description here.  Some long description here.  Some long description
    here.  Some long description here.  Some long description here.
    Some long description here.  Some long description here.  Some long
    description here.
.*/

#
# REQUIRED MODULES:
#
/*.  require_module 'standard';
     require_module 'pgsql';
     require_module 'session';  .*/

#
# REQUIRED PACKAGES:
#
require_once 'SiteConfig.php';
require_once '../php-libs/MimeMail.inc';

#
# PRIVATE ITEMS:
#

/*. private .*/ define("MAX_LOGGED_USERS", 20);
/*. private .*/ class SessionFile { ... }
/*. private .*/ $current_session = /*. (SessionFile) .*/ NULL;

/*. private SessionFile .*/ function NewSessionFile($max_size = 10000)
{ ... }


#
# EXPORTED ITEMS:
#

define("MAX_USER_NAME", 20);
class User { ... }
$loggedUsers = /*. (array[int]User) .*/ array();

/*. void .*/ function UpdateLoggedUsers() { ... }

/*. User .*/ function NewUserSession() { ... }

#
# INITIALIZATION CODE:
#

$current_session = NewSessionFile();
UpdateLoggedUsers();

?>

The /*. private .*/ attribute is an extension of the PHPLint meta-code. The private items of a package can be used only inside the package itself. PHPLint raises an error if a private item of a package is used or re-defined inside another package. Private items are listed in the documentation generated by the PHPLint Documentator, so to make the programmer aware of that global name-space "pollution".

 

Importing modules

Several extension modules are provided with the base PHP interpreter, and many other are available from other sources (mainly from the PECL project). These extensions may or may not be available in our PHP installation, so PHPLint provides a way to specify which extensions our program actually requires. For example, strlen() is provided by the standard module (normally always available), session_start() is provided by the module session, and preg_match() comes from pcre. PHPLint checks if these modules are actually used, and reports them on the final documentation generated automatically.

The PHPLint extended syntax, or meta-code for short, lets the programmer to give some insight about its code that drive PHPLint to an even more strict checking of variables and of functions. Since that meta-code isn't valid PHP code, it must be enclosed inside a comment /*. .*/. Note that there is exactly one period after the first asterisk, and another period before the closing asterisk. Since the PHPLint meta-code is enclosed in a comment, it will not interfere with the PHP interpreter.

The first usage of the PHPLint meta-code we'll describe is the meta-statement require_module 'MODULE' where the MODULE is the name of the extension module required. With this statement we tell to PHPLint which extension modules are required by our program. The name of the module MUST appear inside a single-quoted string; expressions aren't allowed. This meta-code statement MUST appear at the scope level "zero", i.e. it cannot be inside a function or inside a class declaration. For example:

<?php
/*****************************
 *  My beautiful WEB page.   *
 *****************************/

/*. require_module 'standard';
    require_module 'pcre';
    require_module 'mysql'; .*/

...rest of the program here...
?>

PHPLint will parse the files of the modules located in the directory modules/ of the distributed package. Each module is a collection of declarations of constants, functions and classes of the corresponding module, as reported by the PHP manual. Without these modules PHPLint would be unaware of which items are available, and it would raise an error for every entity it don't know, even for a simple standard function as strlen().

The module 'standard' is the library of resources that should always be available in every installation of PHP. In this module functions like strlen(), mail() and trigger_error() are defined. The other modules might or might not be present in your installation of PHP: the function phpinfo() gives the list of the modules currently available in your system. As an alternative you can use this simple program to display which modules are actually available in your system:

<?php print_r( get_loaded_extensions() ); ?>
For example, on my system this program displays:
Array
(
    [0] => tokenizer
    [1] => standard
    [2] => session
    [3] => posix
    [4] => pcre
    [5] => mbstring
    [6] => ftp
    [7] => sockets
    [8] => pgsql
)

The function extension_loaded('MODULE_NAME') returns TRUE if the given module is available, so that you may check at run-time the environment of your program.

Near to the end of its report, PHPLint will list a summary of all the modules that are required by the package and the packages it import, something like this:

...
?: notice: required module: pgsql
?: notice: required module: regex
?: notice: required module: standard
Overall test results: 3 errors, 10 warnings.

From this output we learn that the package just parsed requires the modules pgsql, regex and standard. This information might be very useful in the deployment phase of the application, since the program would not work if the PHP interpreter on the target system lacks some these modules.

A package can include other packages through the require_once statement (see the next chapter), and in turn these included files might require even more modules and packages. Every module and every package is always parsed only once, and it is ignored in the following inclusions.

The complete list of the modules provided with PHPLint is available in a separate document: .

 

Importing packages

PHPLint follows recursively the files that are imported through the PHP statement require_once EXPR; where EXPR is a string expression giving the file name of the file to be included. The included file, in turn, might require several other modules and packages. The require_once statement must appear at scope level "zero" of the program, and the expression giving the file name must be a simple expression of strings that can be evaluated "statically". Expressions involving variables and functions are not allowed. Some typical examples:

define("LIBS", "../libs/");

/* These packages are parsed recursively: */
require_once LIBS . "db_access.php";
require_once LIBS . "users_table.php";
require_once "../frames/frame-top.php";

/* These packages ARE NOT PARSED recursively because the expression
   giving the file name cannot be evaluated statically: */
require_once "lang-$lang.php";
require_once $_SERVER['DOCUMENT_ROOT'] . "/borders.php";

/* Files imported with the require|include|include_once statements
   ARE NOT PARSED recursively: */
require "borders/red.inc";
include "borders/green.inc";
include_once "borders/blue.inc";

Only the require_once statement is parsed recursively. The statements include, include_once and require are scanned by PHPLint but the files specified are not parsed. To parse also these files, you can either change to the statement require_once or add these files to the list of files to be parsed in the command line that started PHPLint.

 

Constants

The PHP statement define() allows to define constants, that is symbolic names whose value is defined once for all and cannot be changed later. The first argument of this function is the name of the constant provided as a string; the second argument is its value. For example:

define("PI", 3.141592);

The name of the constant can be any expression of the type string, but typically it is simply a literal string like in our example. PHPLint raises an error if the name cannot be evaluated statically, so it cannot contain variables nor functions, and raises an error if the name of the constant isn't a valid identifier. Valid identifiers are those composed of letters, digits and the underscore "_"; the first character cannot be a digit. The name of a constant is case-sensitive, so "Pi" and "pi" are different names.

The value is an expression of simple type that can be evaluated statically: a boolean, an int, a float or a string. Also a generic NULL value and the NULL string are allowed, for example:

define("UndefinedValue", NULL);
define("DefaultName", /*. (string) .*/ NULL);

A constant cannot be re-defined.

A constant can be defined inside a function, but PHPLint raises a warning because it cannot ensure the correct usage of its definition: the constant might be used before the function defining it be called, or the function might be called more than once causing a run-time error from the PHP interpreter. The constants cannot be declared inside the classes for the same reason. So, the only remaining place where a constant can be declared is the global scope of the package.

A constant that should not be exported toward the client packages should be declared as "private" to the package where it is defined. To this end, PHPLint provides a specific meta-code word:

/*. private .*/ define("MAX_USERS", 20);

A private constant cannot be re-defined nor it can be used in any other package importing this package. The private constants are reported by the PHPLint Documentator into the list of the Private items of the package, since they still occupy their position into the name-space.

 

Global variables

The global variables are those variables defined at the global scope of the package. "Defining" a variable simply means assigning it a value, like in these examples:

$n = 123;
$src = fopen("data.txt", "r");
$matrix[1][$n] = $n + 1;
$redColor = "red";
$colors = array($redColor, "green", "blue");
$adminName = "admin";
$userName = /*. (string) .*/ NULL;

In the last line you can see another example of the PHPLint meta-code: since according to the PHPLint types model, a string can be either a sequence of characters or the NULL value, and since the NULL alone isn't a well defined type, a formal type-cast to the string type is required in order to specify the type of the expression assigned to the variable $userName (more on this argument in the following chapters).

Global variables that the package don't want to export toward other packages should be qualified as "private" using the PHPLint meta-code:

/*. private .*/ $dbConn = pg_connect("dbname=SiteDB");

These private variables are reserved by the package and cannot be used by client packages. PHPLint raises an error if a private variable is used or re-defined in another package.

The private variables are reported into the list of the Private items by the PHPLint Documentator. Since the private variables still occupy space inside the global name-space, the programmer should take care to limit their usage, possibly choosing an appropriate naming schema that prevent collisions. A common practice is to prefix their name with the package name $ThePkg_theVar.

 

Types

How PHPLint enhance the type model of PHP

The PHP language provides some basic data types: numbers, strings of characters, arrays and classes. Every expression and every variable has its own type. Although PHP allows to mix freely data of different types, abusing of this feature might compromise the efficiency, the safety and the security of the program. Moreover, the long-run aim of the PHPLint is to validate sources ready to be compiled into an efficient and compact executable code.

So, PHPLint takes the type model already existent in the PHP language, and adds some restrictions on how these types can be combined together. For example, we can multiply two numbers, but adding an object to a boolean value generally don't make much sense. PHP allows to add a number to a string $str + $num, but this implies a conversion of the string to a number. Since converting data from a type to another takes some time, and since there might be some safety and security issues involved, in this case PHPLint requires and explicit typecast operator $str + (int)$num just to make the programmer aware that a conversion takes place. The presence of too much conversions in a source is the clear symptom that the types choosen by the programmer does not fit well the problem, and the source should be rewritten in a better way.

How PHPLint handle the PHP types

PHP provides the type null that has only one value, namely the constant NULL. Currently the NULL value is seldom used by PHP programmers. On the contrary, PHPLint implement a different model where some data types take the NULL value as a special value. These are the types commonly known in other programming languages as dynamically allocated data types, namely string, resource, array and object. So, for example, if $str is a variable of the type string, this variable can be either the NULL value or an actual string of zero or more characters.

NOTE. These types are called dynamic in this documentation because most programming languages implement them as pointers to dynamically allocated memory storage. A NULL pointer simply point to nothing and its typical value is zero. Actually, in PHP all the variables are dynamically allocated.

In PHP the data types int float string etc. are keywords the programmer can use only inside the type-cast operator, but these keywords cannot be used elsewere (apart the limited case of the type hinting in function parameters). Since a suitable syntax isn't provided by the language, PHPLint introduces its own meta-code to declare the type of a variable or an expression; remember that this meta-code must always be enclosed inside a comment /*. the_type .*/:

Using a simplified EBNF notation, a type declaration using the PHPLint meta-code can be defined as follows:

x_type = "void" | "boolean" | "int" | "float" | "string" | x_array_type | "resource" | "mixed" | "object" | class_name ;

The syntax of the array type will be explained in the chapter Arrays.

Values and variables of different types can be mixed only in few controlled ways. The chapter Type compatibility gives the rules that PHPLint apply when variables or expressions of different type are assigned together or passed as arguments to a function.

The chapter Typecasting explain how an expression of a type can be converted to an expression of another type.

The boolean type

There are only two possible literal values of the type boolean: they are the constants FALSE and TRUE. The lower-case version of these constants false and true are allowed but their usage is discouraged, since constants are commonly written in upper-case letters only. bool is allowed as an abbreviation for boolean.

The int type

The integer numbers can be either positive and negative. The range is limited: typically, on 32-bit processors the minimum value is -2^31 and the maximum value is 2^31-1. This chunk of code prints the number of bits available on the computer running the program:

/*. int .*/ function size_of_int()
{
    static $n = 0;

    if( $n > 0 )  return $n;
    $n = 1;
    $i = 1;
    while( $i > 0 ){
        $i <<= 1;
        $n++;
    }
    return $n;
}

echo "int size = ", size_of_int(), " bits\n";
# ==> int size = 32 bits

While evaluating an expression, if an intermediate result becomes too big to be represented as an int, the PHP interpreter automatically promote this value to a floating-point precision number. This feature provides an effective way to detect overflow in integer expressions, for example:

$count = $count + $incr;
if( is_float($count) )
    trigger_error("overflow on \$count", E_USER_ERROR);
integer is allowed as alternative name for int.

The float type

The floating-point numbers include the fractional part, and they can hold values much bigger than int, but with limited precision. Float numbers are seldom used in WEB applications; sometimes they are used to store monetary values, but the programmer must take care of the rounding problem, since that might bring to unexpected results also for simple operations. For example, 0.57-0.56 gives 0.0099999999 rather than the expected 0.01.

double is allowed as an alternative name for float.

The string type

According to PHPLint, the type string is something that contain zero, one or more bytes, or possibly the value NULL. The value NULL is handled by PHP mostly like the empty string, so for example $s=NULL; echo $s; will display nothing. The following function will print a string as a proper "PHPLint-compliant" literal string whatever its value is, including the NULL value, and can be useful for debugging and logging purposes:

/*. string .*/ function stringToLiteral(/*. string .*/ $s)
{
    if( $s === NULL )
        return "NULL";
    else
        return '"' . addcslashes($s, "\000..\037\\\"\$\177..\377") . '"';
}
echo StringToLiteral(NULL);  # ==> NULL
echo StringToLiteral("");    # ==> ""
echo StringToLiteral("\\x\"abc\n\x85{\$x}");
# ==> "\\x\"abc\n\205{\$x}"

The function addcslashes() converts all the non-ASCII printable characters to the corresponding escaped sequence. The double quote, the back-slash and the dollar character are converted to their escaped sequence, since they have a special meaning. Note that the special NULL value must be treated differently since the function addcslashes() would consider NULL as an empty string.

Double quoted literal strings can contain simple variables. In PHP 5 also objects of classes providing a __toString() method are allowed. These variables must already have a value and are accounted as "used":

"Found $n records looking for $name:\n"

Variables embedded inside double quoted strings cannot be dereferenced, and the curly brace syntax is not currently supported by PHPLint. For example PHPLint will raise an error if any of these forms is used:

"Element value is $arr[$i]"
"Property value is $obj->prop"
"{$x}${x}"
 

Arrays

PHP implements the arrays as hash tables whose index can be both integers and strings of characters, possibly mixed together in the same array. There are no restrictions on the type of the elements stored inside an array, and they can be all off different types inside the same array. Very simple to use, very powerful for quick-and-dirty programming, very dangerous.

Not surprisingly, PHPLint introduces some restriction on how an array can be defined and used.

The structured array type

Simply declaring that a value is of the type array don't give very much information to the parser. So, PHPLint allows a more accurate description of the array structure that provides the type of the index (int, string or both) and the type of the elements. The general syntax can be expressed in EBNF form as:

x_array_type = "array" [ index { index } x_type ] ;
index = "[" [ "int" | "string" ] "]" ;
array = "array" "(" [ element { "," element } ] ")" ;
element = [ expr "=>" ] expr ;

After the array keyword there may be one or more index descriptors that give the number and the type of each index, and at the end there is the type of the elements. Note that the index type may be only int, string or both if undefined. If the type of the elements is missing, their type is left undefined.


Index declaration Guessed type of the index
array[] int and string are both allowed
array[int] int
array[string] string
Element declaration Guessed type of the elements
array[...] unknown
array[...]int int number
array[...]ClassName object of the class ClassName
array[...][int]string array[int]string

WarningPHP allows to mix indexes of type int and of type string on the same array. However there is a quirk: as stated by the PHP manual, if a key (an index, in our discussion) is the standard representation of an integer, it will interpreted as such. Isn't unclear to me what the manual exactly mean as "standard representation" of an integer, but it gives this example: $a[8] and $a["8"] are the same element, while $a["08"] is dereferenced actually using the string "08" as an index. In general, different strings might be evaluated to indexes that collide with others integer numbers. So, be careful selecting the type of the index that best match your needs.

If you really need an associative array that maps both strings and integers to different values, the problem stated in the box above may be resolved adding a fixed string to the given index. For example, this code maps the key $key into the value $value:

$map[ "x" . $key ] = $value;

This ensures that different values of the $key, that can be either integer numbers or strings of characters, will be mapped into distinct elements of the array. Note too that the NULL string (a value allowed for a string according to the type model of PHPLint) would be mapped into "x" exactly as the empty string "": how to resolve this ambiguity is left as an exercise for the reader :-)

Building arrays element-by-element

PHPLint guesses the structure of a variable examining its usage. The presence of the operator "[" lets to guess that the variable is an array, the type of the expression giving the index gives the type of the index of the array, and the type of the expression assigned gives the type of the elements. In this example, the type of the variables is guessed from their usage:

$players[] = "Rick"; # Guess: $players is array[int]string
$players[] = "John";

$groups['beta'] = $players;  # Guess: $groups is
                             # array[string][int]string

$matrix[0][0] = 1.0;  # Guess: $m is array[int][int]float
$matrix[0][1] = 0.0;
/* ... */

$params["width"] = 10.0;  # Guess: $parmas is array[string]float
$params["height"] = 7.0;
$params["depth"] = 3.0;

The array constructor

The array() is a special construct of the PHP language that allows to define an array, its values and their indexes using only one instruction. The array() lets to assign either the elements than the index (or key) of each one. For example, the same arrays of the example above might be rewritten as:

$players = array("Rick", "John");
$groups = array( "beta" => $players );
$matrix = array( array(1.0, 0.0, 0.0),
                 array(0.0, 1.0, 0.0),
                 array(0.0, 0.0, 1.0));
$params = array("width" => 10.0, "height" => 7.0, "depth" => 3.0);

PHPLint parses the array() constructor to guess the type the resulting array. The first element of the array can contain an expression giving the key and an expression giving the value

array( KEY => VALUE, ...

This first element gives the structure of the array acconding to these rules:

Once the first element of the array constructor has been parsed, PHPLint scans the remaining elements. Each key/element pair found must match the type of the first pair. If a key does not match, a warning message is raised and the type of the index can be either int or string. If an element does not match the type of the first element a warning message is raised and the type of the elements of the array becomes mixed.

The empty array array() don't lets PHPLint to guess anything about the structure of the array, so the resulting type is a generic array[] with index and element types both unknown. To build an empty array of a specific type, you must use a formal type-cast operator (see chapter Typecasting):

$file_names = /*. (array[int]string) .*/ array();

The following table summarizes the rules PHPLint applyes trying to detect the structure of the array. $k is an expression giving a value of the type K (int or string). $e is an expression giving a value of the type E.

Array constructor Detected type Note
array() array[] The type of the index and the type of the elements are both unknown.
array($e, ...) array[int]E The type of the index is int and the first element will be stored at the offset 0. The type of the elements is the type of the expression $e. If more that one element is present, the others elements must be of the same type of $e and the keys, if they are present, must be of the type int.
array($k => $e, ...) array[K]E The type of the index is the type of the expression $k. K must be the type int or the type string. The type of the elements is the type of the expression $e. If more that one element is present, the others elements must be of the same type E and the keys, if they are present, must be of the type K.

Some examples (the comments reports the type of the resulting expression):

class A { }

$a0 = array();  // array[]
$a1 = array( 123, 456, 789 );  // array[int]int
$a2 = array( 123, 456, "xyz"); // array[int]mixed - WARNING: mixing
                               // elements of different types
$a3 = array( 1=>"one", 2=>"two", 8=>"eight");  // array[int]string
$a4 = array( array( new A("bye") ) ); // array[int][int]A
$x = $a4[0]; // array[int]A
$y = $a4[0][0]; // A

/*. array[int]A .*/ function BuildRow(/*.int.*/ $width)
{
    $row = array( new A() );
    for( $i=1; $i<$width; $i++ )  $row[$i] = new A();
    return $row;
}

/*. array[int][int]A .*/
    function BuildMatrix(/*.int.*/ $width, /*.int.*/ $height)
{
    $m = array( BuildRow($width) );
    for( $i=1; $i<$height; $i++ )  $m[] = BuildRow($width);
    return $m;
}

$matrix = BuildMatrix(5, 10);  // array[int][int]A
 

Control structures

PHPLint supports all the control structures of the PHP language, with the only exception of the old form that the PHP manual title Alternative syntax for control structures. In what will follow, a statement is either a single instruction or the compaund instruction "{...}" that may contain zero or more instructions. The empty statement ";" (simply a semicolon) is also allowed as statement.

if()

if( expr ) statement {elseif( expr ) statement} [else statement]
while( expr ) statement
do statement while( expr )
All the expr that appear here must results in a boolean value.

for()

for( expr_list; expr; expr_list ) statement
The expr_list may be either empty or a list of expressions. The expr may be either empty or a boolean expression.

while() and do...while()

while(expr) statement
do statement while(expr);
No much to say about these statements. As you might guess, the expression that control the execution of the loop must be of the type bool.

foreach()

foreach( expr as [ $key => ] [ & ] $value) statement
Here, expr must be either an expression of the type array of any structure, or an object of any class. $key and $value must be two variables. If expr is of the type array, the two variables must be compatible by assignment with the type of the key and the type of the elements of the array, respectively. If expr is an object, the key must be assignment compatible with string and the value must be mixed.

switch()

switch( expr ) {
case static_expr1: [ statements ]
case static_expr2: [ statements ]
...
default: [ statements ]
}
The value of the selector expression expr must be either int or string. Each case selector static_expr1, static_expr2, ... must be of the same type of the selector expression expr. But beware to the strings: PHP execute the comparison using the weak equality operator "==", so you might get unexpected results when a string looks like a number. For example, the string "1" will match the string "01".
Each case can be empty (i.e. no statements), or it can contain one or more statements. In this latter case, the last statement should be either a return or a break; if none of these two is present, PHPLint will raise a warning message. To prevent this message from being displayed, you may add the special meta-code statement /*. missing_break; .*/. Note that the statement is terminated by a meta-code semicolon character.
The branch default is optional, but if missing PHPLint will raise a warning message. To prevent this message, you may add the special meta-code statement /*. missing_default: .*/. Note that the branch is terminated by a meta-code colon character. Example:

switch( $i ){

    case 0:
        echo "do case 0";
        break;

    case 1:
    case 2:
        echo "do case 1 and 2";
        return;
    
    case 3:
        echo "do case 3";
        /*. missing_break; .*/

    case 4:
        echo "do case 4 and possibly 3";
        break;

    /*. missing_default: .*/
}

return

return;
return expr ;
This statement can be used only inside functions and methods (PHPLint restriction; PHP allows to "return" also from included files). If a return value is provided, it must be assignment-compatible with the type of the function or method. PHPLint looks at this statement to guess the signature of the function or method whether not provided as meta-code.

Moreover, PHPLint uses the fist "return" statement found in the function or method to guess the returned type. More on this topic in the chapter Functions.

 

Functions

A function is a chunk of code with a name and possibly some arguments. Once defined, a function can be called from any scope and cannot be re-defined. The functions cannot be nested (although PHP lets to do so, it is dangerous). Differently from the constants and the variables, the name of a function isn't case sensitive. However, PHPLint checks the right spelling and raises a warning if you call a function with a name that does not match exactly that was used in its declaration. So, for example, strlen() cannot be written as StrLen() nor as STRLEN().

NOTE. The general rule in PHPLint is that all the identifiers and the keywords are always case-sensitive and must be written exactly as defined in the PHP manual.

The signature of a function gives the type of the formal arguments and the type of the returned value, or void if the function does not return a value. The PHPLint meta-code lets you to provide the type of the returned value, the types of any mandatory formal argument, the type of the default formal arguments, and lets you to declare if the function can accepts a variable number of optional arguments. The general structure of the declaration can be described informally as follows:

/*. TYPE1 .*/ function funcName(
      /*. TYPE2 .*/ $mandatory1,   # mandatory args
      /*. TYPE3 .*/ $mandatory2,

      $default1 = EXPR1,           # default args
      $default2 = EXPR2

      /*. , args .*/ )             # optional args
{
    ...
}

Returned type

The type returned by the function (TYPE1 in the schema above) can be guessed by PHPLint parsing the first "return EXPR;" statement that appears in the source: the resulting type of the expression gives the type of the function; if the expression EXPR is missing, the function does not return anything, then its type is void.

function title(/*. string .*/ $t)
{
    echo "<h1>$t</h1>\n";
}
# Guessed signature is: void(string)


function mean(/*. float .*/ $a, /*. float .*/ $b)
{
    return 0.5*($a + $b);
}
# Guessed signature is: float(float,float)


function firstByte(/*. string .*/ $code)
{
    if( strlen($code) == 0 )
        return /*. (string) .*/ NULL;
    else
        return $code[0];
}
# Guessed signature is: string(string)

Although omitting some meta-code brings to a shorter source code, however it is better to provide an explicit type for the returned value: it is safer for a strict parsing, and it is more intelligible to the reader of the source.

Mandatory arguments

Every mandatory argument can have its type provided by the PHPLint meta-code. Omitting the explicit declaration of the type, PHPLint will raises a notice message and sets the type of the formal argument to mixed in the attempt to continue its work, but get prepared to see many warnings every time that argument appears inside an expression.

Default arguments

The type of a default argument can be guessed by the type of the expression giving its default value:

function f($x = 123){ ... }

Anyhow, you can also specify a type:

function f(/*. int .*/ $x = 123){ ... }

In this case the explicit type must be assignment-compatible with the default value. An explicit type is required when the type of the expression cannot be guessed by PHPLint. This is the case of the NULL value and of the empty array array() constructor: both require a formal type-cast (see the chapter Typecasting for details). Example:

function f(
    $conn = /*. (resource) .*/ NULL,
    $arr  = /*. (array[int]string) .*/ array()
){ ... }

Optional arguments

The special meta-code keyword args tells that the function takes a variable number of optional arguments that follow the mandatory arguments and the default arguments. For example, the function that follows takes one mandatory argument and a variable number of optional arguments:

/*.void.*/ function PrintArgs(/*.string.*/ $prompt /*. , args.*/)
{
    echo $prompt, "\n";
    $n = func_num_args();
    for($i = 1; $i <= $n; $i++ )
        echo (string) func_get_arg($i), "\n";
}

Please note that a comma character is required before the args meta-code, keyword.

Examples

These examples illustrates the relations among types, variables and function arguments:

$n = 123;           # Guess: $n is of type int
$s = "123";         # Guess: $s is of type string
$n = $s;            # ERROR: type mismatch: the left type is int,
                    #    while the right type is string
$n = (int) $s;      # Typecast prevents warning from PHPLint

PrintArgs($n, $s);  # ERROR: type mismatch on arg. no. 1

PrintArgs((string) $n, $s);
                    # Typecast prevent warning from PHPLint

function f($a, $b)  # Default signature: unknown(mixed, mixed)
{
    if( $a < 0 ) # Warning: comparing unknown type $a with 0
        return 1;   # Guess: f() returns int
    else
        return 0.0; # Warning: that looks to be a float!
}

/*. private float .*/ function fabs(/*.float.*/ $x)
{
    return ($x >= 0.0)? $x : -$x;
}

Functions that are private to the package and that cannot be used by other packages can be qualified as "private":

/*. private void .*/ function DoSomething(){ ... }

The private functions are reported by the PHPLint Documentator inside the list of the Private items of the package.

 

Type compatibility

As a general rule, PHPLint does not allow to mix values and variables of different types. So, for example, if $i is a variable of type int, PHPLint expects this variable appears inside an expression that involves only integer numbers.

PHPLint relaxes this strict compatibility rule with some exceptions that are detailed here. Moreover the chapter Typecasting explains how a value of given type can be converted into another type in a safe, controlled way.

Assignment

A right hand side (RHS) is the expression that generates a value, while a left hand side (LHS) is the entity that stores this value. The type of the RHS expression is checked against the type expected by the LHS in these cases:

The table below summarizes the rules PHPLint applies when a value of the type RHS is assigned (passed by value, etc.) to a variable of the type LHS:


  R H S
(unk.) (null) void bool. int fl. str. arr. mix. res. obj. (cl.)
L

H

S

(unknown) I W E W W W W W W W W W
(null) I - E E E E W W W W W W
void I E E E E E E E E E E E
boolean I E E - E E E E W E E E
int I E E E - E E E W E E E
float I E E E - - E E W E E E
string I - E E E E - E W E E E
array I - E E E E E A W E E E
mixed I - E - - - - - - - - -
resource I - E E E E E E W - E E
object I - E E E E E E W E - -
(class) I - E E E E E E W E E C

LEGEND:
I = Ignore. The RHS is of unknown type (for example, a variable never encountered before), so the assignment cannot be checked. No error message is raised, since an error message has been already raised once the RHS of unknown type was parsed.
E = Raise an error message.
W = Raise a warning message.
A = Arrays are compared index by index and element type by element type. The LHS index can be unknown or the LHS index and the RHS index must be both int or both string. The types of the elements must match as LHS and RHS. The match might raise a warning message or an error.

C = The RHS must be an object of the same class of the LHS, or a class derived from the the same class of the LHS, otherwise an error message is raised.

Comparison operators

All the comparison operators < <= == >= > generate a boolean value. For the sake of brevity, we will call the operators == and != equality operators. The PHP language applies some really complex rules and automatic conversions to the values being compared. For example:

"123" == 123 is TRUE because the string is automatically converted into an integer number;
NULL == "" is TRUE because the NULL value is equivalent to the empty string;
array() == NULL is TRUE;
0.57 - 0.56 == 0.1 is FALSE.

PHPLint simplifies these rules and forces a more consistent way of handling the comparisons. In the table below you may think at the first column as the left term of the comparison, and the second column gives the types that can appear in the right term of the comparison:


Type Comparison with == != < <= >= >
(unknown) Value of unknown type cannot be compared at all. These values of unknown type apper when PHPLint was unable to parse successfully the source code.
null The only expression that may generate a value of the type null is the constant NULL. This value can be compared with other types as listed below.
void This value returns from functions that... do not return a value, so it cannot be compared and PHPLint raises an error.
boolean A boolean value can be compared with another boolean value using the equality operators == and !=. Any other comparison operator raises an error.
int An int value can be compared with another int. If you need to compare an integer value versus a floating point number, it is responsability of the programmer to convert explicitly the int into float using the (float) typecast, something like if( $x < (float) $i ) ...
float A float value can be compared with another float using the operators < <= >= and >. Two floating point numbers cannot be compared with the equality operators - see the following box. Comparison against an integer number requires that the integer number be converted into float.
string Strings cannot be compared at all. Either use a strict comparison operator (see the next paragraph) or the function strcmp(). See the chapter How To... for some examples on how to translate an unsafe string comparison into a PHPLint-compliant safe string comparison.
array Arrays cannot be compared at all. See the next paragraph about the strict equality operators.
mixed A value of type mixed cannot be compared. Typically mixed values comes from predefined superglobal variables or standard functions that might return values of different types: always check the actual type of the value and apply the proper type-cast before the comparison.
resource A resource can be compared with another resource or with the NULL value using the equality operators.
object
(class)
An object can be compared with the NULL value using an equality operator. Two objects cannot be compared. See the next paragraph about the strict equality operators.

Note that a float value cannot be compared with an int value: either apply an (int) typecast to the float value, or apply a (float) typecast to the int value.

 
Warning

Floating point numbers should not be used as "bigger integers with decimals". Instead they are numbers with high range of values, but limited precision. The precision cannot be controlled in an effective way, so that simple calculations might give unpredictable results. For example, this logical expression gives FALSE:

0.57 - 0.56 == 0.01

This is the reason why PHPLint raises a notice messages on comparisons between floating point numbers. Do not use floating point numbers to store currency values that might be fractional, like euro-cents or dollar-cents because their limited precision can cause rounding errors and other unexpected results. Please read carefully the manual page www.php.net/manual/en/language.types.float.php.

 

Strict equality operators

The operator of strict equality === gives TRUE if and only if the operands are both of the same type and they have the "same value". If the two operands are of the same type, the "same value" concept may be expressed as follows:


Type Comparison with === or !==
boolean Gives TRUE if the operands are both FALSE or both TRUE.
int Gives TRUE if the two integer numbers are equal.
float Gives TRUE if the two floating point numbers are equal.
string Gives TRUE if the two strings are both NULL or they contain the same sequence of bytes. For example, "abc" === "ab"."c" gives TRUE.
array Gives TRUE if both the arrays are NULL or they contains the same index/value pairs in the same order, element by element; the index/value pairs are compared with the strict equality operator itself. For example, array(0, 1, 2) === array(0, 1, 2) gives TRUE because the arrays both contains the same index/value pairs in the same order. Instead, the comparison array(0, 1, 2) === array(0=>0, 2=>2, 1=>1) gives FALSE because, although the index/value pairs are the same, the elements are stored in memory in a different internal order.
mixed The result depends on the type of the two operands, and can be computed only at run-time.
resource Gives TRUE if the two operands are both NULL or are the same resource. For example, fopen("xyz", "r") === fopen("xyz", "r") gives FALSE because each call to the fopen() function returns a different file handle.
object
(class)
Gives TRUE if the two operands are both NULL or are the same object. Note that new ACLASS() === new ACLASS() always gives FALSE because the operands are two different instances of the class "ACLASS".

The operator of strict inequality !== just gives the logical negation of the strict equality operator ===: the result is TRUE only if the two operands are of different types or of different values.

Some functions of the standard library are formally declared to return a value of a given type, but in some cases they can return values of different type too. For example, the function fopen() normally returns a value of the type resource if the file was opened successfully, but the same function returns the boolean value FALSE when an error occurs. Here is the correct way to check for such error:

$f = @fopen($fn, "r");
if( $f === FALSE )
    die("fopen($fn, r): $php_errormsg");

Note that we cannot use the simple equality operator == since PHPLint would raise an error message because the two operands are of different types ($f is a resource, FALSE is a boolean value).

 

Typecasting

Value conversion operators

A value conversion operator is an internal function of the PHP interpreter that lets to convert a value of a given type to a value of another type. The general syntax is a sub-expression of the form:

(type) term

PHPLint allows for these value conversion operators:


Value conversion operator Type of the term Description
(int) boolean Gives 0 if the boolean term is FALSE, or 1 if TRUE.
(bool) int Gives FALSE if the value of the term is zero; gives TRUE for any other integer value.
(float) int Gives the floating-point representation of the integer number.
(string) int Gives the string representation of the integer number in decimal notation; a leading minus sign is prepended if negative.
(int) float Gives an integer number that is the integral part of the given floating-point number.
(string) float Gives a string representation of the given floating-point number in decimal notation. Note that in the conversion from the internal binary representation to the decimal representation we might loose precision.
(int) string Given a string that is the decimal representation of an integer number, gives the corresponding integer number. If the string is not a valid representation of an integer number, or it is a number too large, the result is an undefined integer value. If the string is NULL, the result is 0.
(float) string Given a string that is the decimal representation of a floating-point number, gives the corresponding floating-point number. If the string is not a valid representation of a floating-point number, or it is too large, the result is an undefined floating-point value. Note that in the conversion from the decimal representation to the internal binary representation we might loose precision. If the string is NULL, the result is 0.0.
(int)
(float)
number Here the term might be either a value of the type int or of the type float, so that the respective conversion is applied.
(bool)
(int)
(float)
(string)
mixed Since the type of the term is known only at run-time, the program should take care to detect the actual type of the value using one of the following functions and then apply the proper value conversion operator: is_bool(), is_int(), is_float(), is_number(), is_string(). Please note that is_string($s) will give FALSE if $s===NULL.
(string) object of some class In PHP 5 objects of classes providing a __toString() method can be converted to string.

Type conversion operators

A type conversion operator is a formal conversion of the type of a term. Neither the value of the term, nor its type are really changed. These type conversion operators are often required because some functions return a generic type mixed, array or object that does not fit the type of the expression where these function are called. Another typical case where a type conversion operator is required is to set the correct type for the NULL constant and the empty array array(): we already seen some examples in the chapter Functions.


Type conversion operator Type of the term Description
/*. (string) .*/
/*. (array) .*/
/*. (array ...) .*/
/*. (resource) .*/
/*. (object) .*/
/*. (
CLASS_NAME ) .*/
null The NULL value can be formally converted to any type for which PHPLint allows the NULL value (see chapter Types).
/*. (bool) .*/
/*. (int) .*/
/*. (float) .*/
/*. (string) .*/
/*. (array) .*/
/*. (array ...) .*/
/*. (resource) .*/
/*. (object) .*/
/*. (
CLASS_NAME ) .*/
mixed A value of the type mixed can be formally converted to any type. The program should take care to check the type of the value before to do the conversion using one of these functions: is_bool(), is_int(), is_float(), is_string(), is_array(), is_resource(), is_object(). The class to which an object belong to can be checked with the function is_a() (PHP 4) or the binary operator instanceof (PHP 5).
Note that the exact structure of an array cannot be checked easily at run-time, so that the correcness of the conversion is in charge to the internal logic of the program.
Note that a value of the type mixed might be NULL; you can check this value either using the logical expression $value===NULL or the function is_null($value); note that the original type to which this value belonged to cannot be guessed at run-time. Please, note the is_string(), is_array(), is_resource(), is_object() give FALSE if their argument is the value NULL.
/*. (array ... ) .*/ array This operator lets to declare the structure of a term of the generic type array. Note that this operator cannot be applied to an array of a known structure; an array of a given structure can be converted to an array of a different structure using a specific algorithm.
/*. (object) .*/
/*. (
CLASS_NAME ) .*/
object This operator convert the type of a generic object value to the instance of the given class name. Before to apply this operator, the program should check the actual class to which the object belong to using the instanceof binary operator (PHP 5) or the is_a() function (PHP 4).

Examples:
$abc = array("a", "b", "c");
$cba = /*. (array[int]string) .*/ array_reverse($a);
/* Reverse the order of the elements of the array $abc.
   Note that the formal type-cast conversion operator is
   required because the function array_reverse() returns
   a generic array, so loosing info about the actual
   structure of the resulting array. */

$obj = /*. (A) .*/ NULL;
/* This variable is an object initially NULL. Since NULL
   is not a well defined type, a formal type-cast is required. */

$arr = /*. (array[int]string) .*/ array();
/* An array of strings with int index, initially empty. Since
   the empty array does not allows to PHPLint to guess neither the
   type of the index nor the type of the elements, a formal
   type-cast is required. */


class A_DOG { function bark(){} }
class A_CAT { function miaow(){} }

/*. void .*/ function Play(/*. object .*/ $obj)
/*
    Since this function accepts an object of different
    classes, type conversion operators will be required.
*/
{
    if( $obj instanceof A_DOG ){
        echo "It's a dog";
        $dog = /*. (A_DOG) .*/ $obj;
        $dog->bark();
        /* NOTE: $obj->bark() would be correct for PHP, but
           it would not for PHPLint, since a generic object
           does not has the method bark().  That's why a new
           variable $dog needs to be introduced. */
    } else if( $obj instanceof A_CAT ){
        echo "It's a cat";
        $cat = /*. (A_CAT) .*/ $obj;
        $cat->miaow();
    } else {
        echo "It's an unexpected animal";
    }
}

Play(new A_DOG());
Play(new A_CAT());

The last example involving cats and dogs is a typical example of how programs should not be written. PHP 5 introcudes the interfaces, with which the same problem can be resolved in a more elegant way. PHP 4 does not has interfaces, but still PHPLint allows to declare abstract classes with which a similar result can be achieved.

 

Predefined constants

There are only these predefined constants:

/*. boolean .*/ FALSE
/*. boolean .*/ TRUE
Logical values. PHPLint accepts the lower-case letters version false and true.

/*. float .*/ NAN
Not-A-Number.

/*. float .*/ INF
Infinity.

NULL
This constant is the only value allowed for the type null and it can match a string, an array, a resource, an object or a mixed. PHPLint accepts the lower-case letters version null.

 

Predefined superglobal variables

The "superglobals" are variables accessible from any scope. They are predefined by the interpreter and are documented in the PHP manual. PHPLint assign them a type as follows:

/*. array[string]mixed .*/ $GLOBALS
This array is indexed by the names of the global variables and returns their value. If the variable isn't already defined in the global scope, the program raise a warning message. For example:

$x = 1;

function f()
{
    echo $GLOBALS['x'];
    echo $GLOBALS['y'];  # warning: undefined global $y
}

If the expression inside the $GLOBALS[] square braces isn't statically determinable, i.e. it is an expression to be evaluated at runtime, PHPLint will not be able to resolve the variable and it will raise a warning message.

/*. array[string]string .*/ $_SERVER
This array is indexed by the environmental variables that are set by the WEB server.

/*. array[string]mixed .*/ $_GET
/*. array[string]mixed .*/ $_POST
/*. array[string]mixed .*/ $_REQUEST
/*. array[string]mixed .*/ $_COOKIE
These array are all of the same type: they are indexed by the name of a parameter received from the browser. Typically their value is a string, but they can also be an array of strings when the name of the parameter received from the client is terminated by these characters: "[]". Its a good idea to always check for the existence and the actual type of every parameter. For example, in this code the parameter "your_name" is expected to be a string:

if( isset($_POST['your_name']) && is_string($_POST['your_name']) )
    $your_name = /*. (string) .*/ $_POST['your_name'];
else
    $your_name = "";

In the following example, the HTML SELECT tag with the "multiple" option returns an array of the selected options:

if( isset($_POST['your_hobbies']) && is_array($_POST['your_hobbies']) )
    $your_hobbies = /*. (array[int]string) .*/ $_POST['your_hobbies'];
else
    $your_hobbies = /*. (array[int]string) .*/ array();
/*. array[string][string]mixed .*/ $_FILES
Here too, typically the elements of the array are strings and integer numbers, but they can be array[int] if the request from the browser contains a parameter terminating with "[]" (upload of two or more files).

/*. array[string]string .*/ $_ENV
These are the environment variables of the process executing the PHP program.

/*. array[string]mixed .*/ $_SESSION
This array is indexed by the names of the variables stored in the current user session.

/*. string .*/ $php_errormsg
A variable containing the text of the last error message generated by PHP. This variable will only be available within the scope in which the error occurred, and only if the track_errors option of the php.ini configuration file is turned on (it is off by default). Actually this is not a true super-global, but it is a trick of the PHP interpreter that creates $php_errormsg as a variable local to the scope where the error occurred.

 

Classes

Simply stated, a class defines a new type of data, including some variables and some functions acting on these values.

The terminology defining these entities is not completely consolidated, and many different terms are used to indicate the same thing:

In this manual we will use only these terms: class, object, property (possibly non-static or static when the context requires this specification) and method (possibly non-static or static when the context requires this specification).

Here is an example of class in PHP 4:

class Point2D
/*. DOC Implements 2D points (PHP 4). .*/
{
    var $x = 0.0;
    var $y = 0.0;

    function Point2D(/*.float.*/ $x, /*.float.*/ $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    function add(/*. Point2D .*/ $p)
    {
        $this->x += $p->x;
        $this->y += $p->y;
    }
}

$here = new Point2D(0.0, 0.0);
$step = new Point2D(1.0, 0.0);
$here->add($step);

The PHP 5 version of the same class is only slightly different, but its usage is the same:

class Point2D
/*. DOC Implements 2D points (PHP 5). .*/
{
    public $x = 0.0;
    public $y = 0.0;

    function __construct(/*.float.*/ $x, /*.float.*/ $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    function add(/*. Point2D .*/ $p)
    {
        $this->x += $p->x;
        $this->y += $p->y;
    }
}

The name of a class is case-insensitive. However, PHPLint checks the correct spelling and it raises an error if a class with a given name is called with another, similar, name that differ only by upper-case and lower-case letters.

The scope of the classes is global and they cannot be nested. If a class if private to the package and should not be used by the other packages, it can be qualified as private:

/*. private .*/ class MyRecord { ... }

PHPLint ensures that a private class cannot be used or redefined inside another package.

PHPLint support both PHP 4 classes and PHP 5 classes. Since they differ in many ways, this manual has two separated chapters that covers the two versions.

 

Classes (PHP 4)

A class declares properties and methods. PHPLint extends the syntax of the PHP 4 classes through the visibility attributes (public, protected and private), abstract, final and static. Since these attributes are not allowed by PHP 4, they can be specified either through the PHPLint meta-code or through a DocBlock comment. In the examples that follow we will use the PHPLint meta-code because it is shorter. PHPLint uses these informations in several ways:

New attributes for class items

The new visibility attributes are:

public
The item is accessible anywhere. This is the default, but it can be specified for readability.

protected
The item is accessible only from the class itself and from the classes that extend this one. For example:
/*. protected .*/ var $limited_access_var = 123;
/*. protected int .*/ function GetNo(){ ... }
private
The item is accessible only from the class where it is defined.
/*. private .*/ var $log_file = /*. resource .*/ NULL;
/*. private void .*/ function ResetStatus(){ ... }

Any non-static item declared inside a class is by default tied to an object and can be accessed through the "dynamic" dereferencing operator "->", for example $obj->item. Methods that do not use the special variable $this can be marked as static and must be accessed through the "static" dereferencing operator "::", for example ClassName::static_func(). A property cannot be static (only PHP 5 implements the static properties).

The final attribute marks methods that cannot be overridden. A private final item is allowed, although it does not make much sense, since if it is private isn't visible from outside the class.

Class constants

PHP 4 does not provide support for class constants. Constants can be declared using the define() statement at global scope.

Properties

Properties are variables automatically instantiated for every new object of the class. Every object has its own set of properties.

All the properties of the class must be declared explicitly (PHPLint restriction). At run-time you cannot assign new properties to an existing object, so that $obj->newProp = 123; is an error (PHPLint restriction).

A property can be declared using a syntax similar to this one:

/*. visibility .*/ var /*. x_type .*/
    $prop1,
    $prop2 = EXPR1,
    $prop3 = EXPR2;

Note that several properties can be declared, all having the same type and attributes.

The visibility attribute is optional, and the default is public.

The type is optional. It is simpler and often shorter to indicate a proper initial value.

The initial value EXPR is optional and, if provided, its type must match the x_type indicated with the PHPLint meta-code. Remember that properties without an initial value are initialized to the value NULL; in this case the responsibility to assign them a proper initial value is left to the constructor method.

Properties cannot be final nor static.

public and protected properties cannot be overriden nor re-defined in extended classes (PHPLint restriction).

private properties cannot be re-defined (PHPLint restriction due to the PHP 4 lack of actual support of the private visibility attribute).

Properties can be accessed in several ways:

$this->v
Inside a method to access a local property, or an inherited public|protected, property.

$obj->v
Everywhere to access a public property. Note that protected properties are not accessible this way, but only through $this->v. Accessing via $obj-> to protected properties is forbidden (PHPLint restriction).

Lets see how a property can be declared through some examples:

var $something;

A public property of default type mixed and undefined initial value. PHPLint raises a warning on this declaration, since the default value of this property once the object will be created is NULL, a value that might not be suitable for the intended usage of the variable.

var $counter = 123;
var $fn = "";
var $fd = /*.(resource).*/ NULL;

Three public properties of the type int, string and resource respectively. Note that the NULL value requires a formal type-cast, as we already explained.

/*. private .*/ var $names = /*. (array[int]string) .*/ array();

A private property of type array[int]string, initially empty, and that cannot be used outside its class.

Methods

Methods can have one of the visibility attributes private or protected or public. The default attribute is public.

private methods are accessible only inside the code of the class itself, and are not visible outside. private methods cannot be overridden in the child classes (PHP4 limitation).

protected methods are accessible only from the class itself and its child classes.


public methods are always accessible and can be overridden.

Methods can have the static attribute. static methods cannot use the special variable $this. static methods can be accessed only through the "::" operator. Non-static methods (the default) can be accessed only through the "->" operator.

Methods can have the final attribute. final methods cannot be overridden. private methods cannot be final.

The general syntax of methods is the same we already seen for the regular functions (see chapter Functions), apart the attributes. Lets see how a method can be declared through some examples:

function doSomething(){}

A public, non-static, method of default type undefined. PHPLint can guess the returned type from the return instruction, if any is present inside the body of the method.

/*. protected int .*/ function getCounter()
{ return $this->counter; }

A protected, non-static, method that returns an int number.

/*. public final array[int]string .*/
function getNames(/*. string .*/ $substr)
{
    $res = /*. (array[int]string) .*/ array();
    foreach($this->names as $v){
        if( strpos($v, $substr) !== FALSE ){
            $res[] = $v;
        }
    }
    return $res;
}

A public, final, method that returns an array of strings.

Methods can be accessed in several ways, depending on the context and on their attributes:

$this->f()
Inside a non-static method to access a local non-static method, or an inherited public|protected, non-static method.

parent::f()
Inside a method to access an inherited public|protected static|non-static method. Useful if the inherited method has been overridden, so parent::f() is the original method, while $this->f() (if non-static) or CLASSNAME::f() (if static) is the overriding one. Note the usage of the static resolution operator "::" also for non-static methods: in fact in this case the dynamic binding does not take place since the method we are referring to is statically determined. The typical usage is calling the parent constructor from the overriding one using parent::CLASSNAME(); (remember that if the constructor is overridden, the parent constructor isn't called automatically when a new object is created).

CLASSNAME::f()
Everywhere to access a static, public method.

$obj->f()
Everywhere to access a non-static, public method. protected methods are not accessible this way (PHPLint restriction).

PHPLint will raise an error message if a static method does use the special variable $this (PHPLint restriction).

Example

Here is a complete example of an (unuseful) class in PHP 4:


class A {

    /*. public .*/ var
        $prompt = "The current value is ",
        $counter = 0;

    /*. private .*/ var
        $internal_counter = 0,
        $list_of_names = /*.(array[int]string).*/ array();

    function A(/*. int .*/ $n)
    { 
        $this->counter = $n;
    }

    function get()
    {
        return $this->prompt . $this->counter;
    }

    /*. static string .*/
    function getParam(/*. string .*/ $name)
    {
        if( isset( $_REQUEST[$name] ) )
            return (string) $_REQUEST[$name];
        else
            return NULL;
    }
}


$title = A::getParam("TITLE");
$obj = new A(789);
echo $obj->get();  # Output: "The current value is 789"

Overriding properties

Properties cannot be overridden (PHPLint restriction).

Overriding methods

A class B is said to be a subclass of the class A if the class B extends the class A or if the class B extends a class X that is a subclass of A (note the recursive definition).

Normally, the names of the methods of a subclass differ from any other method of its parent class. However, the subclass B can override the method A::a() of its parent class defining the overriding method B::a().

The basic rule of the polymorphism in OOP is that overridden methods must be usable exactly as the original ones. For example, if A::a() is expected to return a string, the overriding method B::a() must return a string; if A::a() requires two mandatory arguments, also B::a() must be callable with two arguments. PHPLint checks accurately every overridden method: both the signature and the attributes are compared and possible incompatibilities are detected. Lets start defining what a signature is.

The signature of a method is given by

  1. the type of the returned value, possibly void if none is returned;
  2. the type of each mandatory formal argument;
  3. the type of each default formal argument;
  4. the possible presence of optional arguments /*. args .*/ (PHPLint extension).

For example, the signature of the method

/*. string .*/ function aMethod(/*. string .*/ $s, $n = 2 /*., args .*/){}

is given by its return type (string), its mandatory arguments (one of type string), its optional arguments (one of type int) and its variable number of optional arguments (args). To be concise:

string(string [, int, ...])

The attributes and the signature of the overriding method B::a() are subject to these rules (PHPLint restrictions):

In this example, the subclass B override all the methods of its parent class A. The body of all the methods is left empty, since it does not matter in our discussion. Note that B::g() adds a default argument $y, and B::h() adds optional arguments and raises its visibility from protected to public.


class A {
    /*. void .*/ function f(){}
    /*. int  .*/ function g(/*. int .*/ $i){}
    /*. protected void .*/ function h(/*. int .*/ $x){}
}

class B extends A {
    /*. void .*/ function f(){}
    /*. int  .*/ function g(/*. int .*/ $x, /*. int .*/ $y = 0){}
    /*. public void .*/ function h(/*. int .*/ $x /*., args .*/){}
}

Special methods

A method whose name is the same as the class is assumed to be the constructor. The constructor is called implicitly by the new operator. A class constructor can be called explicitly only inside the constructor of an extended class and it cannot be called explicitly elsewhere (PHPLint restriction).

The signature of the special methods __sleep() and __wakeup() must be as follows:

/*. public array[int]string .*/ function __sleep();
/*. public void .*/ function __wakeup();

An error is raised if the method name begins with two underscore characters, since those names are reserver for future extensions of the language.

Final classes

A final class is a class that cannon be extended anymore. Since the final attribute is an extension to the PHP 4 language, it can be indicated either through PHPLint meta-code

/*. final .*/ class CLASS_NAME { ... }

The reasons why a class should be made "non-extensible" go beyond the aims of this reference manual. Take a good book about OOP if you are interested to the subject.

In the following examples we will use the PHPLint meta-code because it is shorter, although you can use a DocBlock instead with exactly the same meaning.

Abstract classes

An abstract class is a class with the abstract attribute in its declaration. The abstract attribute is an extension to the PHP 4 language, so it must be indicated either through meta-code

/*. abstract .*/ class MyAbsClass { ... }

or unsing the @abstract line tags of a DocBlock.

Abstract classes can contain abstract methods, i.e. methods whose body is left empty. Abstract methods must have the abstract attribute:

/*. abstract .*/ class MyAbsClass {

    /*. abstract void .*/ function doSomething() {}

}

An abstract class can have also non-abstracti (aka "concrete") methods and properties. The basic properties of abstract classes are:

This example should be self-explanatory. An abstract class provides the interface to a generic container of strings; every string has a name and its value can be written and read with the abstract methods set() and get():

/*. abstract .*/ class StringContainer {

    /*. abstract void .*/ function set(
        /*. string .*/ $name,
        /*. string .*/ $value){}

    /*. abstract string .*/ function get(/*. string .*/ $name){}

    /*. abstract void .*/ function dispose(){}
}

Since this class is abstract it cannot be used directly to instantiate objects, but it must be implemented in some concrete class. For example, the following example shows two concrete classes that implements StringContainer. These implementations are very crude, without error handling and with possible infinite loops: do not use them in real applications! The first implementation, StringOnFile, creates a directory whose name is randomly generated, then here it saves the value of each string in a file with its name:

class StringOnFile extends StringContainer {

    /*. private .*/ var /*. string .*/ $dir;

    /*. void .*/ function StringOnFile()
    {
        do {
            $this->dir = "strings-" . rand();
        } while( file_exists( $this->dir ) );
        mkdir( $this->dir );
    }

    /*. void .*/ function set(
        /*. string .*/ $name,
        /*. string .*/ $value)
    {
        file_put_contents( $this->dir ."/$name", $value);
    }

    /*. string .*/ function get(/*. string .*/ $name)
    {
        $s = file_get_contents( $this->dir ."/$name" );
        return ($s===FALSE)? /*. (string) .*/ NULL : $s;
    }

    /*. void .*/ function dispose()
    {
        system( "rm -r ". $this->dir );
    }
}

The second implementation, StringOnSession saves each string in the current session:

class StringOnSession extends StringContainer {

    /*. private .*/ var /*. string .*/$arr;

    /*. void .*/ function StringOnFile()
    {
        do {
            $this->arr = "strings-" . rand();
        } while( isset( $_SESSION[ $this->arr ] ) );
        $_SESSION[ $this->arr ] = array();
    }

    /*. void .*/ function set(
        /*. string .*/ $name,
        /*. string .*/ $value)
    {
        $arr = /*.(array[string]string).*/ & $_SESSION[ $this->arr ];
        $arr[$name] = $value;
    }

    /*. string .*/ function get(/*. string .*/ $name)
    {
        $arr = /*.(array[string]string).*/ & $_SESSION[ $this->arr ];
        if( ! isset( $arr[$name] ) )
            return (string) NULL;
        return $arr[$name];
    }

    /*. void .*/ function dispose()
    {
        unset( $_SESSION[ $this->arr ] );
    }
}

The power of abstract classes can be seen in the code below, where the function can handle a container without knowing anything about its concrete implementation. The object $container has only formally the type StringContainer, but actually the concrete object that will be passed to the function will be an instance of some implementation of this abstract class. By the way, this function saves every entry of an associative array into the container given:

/*. void .*/ function SaveParams(
    /*. array[string]string .*/ & $params,
    /*. StringContainer     .*/ $container)
{
    foreach($params as $k => $v)
        $container->set($k, $v);
}


$data = array("userid"=>"guest",
    "username"=>"Guest",
    "userprivileges"=>"0");

/* Save data on file: */
SaveParams($data, new StringOnFile());

/* Save data on session as well: */
SaveParams($data, new StringOnSession());
 

Classes (PHP 5)

Contents of this chapter:

Introduction

PHP 5 introduces many new features in its OOP model: class constants, the visibility attributes (public, protected, private), the "static" and "final" attributes, object introspection, etc. PHPLint generally supports all the features of PHP5, but it adds some restrictions and detects some weakness of the language: the differences are here detailed.

Lets see an example:

class A {
    const a_string = "The current value is ";
    public $an_int = 1;
    private /*. mixed .*/ $unknown;

    public /*. void .*/ function __construct(/*. int .*/ $n)
    { 
        $this->an_int = $n;
    }

    public /*. string .*/ function Get()
    {
        return A::a_string . $this->an_int;
    }
}

The only entities that require an explicit type are the property $unknown and the formal argument $n.

Class constants

Class constants can be declared using a syntax similar to this one:

/*. visibility .*/ const
    CONSTNAME1 = STATICEXPR1,
    CONSTNAME2 = STATICEXPR2,
    CONSTNAME3 = STATICEXPR3;

Class constants never require (nor PHPLint will allow to provide) an explicit type: the type of a constant is always given implicitly by its value.

Class constants are public by default, but an explicit visibility attribute can be provided as PHPLint meta-code. private class constants are not reported by the PHPLint Documentator.

Accessing. Class constants can be accessed in several ways, depending on the context:

self::CONSTNAME
Inside a class to access a local constant or an inherited non-private constant.

parent::CONSTNAME
Inside a class to access a non-private constant of a parent class, possibly re-defined inside the current class.

CLASSNAME::CONSTNAME
Everywhere to access a public constant of a class.

Collisions. A class can extend another class and can re-define some of the inherited constants. A class can also implement several interfaces, but constants inherited from all these classes cannot collide and need to be all distinct. Example:

# All these interfaces and classes define the same constant:
interface IF1 { const x = 0; }
interface IF2 { const x = 1; }
abstract class ABS { const x = 2; }
class A { const x = 3; }

# Now testing collisions:

class B extends A {
    const x = "hello";  # re-defining A::x is allowed
                        # Note the different type
}

class C extends ABS implements IF1, IF2 {
    # ERROR: collision between inherited
    # consts ABS::x, IF1::x, IF2::x

    const x = 5;  # ERROR: collides with all the consts above
}

The /*.private.*/ meta-code attribute does not help to limit the scope of visibility of a constant because it is not supported by PHP, so also private constants can collide in derived classes.

Re-defining. Class constants declared in interfaces and abstract classes cannot be re-defined. Only constants inherited from concrete classes can be re-defined. In this latter case the operators self::CONSTNAME and parent::CONSTNAME allow to access the desired value in the current or in the parent class, respectively.

Overriding. Class constants cannot be overridden. From the point of view of PHPLint, this implies that constants with the same name defined in related concrete classes can have different types (see example above).

Properties

Classes and abstract classes can define several properties, basically variables bound to the class (if static) or bound to every object instantiated from this class (if non-static). A property can be declared using a syntax similar to this one:

visibility [static] [ /*. x_type .*/ ]
    $name1 = STATICEXPR1,
    $name2 = STATICEXPR2,
    $name3 = STATICEXPR3;
where:

The visibility attributes are: public, protected or private.

The static attribute marks the property to be a proper class property rather than object property, i.e. there is only one instance of this property for all the objects of this class.

The visibility and the static attributes can be indicated in any order. Properties that are both public and static can be indicated simply being static omitting the other attribute.

The type is optional. It is simpler and shorter to indicate a proper initial value, since PHPLint guesses the type of the property from the type of the expression. Providing both the type and the initial value is allowed for clearness if desired, but it is redundant. For example, writing

public $counter = 0;
public $status = array("off", "on");
public $file = /*.(resource).*/ NULL;
is equivalent to
public /*. int .*/ $counter = 0;
public /*. array[int]string .*/ $status = array("off", "on");
public /*. resource .*/ $file = NULL;

The initial value STATICEXPRn is optional and, if provided, its type must match the x_type. Remember that properties without an initial value are initialized to the value NULL; in this case the responsibility to assign them a proper initial value is left to the constructor method.

private properties can be re-defined inside the derived classes, but this is not a proper "overriding", since the methods of the child and of the extended class each one access its own properties.

public and protected properties cannot be redefined in extended classes (PHPLint restriction).

Accessing. Properties can be accessed in several ways, depending on the context and on their attributes:

$this->v
Inside a non-static method to access a local non-static property, or an inherited public|protected, non-static property.

$obj->v
Everywhere to access a non-static, public property. Note that protected properties are not accessible this way, but only through $this->v or self::$v. Accessing via $obj-> to protected properties is forbidden (PHPLint restriction).

self::$v
Inside a method to access a local static property, or an inherited public|protected, static property.

parent::$v
Inside a method to access an inherited public|protected static property. Since in PHPLint the non-private properties cannot be re-defined, nor public|protected properties can be overridden, this gives the same as self::$v.

CLASSNAME::$v
Everywhere to access a static, public property.

Re-defining. Only private properties can be re-defined in extended classes.

Overriding. Properties cannot be overridden (PHPLint restriction).

NOTE. PHP allows to override properties, but the overridden property gets shadowed by the new one and cannot be accessed anymore, neither from its own class.

Methods

A method can be declared using a syntax similar to this one:

[visibility] [static] [final] /*. x_type .*/
function methodName(arguments)
{
    // the code here
}

Methods attributes

A method is basically a function that can have several attributes, in any order.

The allowed visibility attributes for methods are: public (the default), protected or private.

The static attribute marks the method to be a proper class method rather than an instance method, so this methods is not bound to a particular object. static methods cannot use the special variable $this (PHPLint restriction).

The final attribute marks methods that cannot be overridden.

The type of the returned value isn't mandatory since PHPLint can guess the type from the return statement, but it is recommended be always indicated explicitly. Methods that do not return a value should be void.

Inheritance and methods overriding

A class B is said to be a subclass of the class A if the class B extends the class A or if the class B extends a class X that is a subclass of A (note the recursive definition). For example:

class A {
    function a() {}
}

class B extends A {
    function a() {}
}

Normally, the names of the methods of a subclass differ from any other method of its parent class. However, the subclass B can override the method A::a() of its parent class defining the overriding method B::a().

The basic rule of the polymorphism in OOP is that overridden methods must be usable exactly as the original ones. For example, if A::a() is expected to return a string, the overriding method B::a() must return a string; if A::a() requires two mandatory arguments, also B::a() must be callable with two arguments. PHPLint checks accurately every overridden method: the signature, the attributes and the thorwn exceptions are compared and possible incompatibilities are detected. PHP 5 already does some checks on the attributes and on the number of mandatory and default arguments, but PHPLint enhance these restrictions to the types of the arguments, and the possible presence of optional arguments /*.args.*/, the type of the returned value, and the exceptions thrown. Lets start defining what a signature is.

The signature of a method is composed of:

For example, the signature of the method

/*.int.*/ function a(/*.int.*/ $a, $b=3.14 /*., args.*/)

is int(int,[float],...) with one mandatory argument ($a), one default argument ($b) and possibly a list of more optional arguments.

The attributes, the signature and the list of the exceptions thrown of the overriding method B::a() are subject to these restrictions:

In this example, the subclass B overrides all the methods of its parent class A. The body of all the methods is left empty, since it does not matter in our discussion. Note that B::g() adds a default argument $y, while B::h() adds a variable number of optional arguments and raises its visibility from protected to public.

class A {
    public /*. void .*/ function f(){}

    public /*. int  .*/ function g(/*. int .*/ $i){}

    protected /*. void .*/ function h(/*. int .*/ $x){}
}

class B extends A {
    public /*. void .*/ function f(){}

    public /*. int  .*/ function g(
        /*. int .*/ $x,
        /*. int .*/ $y = 0)
    {}

    public /*. void .*/ function h(/*. int .*/ $x /*., args .*/){}
}


NOTE 1. PHP 5 does not allow to re-define a private method, and it checks the new method as it were an overriding one. This implies that a different number of arguments generates a warning when the E_STRICT mode is enabled. It seems to me a little bug, since I'm used to split complex methods into several little private functions with short, trivial names; these utility functions should not be visible outside the class.

Workaround: avoid trivial names for private methods. For example, if the method Items::printList() needs an utility function that sort the list before be printed, you can call this private function something like Items::printList_sort(), and not simply Items::sort(): not only this prevent collisions with possible other private methods of extended classes, but it also make clear to the human reader of the source that this is an utility method bound to printList().


Accessing to the methods

Methods can be accessed in several ways, depending on the context and on their attributes:

$this->f()
Inside a non-static method to access a local non-static method, or an inherited public|protected, non-static method.

self::f()
Inside a method to access a local static method, or an inherited public|protected, static method.

parent::f()
Inside a method to access an inherited public|protected static|non-static method. Useful if the inherited method has been overridden, so parent::f() is the original method, while $this->f() (if non-static) or self::f() (if static) is the overriding one. Note the usage of the static resolution operator "::" also for non-static methods: in fact in this case the dynamic binding does not take place since the method we are referring to is statically determined. The typical usage is calling the parent constructor from the derived class using parent::__construct(); (remember that if the derived class has a constructor, the parent constructor isn't called automatically, but it must be called explicitly from the constructor of the derived class).

CLASSNAME::f()
Everywhere to access a static, public method.

$obj->f()
Everywhere to access a non-static, public method. protected methods are not accessible this way (PHPLint restriction).

PHPLint will raise an error message if a static method does use the special variable $this (PHPLint restriction).

Special methods

Constructors. A constructor method __construct() can have a visibility attribute, then the usual visibility rules apply. For example, note that if the constructor is private or protected, objects of its class can be instantiated only inside the class itself (if private) and the constructor can be called by the extended classes (if protected).

A class constructor cannot be abstract nor static.

The signature of a class constructor must return void and it may contain arbitrary formal arguments, since its signature is not compared with that of the parent class (constructors do not "override" constructors).

A class constructor is called implicitly by the new operator; it cannot be called explicitly with the only exception of the constructor of the derived classes (PHPLint restriction).

A constructor must call explicitly its parent constructor if one exists, since in this case PHP would not call automatically the constructor of the parent class. It is an error if the constructor of the derived class omits to call the parent constructor through parent::__construct(); (PHPLint restriction).

Destructors. A class destructor __destruct() can have a visibility attribute. Destructors are invoked automatically by the PHP interpreter once the object has no more references. Destructors cannot be called explicitly by the program (with the only exception of the destructor of the derived class; see below).

Note that private and protected destructors cannot be invoked outside their class or their derived classes, so that if the destruction of an object occurs outside the visibility context of this method, the PHP interpreter raises a warning message. Since PHPLint cannot practically determinate in which context the object of this class will be destroyed, it cannot check the proper usage of the private and protected attributes applied to destructors, so it imposes these methods be always public (PHPLint limitation).

A class destructor cannot be abstract nor static.

A class destructor must return void and it cannot have formal arguments.

Thus the only allowed signature of a destructor is as follows:

function __destruct(){ ... }

A destructor must call explicitly its parent destructor if one exists, since in this case PHP would not call automatically the destructor of the parent class. It is an error if the destructor of the derived class omits to call the parent destructor through parent::__destruct(); (PHPLint restriction).

Other special methods. This table summarizes the other special methods and their expected visibility and signature:

Visibility and Signature Notes
public [final] void __clone()  
public [final] static __set_static(array[string]mixed)  
public [final] array[int]string __sleep()  
public [final] void __wakeup()  
public [final] string __toString()  
public [final] void __set(string, mixed) These special methods are parsed but actually not supported. PHPLint has not support for properties dynamically created.
public [final] mixed __get(string)
public [final] boolean __isset(string)
public [final] void __unset(string)
public [final] mixed __call(string, array[]mixed) This special method is parsed but actually not supported. You can't call undefined methods.

Abstract classes

The general syntax of an abstract class can be informally described as follows:

/*. private .*/ abstract class CLASSNAME
    extends ANOTHER_ABSTRACT_CLASS
    implements INTERFACE1, INTERFACE2
{
	constants
	properties
	abstract methods
	concrete methods
}

An abstract class is similar to any other concrete class, but with three important differences: 1. it can contain abstract methods; 2. it cannot be instantiated with the new operator; 3. derived, concrete classes must implement all the abstract methods defined inside the abstract class; if the abstract class extends another abstract class, or implements some interfaces, the concrete class must also implement the inherited abstract methods.

NOTE. In practice, the same result can be obtained with a concrete class simply leaving empty the body of the abstract method: this would satisfy the requisite no. 1, while a source validator like PHPLint might ensure the observance of the points 2 and 3. In PHP 4, where abstract classes and abstract methods are not available, the PHPLint meta code /*.abstract.*/ allows to to obtain the same result.

The basic properties of abstract classes are:

Abstract classes can be private to the package: in this case also any derived class must be private. Abstract classes cannot be final, since they are useful only if they can be extended.

Abstract classes can extend another abstract class, and can implement several interfaces. The abstract class itself may implement some of the inherited abstract methods.

Constants inherited from the implemented interfaces and from the extended class, and properties inherited from the extended class cannot collide together nor they can collide with the items defined inside the abstract class.

Instead, a method can override or implement several methods inherited from the extended classes and from implemented interfaces, provided that this method be compatible with all them.

An abstract method is a public, non-static, non-final method that has the abstract qualifier, and its body is substituted by a semicolon:

abstract class Shape
{
    const DEF_SCALE = 1.0;

    public $x = 0.0, $y = 0.0;

    function moveTo(/*. float .*/ $x, /*. float .*/ $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    abstract /*. void .*/ function scale(/*. float .*/ $factor) ;
}

This class describes a generic geometric shape that can be moved and scaled. The position is a property that can be defined for any shape, so the moveTo() method can be concrete. On the contrary, scaling a generic shape cannot be implemented in a general way and the method implementing this feature only declares its interface but not its code, i.e. it is abstract.

Since the method scale() is abstract, it must be implemented in any derived, concrete, class:

class Circle extends Shape
{
    public $radius = 1.0;

    function scale(/*. float .*/ $factor)
    {
        $this->radius *= $factor;
    }
}

class Rectangle extends Shape
{
    public $side_a = 1.0, $side_b = 2.0;

    function scale(/*. float .*/ $factor)
    {
        $this->side_a *= $factor;
        $this->side_b *= $factor;
    }
}

Abstract classes are useful to describe data structures and deal with them without knowing their actual nature. For example, this chunk of code declares a drawing as a list of shapes and declares a function that scale them all:

$drawing = /*. (array[int]Shape) .*/ array();

function scale_shapes(/*. float .*/ $factor)
{
    foreach($GLOBALS['drawing'] as $shape)
        $shape->scale($factor);
}


$drawing[] = new Circle();
$drawing[] = new Circle();
$drawing[] = new Rectangle();
scale_shapes(10.0);

Interfaces

The syntax of an interface can be described informally as follows:

/*. private .*/ interface NAME
    extends INTERFACE1, INTERFACE2
{
	public constants
	abstract methods
}

An interface is similar to an abstract class declaring only constants and abstract methods. Basically, interfaces are used as "syntactical glue" between different classes, and are not really necessary for a scripting language as PHP is. Nevertheless, interfaces are an important feature in complex applications:

Constants and abstract methods inherited from the extended interfaces cannot collide together and cannot collide with items defined inside the interface.

Properties are not allowed in interfaces (PHP 5 restriction). You can still force implementing classes to define some accessor methods (also known as "setters" and "getters") to write and read the property. The following example illustrates two accessor methods allowing to read and write the property "xxx":

interface IAccessorsDemo
{
    /*. void .*/ function setXxx(/*. int .*/ $value);
    /*. int  .*/ function getXxx();
}

Constructor methods are allowed in interfaces only since PHP 5.2.0.

All the methods must be abstract, public and non-final, but the abstract attribute is not required, since all the methods of an interface must be abstract.

Methods can be static.

An interface can extend several other interfaces, so inheriting all their abstract methods.

Interfaces cannot be instantiated into an object, so the new operator cannot be applied to an interface. Interfaces can be extended by other interfaces, and can be implemented by abstract and concrete classes.

In the following example, an interface describes the methods that a generic "data container" must provide. Every datum has a name and a value; the methods set() and get() allow to store/retrieve that value. The abstraction introduced by this class is that the medium where these data have to be stored is left to the implementation of the interface. Several implementations can follow different strategies, for example data can be stored in memory, in the user session, inside a DB, etc. The point is that the software written in terms of this interface becomes largely independent from the implementation we can choose.

Two (very crude) concrete classes implementing this interface are also defined, then an example that stores a copy of the $_SESSION[] data is shown.


interface DataContainer
{
    /*. void .*/ function set(
        /*. string .*/ $name,
        /*. string .*/ $value);

    /*. string .*/ function get(/*. string .*/ $name);
}


class MemoryBasedContainer implements DataContainer
{
    private $data = /*.(array[string]string).*/ array();

    /*. void .*/ function set(
        /*. string .*/ $name,
        /*. string .*/ $value)
    {
        $this->data[$name] = $value;
    }

    /*. string .*/ function get(/*. string .*/ $name)
    {
        if( isset($this->data[$name]) )
            return $this->data[$name];
        else
            return NULL;
    }
}


class FileBasedContainer implements DataContainer
{
    private $base_dir = "";

    /*. void .*/ function __construct(/*. string .*/ $base_dir)
    {
        $this->base_dir = $base_dir;
    }

    /*. void .*/ function set(
        /*. string .*/ $name,
        /*. string .*/ $value)
    {
        file_put_contents($this->base_dir ."/". $name, $value);
    }

    /*. string .*/ function get(/*. string .*/ $name)
    {
        $fn = $this->base_dir ."/". $name;
        if( ! file_exists($fn) )  return NULL;
        return file_get_contents($fn);
    }
}


class DbBasedContainer implements DataContainer
{
    /* etc etc etc */
}


function save_session_data(/*. DataContainer .*/ $c)
{
    foreach($_SESSION as $k => $v)
        $c->set($k, serialize($v));
}


$c = /*. (DataContainer) .*/ NULL;

switch( SAVE_METHOD ){
    case SAVE_ON_MEMORY: $c = new MemoryBasedContainer(); break;
    case SAVE_ON_FILE:   $c = new FileBasedContainer("/tmp"); break;
    case SAVE_ON_DBASE:  $c = new DbBasedContainer(); break;
}

save_session_data($c);
/* ... */

Note that the function save_session_data() formally accepts an object from a class that cannot be instantiated. This is obviously a syntactical trick that simply says: well, my dear function, whatever the actual object may be, that object will behave as specified by the DataContainer interface, so that the methods specified there will be available.

Note that the same code would work perfectly on PHP also omitting the interface declaration at all, as the type of the variable $c is dynamically determinated and the method calls are always dynamically resolved at run-time. Yet, the interface makes evident to the human reader (an to PHPLint) which are the features shared by every instance of the object $c.

 

Recursive declarations

PHPLint is a single-pass parser. With this design, the program becomes simple and fast. The drawback is that every item has to be declared before be used, and the programmer is forced to declare functions, classes and methods in a strict bottom-up order: low-level come first, high-level ones next. However there are cases in which functions (or even methods and classes) must call each other recursively because this may be the "natural" way to resolve some complex algorithms. PHPLint resolves these cases through the forward declaration.

Forward declarations are meta-code statements that may declare the prototype of a function, a class or a method, so that these elements can be used before being fully declared in actual PHP code.

All the prototypes declared in a file MUST have their corresponding full code declaration in the same file. When PHPLint ends parsing a file, in fact, it checks that all the prototypes declared in that file be resolved in actual PHP code.

Function prototypes

Function prototypes MUST be declared at global scope. Function prototypes cannot be declared inside a function or inside a method. This also means that function prototypes cannot be used inside files included by a function.

Function prototypes MUST fully declare the signature of the function, including returned type and argument types, as in this example:

/*. forward void function Location(string $url = ); .*/

Note the reserver keyword forward that marks the prototype. Note also that the expression giving the default value of the argument must be left empty, so that this value can be indicated only once for all where the actual code is implemented.

The actual code implementing this prototype MUST match exactly the signature of the prototype. Here we can rely on the guesses PHPLint performs, but the resulting signature must be that expected. For example, the function above can be implemented in code omitting the returned type, as this type can be guessed by PHPLint:

function Location(/*. string .*/ $url = NULL)
{
    if( $url === NULL )
        header("Location: http://www.mysite.com/");
    else
        header("Location: $url");
}

Note that the $url argument has a default value, and this default value has been explicitly given only in the actual PHP code and was omitted in the prototype.

Function prototypes can include also the private attribute and the args formal argument. The following example illustrates a private function that has a mandatory argument, a default argument and zero or more optional arguments:

/*. forward private void function
    print_list(string $label, string $sept=, args); .*/

The implementation in actual PHP code must match the same signature:

/*. private void .*/ function print_list(
    /*. string .*/ $label,
    /*. string .*/ $sept = ", "
    /*., args .*/)
{
    #...
}

None of the examples above actually required a prototype, as none of the functions depends recursively from another function (or class). If you know any concrete, short example that illustrates the actual need for recursive functions, please let me know and I will add it here.

Method prototypes

Recursive calls arise also within the methods of a class. In this case some of the methods have to be declared forward. Methods prototypes are just like function prototypes, with the only difference that methods may have also their proper attributes, visibility, static and final.

class Data {

    /*.
        forward public void function __construct(string $value);
        forward public Data function getMoreData();
    .*/

    # From now on __construct() and getMoreData() can be used by
    # any method and can be declared in PHP code in any order.
}

Class prototypes

A class prototype declares the class name and possibly the signature of some of its methods. There is not need to list the signatures of all the methods that will be actually declared in the actual PHP code, but only those that are involved in recursive declarations or recursive calls. When only the class name is involved in a recursive declaration, defining a forward class is sufficient:

/*.  forward class A {}  .*/

class B {

    private $a = /*. (A) .*/ NULL;

    /*. A .*/ function getA(){ return $this->a; }
}

class A {

    private $b = /*. (B) .*/ NULL;

    /*. B .*/ function getB(){ return $this->b; }
}

In more complex cases of recursive depency between classes, the A class also call some methods of B and vice-versa. In this case we declare the prototype of the first class along the methods that are required by the B class:

/*.
forward class A {
    void function __construct(mixed $x);
    int  function someMethod();
}
.*/

class B {
    # Here we can use the A class
    # and the methods A::__construct()
    # and A::someMethod().
}

class A {
    # Finally, here is the A class,
    # that may fully use B and its
    # methods.
}

Programmers accustomed to split their program source one class per file, may encounter a problem when defining classes that have recursive references. Lets take for example two classes A and B, each one declared in its own file A.php and B.php respectively. Now suppose both the classes need the other one, for example each class has a method that return an object of the other class. Here is structure of the source that resolves the recursive references:

File A.php:
<?php

/*. forward class A{} .*/

require_once 'B.php';

class A {
    /*. B .*/ function m(){}
}
File B.php:
<?php

/*. forward class B{} .*/

require_once 'A.php';

class B {
    /*. A .*/ function n(){}
}
We note that:

 

Exceptions

PHPLint collects thrown exceptions and checks for their proper usage. Exceptions can be explicitly raised by the throw statement, and PHPLint tracks their generation and their propagation through the call three of functions and methods. PHPLint also accurately checks try...catch blocks and shows improperly sorted catch brances and uncought exceptions.

PHPLint handles exceptions thrown by a method as part of its signature, just like the return type and its arguments. To be more precise, being the list of the exceptions part of the interface that method exhibits, overriding (or implementing) methods must comply with the signature of the original method. So, overriding (or implementing) methods cannot throw more exceptions than the original method; nevertheless the new method can throw more specialized exceptions derived from the exceptions already thrown by the original method without violating the interface contract.

For every function and method, the Documentator lists all the exceptions it may throw.

The Exception base class

Exceptions are only the classes derived from the Exception base class, declared in the standard module. In the following example, PHPLint forbids to use a generic class as exception:

class NotAnException {}

throw new NotAnException();  # <= ERROR: not an Exception

Extending Exception

The base Exception can be extended to get even more specialized exception classes. In the following example the CException specializes BException, that in turn specializes BException and so on. All these classes are valid exceptions that will be used in the following examples:

/*. require_module 'standard'; .*/
class AException extends Exception {}
class BException extends AException {}
class CException extends BException {}

Function throwing exceptions

The following example declares a function that raises an exception. This function is obviously completely unuseful, but will be used only as a prototype of a function that generates an exception:

function f1()
{
    if( true )
        throw new BException();
    else
        throw new CException();
}

Detecting the exceptions

PHPLint checks the proper order of the catch() branches, and warns about unuseful branches and uncought exceptions. As a general rule, the catch() branches must be ordered so that the more specialized exceptions come first, and then the more general exceptions next, so the correct order is CException, BException, AException and then Exception. The following chunk of code illustrates several cases:

f1(); # <= Warning: uncought exceptions BException, CException

# Full handling:
try { f1(); }
catch( CException $e ){ }
catch( BException $e ){ }

# Incomplete handling:
try { f1(); }
catch( CException $e ){ }  # <= Notice: uncought exception BException

# Improper order of the branches:
try { f1(); }
catch( BException $e ){ }
catch( AException $e ){ }  # <= ERROR: BException already caught CException

Note that PHPLint raises a Warning message on unhandled exceptions that can be thrown at global scope, that is in the main program. This is the case of the function f1() called outside of the try{} block. Instead, unhandled exceptions that can be thrown inside a function or inside a method are simply highlighted with a Notice and then inherited between the exceptions thrown by the that function or method.

Note also how PHPLint accounts for all the exceptions thrown inside the try{} block and then checks every catch(){} branch agains the list of exceptions involved. Exceptions not handled by any of the catch(){} branches are processed just like any other exception that can be thrown there, as explained above.

The "throws" declaration

The special meta-code declaration throws also allows to specify one or more exceptions a function or a method can throw, independently from the exceptions this function or method actually throws or inherits in its code. This is mainly useful in abstract methods, but may be useful also in concrete methods and in functions.

When function prototypes are needed, the throws declaration must be specified and it must list all the exceptions the actual function can throw. The function that actually implements this prototype is allowed to throw only the exceptions listed in the prototype, or even more specialized exceptions extended from them:

/*. forward void function f2() throws BException; .*/

function f2() {
    # can throw BException
}

The throws declaration may also list more exceptions that are not currently thrown by the function, but that the programmes plans to add later.

In general, the throws declaration is allowed in anywhere a function or method is declared. In the abstract methods the throws declaration is the only way to assign the exceptions to the method. The following example illustrates how the throws declaration can be inserted in a class:

interface I1 {
    public function m() /*. throws BException .*/ ;
}

class C1 implements I1 {
    public function m()
    {
        # This method may throw BException or even
        # any extension of BException.
    }
}

Concrete methods that implements an abstract method may throw any of the exceptions declared in the overridden method, or even more specialized exceptions extended from them. Instead, overriding methods cannot declare more exceptions nor they can throw more exceptions that those compatible with the expected exceptions.

As said, PHPLint considers both the exceptions explicitly declared with throws along the exceptions collected parsing the code of the method (or function), so the resulting list of exceptions raised can be longer than expected from the declaration. Overriding methods can throw more specialized exceptions, provided that these exceptions be an extension of the inherited exceptions.

In the following example PHPLint detects that the overriding method C2::m() throws an incompatible exception, and it raises an error:

class C2 extends C1 {
    public function m()
    {
        if( true )
            throw new CException();
        else
            throw new AException();  # <= ERROR: incompatible
    }
}

In the example above, note that CException is a valid extension of BException and then it is accepted. On the contrary, AException does not extend any of the inherited exceptions (it is, by the way, a parent class) and must be rejected: C2::m() is not a valid overriding method of C1::m().

To summarize, in most of the cases PHPLint can detect automatically the exceptions thrown by each function and method, collects them and tracks their propagation. But there are cases in which the intervention of the programmer is still required. The following list details these cases:

Can overriding methods throw new exceptions?

As a conseguence of the strict inheritance model PHPLint implements, new methods that implements or override an original method can only throw the same exceptions of the original method or even more specialized extensions of these latter.

This strict model corresponds exactly to what the Java language does with its checked exceptions. Someone might point out that Java has also the unchecked exceptions, like RuntimeExceptions and any of its derivative, that any method can freely throw, but currently such a concept is not available in PHPLint, as it handles all the exceptions as "checked"; still, "unchecked" exceptions might be introduced also in PHPLint in some future release.

We will consider the following example, where an interface is defined in order to access the elements of a list of strings:

class OutOfBoundException extends Exception {}

interface MyList {
    /*. string .*/ function getElement(/*. int .*/ $index)
        /*. throws OutOfBoundException .*/
}

The interface provided by the getElement method perfectly fits an implementation in which the list is stored in memory, but what if the same interface has to be implemented on a file? Accessing files may raise exceptions like IOException or something alike, but such an exception can't be thrown by getElement without violating its interface contract. So how to cope with this restriction when the new method really needs to throw another, new exception? Here there are some suggestions and alternative solutions:

 

Documentator

The features of the PHPLint Documentator are described in the separated document www.icosaedro.it/phplint/documentator.html.

 

Usage

The general syntax of the command is:

phplint [ option | file ] ...

where the arguments are any combination of files and options. Options start with an hyphen "-", so that the file names cannot start with this character. All the arguments are parsed from left to right, and the files are parsed in the order, so that every option affects only the files to its right.

Normally only one source file at a time should be parsed, leaving to PHPLint to follow recursively possibly required_once sources.

Several files can also be provided as well, and they are parsed in the order. But be careful because PHPLint collects items defined in each source parsed so it cannot detect if some of these files omits to include any of the sources already scanned. It is ok to scan several, unrelated, files at once in order to detect possible collisions between global items: this test can be useful if you plan afterward to include all these file in some application.

Many options are flags and have the form --xxxx to enable, and the corresponding --no-xxxx to disable a feature.

General options:

--version
Print version and copyright notice of the program.
--help
Print a brief description of the command and of the options.
--php-version 4
--php-version 5 (default)
The PHP version, 4 or 5. The differences between PHP 4 and 5 are mostly limited to the OOP, but there are also some differences in the extension modules that are available with a version of the language and not in the other. The source is parsed according to the version specified, and this version is also reported in the documentation so generated.
--modules-path DIR1:DIR2:DIR3
Modules required with require_module 'MODULENAME'; are first searched inside the same directory of the file currently being parsed, then are searched inside the directories specified by this option. The directory modules/ that comes with the PHPLint package hold the standard modules provided with the PHP interpreter, but you can add also yours custom extensions. Example:
phplint --modules-path /home/me/phplint/modules:/home/me/php-ext
--is-module
--no-is-module (default)
This flag should never be used when checking regular user's packages; it was introduced just as a trick to generate the documentation about extension modules avoiding several errors from being displayed. Enabling this flag, the parsed files are considered as files of prototypes describing extension modules of the PHP language and then: This flag is effective until disabled by --no-is-module, so several modules can be parsed at once with the flag enabled.
--packages-path DIR1:DIR2:DIR3
Packages required with require_once "some/file"; are first searched inside the same directory of the file currently being parsed. Then, if the file name provided is relative (i.e. it does not begin with "/"), PHPLint will continue searching for the package in the directories specified by this option, in the order: the relative path is appended to each directory and a slash is added between them, so the DIR1/some/file, DIR2/some/file, etc. are scanned in turn. The packages that are detected in this latter way are also listed in the documentation generated under the label "include_path must resolve these packages: pkg2.php, pkg3.php" so that system administrators are made aware of the installation requirements of the package, and will update the include_path of the php.ini accordingly. Example:
phplint --packages-path /home/me/php-lib:/home/me/php-ext
--recursive (default)
--no-recursive
Parse recursively every require_once statement that appears at scope level zero.
--parse-phpdoc (default)
--no-parse-phpdoc
With this option enabled, PHPLint will parse the phpDocumentor DocBlocks comments and will collect short/long descriptions, variables, parameters, etc. herein declared. Sources not intended to the phpDocumentor-compliant often uses /******/ or alike as visual markers, but these markers are not valid DocBlocks and an error is raised: in this case disabling this option is required.
--doc-help
A summary of the options of the PHPLint Documentator gets shown. See the manual of the PHPLint Documentator for a detailed explanation of these options.

Error detection:

--ctrl-check (default)
--no-ctrl-check
Reports control characters found in strings.
--ascii-ext-check (default)
--no-ascii-ext-check
Reports "extended ASCII" characters (ISO-xxx, UTF-8, etc.) found in the source, either in the literal strings and in IDs. The bare HTML code outside the PHP code is not checked.
--print-notices (default)
--no-print-notices
--print-warnings (default)
--no-print-warnings
--print-errors (default)
--no-print-errors
Print error/warning/notice messages. Errors are problems that might involve safety or security issues, or that may prevent the program from running at all. Notices have mostly informational purpose, but are still useful to "fine tuning" the source (unused variables, misspelled case-insensitive names, etc.). Warnings are all the other cases that PHPLint was not able to resolve neither as error nor as notice, anyhow human attention is required.

Format of the report. The report generated on standard output is a sequence of lines having this general structure:

file_name:line_no: severity: textual description
?: notice: textual description

where

file_name is the absolute path of the file the message is referring to,

line_no is the line number, the first line of the source being the number 1,

and the severity can be FATAL ERROR, ERROR, Warning or notice.

Lines beginning with a question mark are notices PHPLint cannot relate to a specific source file. A fatal error is raised if PHPLint cannot proceed with the parsing because of a severe error or an unsupported feature, then the execution is interrupted abruptly with exit status 1. On normal termination, a last line summarizes the total number of errors found:

Overall test results: number errors, number warnings.

The format described here is suitable to be post-processed by other programs. Several options can modify the format of the report, mostly to render it more readable:

--print-source
--no-print-source (default)
Print the source, line by line, along with its number before the line be parsed. Source and diagnostic messages are so mixed in a human-readable form.
--print-line-numbers (default)
--no-print-line-numbers
Enable/disable the line numbers printed along the source, when --print-source is enables. Disabling line numbers the shorter report so generated is suitable to be compared with an old one to see the differences and to monitor the improvements of the source, avoiding to confuse the diff program even if the source gets slightly changed adding or removing lines.
--print-file-name (default)
--no-print-file-name
Every error message marks the message with the name of the file, the line number and the column number. The resulting report file might become very long and difficult to read, especially if absolute file names are printed. When this option gets disabled, and the source gets printed (--print-source), the file name of the source itself is not printed to make the report a bit more readable to humans; only the line number gets printed. The file name gets printed only if the message involves errors found in imported modules.
--print-path absolute (default)
--print-path relative
--print-path shortest
By default file names are always displayed as absolute paths, so to simplify the work of automated programs to which the report has to be submitted for further processing. The report can be made more human readable enabling the displaying of paths relative to the current working directory (the directory from which PHPLint was started). The option shortest automatically choose the shortest representation between the absolute and the relative path.
--report-unused (default)
--no-report-unused
Report unused items. Items collected from here on are reported in a notice message if not used. This include both global entities (constants, global variables, functions, classes, class constants, properties and methods) and local variables inside functions and methods.
--print-context (default)
--no-print-context
When an error is detected, the line of the source involved is printed along the error message. The exact point of the line where the error was detected is visually marked with "\_HERE".
--tab-size N (default: 8)
Set the size of the tabulation character (ASCII HT) typically used to indent the code. Every error message raised by PHPLint is preceded by the file name, the line number and column number. The tabulation size is required to exactly calculate the column where the problem was detected.

Exit status. The program exits with a status code of 1 on error or fatal error, or 0 if only warnings and notices occurred.

Examples

A typical interaction with the phplint command might look like this one:

$ cat test-page.php
<?php
/*. require_module 'standard';  .*/
require_once 'banner.php';
echo $undef_var;
phpinfo();
?>

$ phplint --modules-path phplint/modules test-page.php
4: ERROR: variable `$undef_var' has not been assigned
4: Warning: can't determinate type of the argument of `echo'
?: notice: required module: standard
Overall test results: 1 errors, 1 warnings.

The module phplint/modules/standard will be loaded and the file banner.php will be parsed as well. In this example, PHPLint raised an error about a variable never assigned.

It might be useful to validate an existing source that was not intended to be PHPLint-compliant. In this case every file of the project should be edited in order to add the extension modules required: boring. Useful trick: a dummy.php file can contain the list of the required extensions used by your project:

<?php  # dummy.php
/*.
    require_module 'standard';
    require_module 'session';
    require_module 'mysql';
    require_module 'pcre';
.*/
?>

then this file can be added to the command invoking PHPLint, just before the file being checked:

$ phplint options dummy.php myPage.php

There are several options and many long pathfiles to type. You may use the GUI version of the program (phplint.tk), but there is also another useful trick: define your own phpl program using a scripting language. Here is the script I use, written in the Bash shell scripting language, you should only modify the pathfiles and the options accordingly to the layout of your system:

#!/bin/bash
# Syntax: phpl OPTIONS FILES
d=/home/yourname/src/phplint   # PHPLint base dir.
$d/src/phplint \
    --no-print-file-name \
    --print-path shorter \
    --modules-path $d/modules \
    /home/yourname/dummy.php \
    --print-source \
    --print-context \
    --doc-ref-remap $d/modules/ \
    http://www.icosaedro.it/phplint/modules.cgi?module= \
    "$@"

Checking a file while debugging the application becomes as simple as typing the command:

$ phpl myprogr.php | less

The less command allows to move up and down through the report; jumping to the next error requires to type /HERE and jumping to the next error requires only to press the n key, or SHIFT-n to jump backward to the previous one. Once the errors have been fixed, the documentation can be generated simply adding the --doc option, and the file myprogr.html will be produced:

$ phpl --doc myprogr.php
 

An example

In these examples we will use the phpl script defined in the previous chapter Usage.

PHPLint used as a debugging tool

Here we have a WEB page intentionally pourly written. PHPLint helps discovering many bugs:

$ phpl very-bugged-web-page.php

BEGIN parsing of bugged-web-page.txt
1:  <HTML>
2:  <BODY>
3:  <H1>BUGGEDD WeB pAGE</H1>
4:  <?
5:      /*. require_module 'standard'; .*/
6:  
7:      define('MY_LIBS', '/here/they/are/');
8:      include(MY_LIBS . "commons");

        include(MY_LIBS . "commons");
                                     \_ HERE
**** 8: notice: include "/here/they/are/commons"
9:      require("more-stuff");

        require("more-stuff");
                              \_ HERE
**** 9: Warning: require "more-stuff" uses relative pathfile, check `include_path' in php.ini
10: 
11:     define(Pi, 3.14159);           # it should be define('Pi', ...

        define(Pi, 3.14159);           # it should be define('Pi', ...
                  \_ HERE
**** 11: ERROR: undeclared constant `Pi'
12:     $debugging = TRUE;
13: 
14:     function PrintNumber($i, $k)

        function PrintNumber($i, $k)
                                \_ HERE
**** 14: Warning: undefined type for argument `$i', presuming `mixed' and trying to continue anyway. Hint: either use an explicit type (example: `/*.int.*/ $i') or assign a default value (example: `$i=123').

        function PrintNumber($i, $k)
                                    \_ HERE
**** 14: Warning: undefined type for argument `$k', presuming `mixed' and trying to continue anyway. Hint: either use an explicit type (example: `/*.int.*/ $k') or assign a default value (example: `$k=123').
15:     {
16:         global $debugging;         # global var. not used...
17:         echo("$k");                # will print nothing
18:                                    # argument $i not used
19:     }
20: 
21:     for($i=1; $i<=10; $i++){

        for($i=1; $i<=10; $i++){
           \_ HERE
**** 21: notice: in the last function, variable `$debugging' declared global but not used
**** 14: notice: variable `$i' assigned but never used
**** 14: notice: guessed signature of the function `PrintNumber()' as void(mixed, mixed)
22:         printNumber($i, 2, 3);     # mispelled; too many arguments

            printNumber($i, 2, 3);     # mispelled; too many arguments
                        \_ HERE
**** 22: notice: function `printNumber' was declared in line 14 as `PrintNumber' that differ by upper/lower-case letters only

            printNumber($i, 2, 3);     # mispelled; too many arguments
                                \_ HERE
**** 22: ERROR: `PrintNumber()' declared in line 14: too many arguments
23:     }
24: 
25:     if( $debuging ){               # give me a 'g'...

        if( $debuging ){               # give me a 'g'...
                       \_ HERE
**** 25: ERROR: variable `$debuging' has not been assigned
26:         echo "Today is: ", date("Y-m-d");
27:                                    # ...or the date will never
28:                                    # be printed!
29:     }
30:     $first_name = "John';          # unclosed double quote

        $first_name = "John';          # unclosed double quote
                                                              \_ HERE
**** 30: Warning: found control character (line feed, LF, 10) in literal string. This msg is reported only once for each string
31:     $last_name = 'Smith";          # the variable isn't assigned...

        $last_name = 'Smith";          # the variable isn't assigned...
                  \_ HERE
**** 31: ERROR: variable `$last_name' has not been assigned
32: 
33:     echo $first_name, " ", $last_name;
34:                                    # $last_name isn't defined
35: 
36:     $radius = 10.0;
37:     $circum = 2.0 * PI * $radius;  # mispelled constant name

        $circum = 2.0 * PI * $radius;  # mispelled constant name
                            \_ HERE
**** 37: ERROR: undeclared constant `PI'
38:     echo "Circumference=$circum";  # will print 0 for any $radius...
39: 
40:     if( ! file_exists("important-data.dat") ){
41:         mai1("webmaster", "ALERT, important data missing!", "");

            mai1("webmaster", "ALERT, important data missing!", "");
                 \_ HERE
**** 41: Warning: function `mai1()' (still) not declared. Guessing signature from its usage. Hint: it's better to declare the functions before their usage.
42:                                    # this mail will never be sent
43:                                    # because "mail" is mispelled
44:     }
45: 
46:     # ...and still "php -l" gives "no errors" :-)
47: 
48: ?>
49: </BODY></HTML>
END parsing of bugged-web-page.txt
**** bugged-web-page.txt:12: notice: variable `$debugging' assigned but never used
**** bugged-web-page.txt:11: notice: undeclared constant `Pi' used only once: misspelled?
**** bugged-web-page.txt:37: notice: undeclared constant `PI' used only once: misspelled?
**** bugged-web-page.txt:41: ERROR: undeclared function `mai1()' used only once: misspelled?
**** ?: notice: unused package `../dummy.php'
**** ?: notice: required module `standard'
Overall test results: 6 errors, 5 warnings.

PHPLint used for project development

This source is a simple class that lets to a build a multipart mixed MIME email.

<?
/*.
    DOC    MIME multipart/mixed email composer.

    <@package MimeMultipartMixedMail>
    <@version 1.0>
    <@author <a href="mailto:phplint@icosaedro.it">Umberto Salsi</a>>

    BUGS:
    <ul>
    <li> The subject should be encoded as per the RFC 2047.
    <li> Every text part should specify the "charset" property
    </ul>
.*/

/*. require_module 'standard'; .*/

class MimeMultipartMixedMail
/*. DOC Send an email using the MIME multipart/mixed format. .*/
{
    public $subject = "";
    /*. DOC The subject of the email, initially empty. .*/

    private $boundary = "";
    private $headers = "";
    private $body = "";


    public /*. void .*/ function __construct()
    {
        #$this->boundary = md5( uniqid( (string) time() ) );
        $this->boundary = "__";
        $this->headers = "MIME-Version: 1.0\r\n"
            . "Content-Type: multipart/mixed; boundary = "
            . $this->boundary;
    }


    public /*. void .*/ function AddHeader(/*. string .*/ $header)
    /*. DOC Add a custom header to the email.

        The argument $header must be a valid RFC 2822 email header,
        having the form "<code>HeaderName: header value</code>".
        Typically you might want to add some Cc: or Reply-To: header.
        Example:
        <p>
        <blockquote><code>
        $e-&gt;AddHeader("Reply-To: junk@mydomain.com");
        </code></blockquote>
    .*/
    {
        $this->headers .= "\r\n$header";
    }


    public /*. void .*/ function AddPart(
        /*. string .*/ $ctype,
        /*. string .*/ $name,
        /*. string .*/ $message
    )
    /*. DOC Add a MIME part to the email.

        $ctype is the MIME type of the part. Examples:
        "text/plain", "image/jpeg"<br>

        $name is the file name containing the data. The name
        provided in this parameter is sent to the remote client
        as an hint to compose the name of the file where to
        save the data. If NULL, this info is not sent. Examples:
        "price_list.txt", NULL<br>

        $message contain the effective data to be sent, in binary form.
        <p>

        Example:
        <blockquote> <code>
        $e-&gt;AddPart("image/gif", "product.gif", file_get_contents("123456.gif"));
        </code> </blockquote>
    .*/
    {
        $this->body .=
            "--" . $this->boundary . "\n"
            . "Content-Type: $ctype"
                . ( $name === NULL? "\n" : "; name=\"$name\"\n" )
            . "Content-Transfer-Encoding: Base64\n\n"
            . chunk_split( base64_encode( $message ) ) . "\n";
    }


    public /*. bool .*/ function Send(/*. string .*/ $to)
    /*. DOC Send the email.

        The email composed up-there is sent using the built-in
        mail() function. Note that you may call this function
        several times to send the same message to different
        recipients.<p>

        Returns the exit status of <@item mail()>.
    .*/
    {
        return mail(
            $to,
            $this->subject,
            $this->body . "--" . $this->boundary . "--\n",
            $this->headers
        );
    }

}

/*. DOC  <p>Example of usage of this package:
<blockquote><pre>
$m = new MimeMultipartMixedMail();
$m-&gt;subject = "Just testing the class MimeMultipartMail";
$m-&gt;AddHeader("From: Umberto Salsi &lt;phplint@icosaedro.it&gt;");
$m-&gt;AddHeader("Errors-To: phplint@icosaedro.it");
$m-&gt;AddPart("text/html", NULL, "&lt;html&gt;&lt;body&gt;This is a test.&lt;/body&gt;&lt;/html&gt;");
$m-&gt;AddPart("image/jpeg", "test.jpg", file_get_contents("test.jpg"));

$m-&gt;Send("a_guy@somewhere.com");

$m-&gt;Send("another.guy@domain.it");
</pre></blockquote>
.*/

?>
 

How To...

This chapter contains a collections of suggestions to get the best results developing with PHPLint.

The declarations first.
Since PHPLint is a single-pass parser, it is better to declare things before to use them. On a typical WEB application, you might collect all the constants, global variables, functions and classes on a file, and then you can include this file in any page.

Constants should be... constants!
The expression defining a constant should be statically determinable by the parser. Don't use variables and functions, but only literals (numbers and strings) and other constants. If an expression isn't statically computable, PHPLint will complain not to be able to calculate its value or its type.

Assign a value to the global variables.
PHPLint can guess the type of these variables from the result of the expression. Isn't required that this expression be statically evaluable, since what is of interest to PHPLint is the resulting type, not the value. For example:

$now = time();           # time() returns an int
$tout = $now + 100;      # adding two int gives another int
$today = date("Y-m-d");  # date() returns a string

Declarations of functions and methods.
Complete the declarations indicating the type of the returned value, or void if no value is returned. For any formal argument, add its type. This is particularly recommended for libraries that are used often, but it is useful also for ordinary programs to enforce a strict type checking.

Class properties.
Always assign them a type and/or a value, as a literal or a static expression. PHPLint uses the result if the expression to guess the type of the property whenever not given explicitly.

Safe handling of the data coming from the browser.
Remember that the type of the elements of the array $_POST and $_GET can be either strings or arrays. Always check for the existence of the parameter and its actual type. See the chapter Predefined superglobals for examples.

Don't use the function each().
This function returns a generic array, so loosing the type of the elements of the array passed by argument. The following code

/* WRONG CODE: */
$a = array(1, 2, 3);
reset($a);
while( list($k, $v) = each($a) ){ echo $k, $v; }

although valid for the PHP interpreter, raise many error messages because the expression inside the while(EXPR) is expected to be of the type boolean, and the types of the variable $k and $v cannot be determined. The same code can be rewritten as:

$a = array(1, 2, 3);
foreach( $a as $k => $v ){ echo $k, $v; }

Some functions might return values of different types.
For example, the strpos($str, $substr) function returns an integer number that is the position of the string $substr inside the string $str, or it returns the boolean value FALSE if the sub-string was not found. The right way to handle this case is:

$pos = strpos($str, $substr);
if( $pos === FALSE )
    # not found
else
    # found

If you are interested only to know if the sub-string is contained inside the string, the code can be made shorter:

if( strpos($str, $substr) !== FALSE )
    # found!
else
    # not found.

Another example is the function ereg(). The code

/* WRONG CODE: */
if( ereg("^[0-9]+$", $str) ){ }

is not valid because the if(EXPR) instruction require a boolean expression, but the function ereg() formally returns an integer number, that is the number of characters of the given string that match the regular expression. Even more: the function ereg() returns FALSE if the regular expression does not match the given string. If you are interested on the value returned by the function, you should write something like this:

$n = ereg("^[0-9]+", $str);
if( $n === FALSE )
    # does not match at all
else
    # $n leading digits found

If, instead, you are only interested to check if the given string match or not the regular expression, this is the right way:

if( ereg("^[0-9]+$", $str) !== FALSE )
    # it match

The function fopen() formally returns a value of the type resource, but it might return a boolean FALSE value as well if the file cannot be opened. This is the right way to use this function:

$f = fopen("xyz", "r");
if( $f === FALSE )  die("cannot open xyz");

Invalid ways:

/* WRONG CODE: */
$f = fopen("xyz", "r") or die("cannot open xyz");
# the operands of the "or" operator aren't boolean values
# because fopen() returns a resource, and die() does not
# return a value at all.

/* WRONG CODE: */
if( $f = fopen("xyz", "r") ) die("cannot open xyz");
# the expression inside if(EXPR) isn't of the type boolean.

/* Valid, but confusing: */
if( ($f = fopen("xyz", "r")) === FALSE ) die("cannot open xyz");

Beware of unsafe string comparisons.
When a string looks like a number, PHP try to compare strings as such, so often giving unexpected results. For example, these comparisons give TRUE, although a strict character-by-character comparison would give a well different result:

"1.00" == "1"
"01" >= "1"
"02" > "1"

That's why PHPLint allows to compare strings only using the strict comparison operators === and !== since these two operators gives the expected results. Any other string comparison should be done with the strcmp() function. This table should help in translating commonly used, unsafe, PHP string comparisons into safe, PHPLint-compliant, string comparisons:


Invalid:Valid:
$a == $b $a === $b
or
strcmp($a, $b) == 0
but see the note below.
$a != $b $a !== $b
or
strcmp($a, $b) != 0
but see the note below.
$a < $b strcmp($a, $b) < 0
$a <= $b strcmp($a, $b) <= 0
$a >= $b strcmp($a, $b) >= 0
$a > $b strcmp($a, $b) > 0

It is interesting to note that introducing the strcmp() function, the comparison operator does not change. Simply, we have mapped

$a operator $b
to
strcmp($a, $b) operator 0

Note. There is a subtle difference between the strcmp() function and the strict comparison operators when one of the strings being compared is NULL. According to the strcmp() function the NULL value is equivalent to the empty string "". By the contrary, for the strict comparison operators a NULL value is different from an empty string.

 

Reserved keywords

There are some differences on how PHP and PHPLint scan the source of a program. PHPLint behave much like a compiler would, first scanning the source in distinct symbols and keywords, then parsing the semantic of these symbols. Instead, the PHP interpreter act much like a scripting language would do, and some of the words that compose the source have a meaning that depend on the context.

PHP keywords

These keywords are reserved by the parser of the PHP code, and cannot be used for names of constants, variables, functions, classes, class constants, properties or methods.

FALSE NULL TRUE abstract and array as bool boolean break case catch class clone const continue declare default define die do double echo else elseif enddeclare endfor endforeach endif endswitch endwhile exit extends final float for foreach function global if implements include include_once instanceof int integer interface isset list new object or parent print private protected public real require require_once return self static string switch throw trigger_error try use var while xor

This list of keywords is longer than that stated by the PHP manual. Moreover, variables, properties and methods cannot take the name of a keyword, also if this name might be valid for the PHP interpreter. For example, this code:

define('function', 'xxxx');

class string
{
    private $private = "yyyy";

    function int()
    {
        $private = $this->private;
    }
}

is valid for the PHP 5 interpreter, but it is not for PHPLint because the constant name function, the class name string, the property name $private, the function name int(), the variable name $var and the property ...->private are all reserved keywords. PHPLint will raise an error if a constant or variable name collide with a keyword, and a fatal error in all the other cases.

PHPLint keywords

These keywords are reserved by PHPLint when parsing the extended code inside the comments "/*. .*/":

abstract args array bool boolean class else end_if_php_ver extends final float forward if_php_ver_4 if_php_ver_5 implements int integer interface missing_break missing_default mixed object parent private protected public require_module resource self static string throws void

Since the extended code can include the class names, class names cannot collide with these keywords.

 

To-do list

Missing features

PHP features intentionally omitted

Other differences

Wish list

 

Memorandum

A brief summary of the goodnesses and the oddities of the PHP language. All the examples are tested on PHP version 5.0.4 with error_reporting set to E_ALL | E_STRICT.

Source encoding

The source may be encoded in any of the ISO-8859 charsets, or UTF-8 or any other encoding that preserves the representation of the ASCII charset. PHP isn't really aware of the encoding of the file, provided that its keywords and symbols be recognizables as sequences of ASCII characters. For example, the reserved keyword "for" must be a sequence of the corresponding three ASCII characters, whatever the encoding of the source may be.

Strings are sequences of bytes without any particular encoding. The multi-byte strings library mbstring provides the support for the conversions from any encoding to another. UTF-8 is the preferred encoding for internationalized applications.

Identifiers

In what will follow, an identifier is any sequence of letters, digits and the underscore "_" character. The first character cannot be a digit. The letters include the ASCII lower-case characters a...z, the ASCII upper-case characters a...z, and any other code from 127 to 255, so that an ID may be encoded into the charsets ISO-8859, UTF-8, etc. The ASCII control code 127 (DEL) is a valid "letter". By default, PHPLint raises a warning if an ID contain this control code or any non-ASCII characters.

Constants

The constants (not to be confused with the class constants, discussed below) have their name defined by a string ("PI"), but they must be used as an unquoted identifier (PI).

Their name is case-sensitive, but the instruction define() has a third optional argument that allows the constant name to be used in any combination of upper-case and lower-case letters. I have not tested is this case-insensitiveness works when the constant name contain letters of some extended charset, but since PHP is unaware of the encoding of the source (it might be either ISO-8859-15 or UTF-8), I think this feature be restricted to the ASCII letters only. There is no reasons to use this feature, so PHPLint don't allows this third arguments, and constants are always case sensitive.

All this has some strange consequences:

define("VALID_NAME", 123);
echo VALID_NAME;
# ok

define("1nv@lidname", 123);
# PHPLint: invalid constant name
# PHP: accepted, but it can't be used because is an invalid ID!

define("foreach", 123);
# PHPLint: error: the constant name is a keyword
# PHP: accepted, but it is a reserved word and can't be used!

The scope of constants is global and they, once defined, can be seen everywhere. Their instance is global too: there is always one and only one constant with a given name.

Constants cannot be re-declared.

Constants may be declared inside a function, but the scope of the constant is still global. So this function cannot be called twice because there would be a collision with the previous declaration. PHPLint raises a warning if a constant is declared inside a function. The namespace of the constants is distinct from that of the functions and that of the classes, so that there may be several items sharing the same name but with different meaning, depending on the context where these IDs where found. In this example, the ID "A" is heavily overridden:

define("A", 123);
$A = 456;
function A(){}
class A{}

echo A, $A;
A();
$a = new A();

It may be a bit confusing, and in some version in the future PHPLint might raise a warning if any of these cases occurs.

Variables

Their name is an ID with a leading "$" character. The name is case-sensitive.

The scope may be global or local to a function or method. The variables instantiated inside a function are automatically released exiting that function. A variable local to a function cannot be seen outside that function, and does not collide with the name of other global variables and with the local variables of the other functions.

Some special variables, named "superglobals", have always global scope. Superglobals are defined by the PHP interpreter and have a special meaning. The programmer cannot define new superglobal variables.

The ID part of the name of a variable may be a reserved word: $function, $define and $true are all valid variable names. PHPLint raises an error in these cases.

Functions

The name of a function is an ID and cannot be re-declared. Their namespace is global.

The name of a function is case-insensitive: Print(), print() and PRINT() are the same. PHPLint promotes a clean programming style, and raises a warning if a function is used with names that differ only by upper-case and lower-case letters.

A function may be declared inside another function, but the first function cannot be called once more because there would be a collision in the namespace with the previous declaration, like in this example:

function parent_f()
{
    function nested_f()
    {
    }
}

parent_f(); # ok; now also nested_f() exists
parent_f(); # FATAL ERROR

So, practically, nested functions aren't allowed in PHP as them cannot be used in a natural way. Nested functions are there in PHP only to allow inclusions of files inside a function. But beware: "global" variables of the included file becomes local variables of the function!

PHPLint raises a warning on nested functions. The require_once statement is allowed only at global scope and the included file is parsed recursively by PHPLint. The require, include and include_once statements are allowed inside functions, but the included files are not parsed recursively by PHPLint.

Classes

Class names are case-insensitive. "Inner" classes are not allowed. Classes may be declared inside a function, but if this function is called twice it is a fatal error. Classes cannot be defined inside a method. The scope of classes IDs is global. The namespace of the entities declared inside a class is protected by the name of the class and does not collide with global items.

Constants, properties and methods declared inside the class each one has a reserved namespace; the syntax of the language allows to discern which item has to be used. For example there may be an "x" constant, a "$x" property and a "x()" method that can be referred as CLASS::x, CLASS::$x or $obj->x and CLASS::x() respectively.

Class::Constants

Case-sensitive.

Class::Variables (aka Properties, aka Members)

Case-sensitive.

Class::Functions (aka Methods)

Case-insensitive.

 

License

Copyright (c) 2005, Umberto Salsi, icosaedro.it di Umberto Salsi
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 

The program

PHPLint is available in three forms: on-line WEB interface, M2 source, C source derived from the M2 source. A binary, ready to run version isn't currently available, so you must compile from the sources yourself.

The stand-alone version compiled from the sources (either M2 or C) is highly recommended to check complex projects, since it allows to validate many source files at once - see the chapter Usage for insights. The WEB version is here just to try PHPLint, and it might be useful to check simple chunks of code.

The chapter Usage explains how the program can be used, lists all the options, and gives some hints about the usage of the program. The chapter How To... contains a collections of suggestions to get the best results developing with PHPLint.

On-line WEB version

You can try PHPLint directly on-line in www.icosaedro.it/phplint/phplint-on-line.html. Some of the examples are taken from this same WEB site, but you can provide your own code, either typing by hand, or using the copy/paste from your text editor. There is not a limit to the length of source to be tested, but to check projects of real interest you will need to download and install the program on your system.

Some of the examples proposed passes the check without raising error messages. On some others examples PHPLint complains about undefined items in files not included in the check.

For security reasons, the recursive inclusion of files is disabled. Again, if your source requires two or more files mutually dependent, they can be checked one by one separately; as a logical consequence, PHPLint will raise an error for every unknown cross-referenced item.

IMPORTANT. Only the "standard" extension module is included by default. Every other extension your source might require MUST be specified explicitly. For example, to include the support for MySQL, this PHPLint meta-code must appear at the start of the code:

/*. require_module 'mysql'; .*/

Stand-alone version for Microsoft Windows

Currently, there is not a binary version of PHPLint, nor there is an installer. You need to download the source and compile the program by yourself.

If you simply want to use PHPLint:

  1. Download and install the Cygwin Development System (www.cygwin.com). Ensure the modules gcc-core and make be selected in the installation mask.
  2. Download the pure-C version of PHPLint as listed at the bottom of this page. Put the compressed file in the home directory of Cygwin, then explode the file with the commands below:
    $ tar xvzf phplint-pure-c-200XXXXX.tar.gz
    $ cd phplint-pure-c-200XXXXX
    $ gcc src/phplint.c -o src/phplint
    $ strip src/phplint.exe
    $ src/phplint --help
    
    where 200XXXXX obviously is the current version of PHPLint.

If you want to be involved in the PHPLint development:

  1. Install the Cygwin Development System and the M2 Development System as explained in this page: www.icosaedro.it/m2/install-windows/index.html.
  2. Download the M2 source version of PHPLint as listed at the bottom of this page. Put the compressed file in the home directory of Cygwin, then explode the file with the commands below:
    $ tar xvzf phplint-200XXXXX.tar.gz
    $ cd phplint-200XXXXX
    $ ./configure
    $ make
    $ src/phplint --help
    
    where 200XXXXX obviously is the current version of PHPLint.

Stand-alone version for Linux/Unices

Currently, there is not a binary version of PHPLint, nor there is an installer. You need to download the source and compile the program by yourself.

If you simply want to use PHPLint:

  1. Ensure the C development system be installed: basically you will need gcc and make.
  2. Download the pure-C version of PHPLint as listed at the bottom of this page, then explode the file with the commands below:
    $ tar xvzf phplint-pure-c-200XXXXX.tar.gz
    $ cd phplint-pure-c-200XXXXX
    $ gcc src/phplint.c -o src/phplint
    $ strip src/phplint
    $ src/phplint --help
    
    where 200XXXXX obviously is the current version of PHPLint.

If you want to be involved in the PHPLint development:

  1. Ensure the C development system be installed: basically you will need gcc and make.
  2. Download and install the M2 Development System as explained here: www.icosaedro.it/m2. In order to succesfully compile this source under GCC 4 you will need the new M2 Development System version 20070924 or above. Older versions of M2 does not work under GCC 4.
  3. Download the M2 source version of PHPLint as listed at the bottom of this page, then explode the file with the commands below:
    $ tar xvzf phplint-200XXXXX.tar.gz
    $ cd phplint-200XXXXX
    $ ./configure
    $ make
    $ src/phplint --help
    
    where 200XXXXX obviously is the current version of PHPLint.

PHPLint Packages

Dowload here the PHPLint package, choosing between the original M2 version or the pure-C version as explained above:

 

Changes

Version 0.8-20090517:
====================
+ Perform static flow analysis and detect unreachable code and missing return
  statements.
+ In `switch' statement: `case' brances and `default' branch now allowed
  in any order (previously `default' had to be the last).
+ Compound statement {...} now allowed.
+ phpDocumentator DocBlock:
  + Fixed bug in list rendering.
  + Added inline tag {@link}.
  + Added <kbd> speudo-HTML entity.
  + Short description now allows speudo-HTML entities and inline tags.
  + Unimplemented and unknown line tags and inline tags are signaled and then
    rendered verbatim.
+ Added option --[no-]is-module.
+ Updated modules: pdo, spl, standard.

Version 0.8-20090428:
====================
+ Bug fix: in some cases did not correctly built inheritance path of
  extended/implemented methods, causing a program abort.
+ Bug fix: inherited abstract methods cannot be redeclared as abstract
  (PHP restriction).
+ Bug fix: detects circular references in class declarations.
+ Detects redundant class extension and implementation of classes that are
  already in the inheritance tree of the class or interface being declared.
+ Prototypes: `extends' for concrete and abstract classes now supported
  (`implements' still is not) (Jirka Grunt).
+ Prototypes: interface prototypes now supported (but `extends' still
  unimplemented) (Jirka Grunt).
+ DocBlock: @throws line tag added (extension to phpDocumentator commonly
  accepted) (Jirka Grunt).
+ `final' special methods (__construct, __set, __get, ...) now allowed (Jirka
  Grunt).
+ Documentator: fully documents class hierarchy with inheritance tree.
+ Documentator: fully documents descriptions of thrown exceptions (see @throws).
+ Updated modules: curl, ftp, gd, mhash, mysqli, standard (Jirka Grunt),
  spl (Jirka Grunt), standard_reflection, variant.
+ Added modules: xdebug (Jirka Grunt).

Version 0.8-20090102:
====================
+ Class constants now support the visibility flags as meta-code (Jirka Grunt).
+ "... instanceof $str | $obj" now supported (Jirka Grunt).
+ "... instanceof self" and "... instanceof parent" now supported.
+ "new CLASS_NAME();" is now accepted as valid statement.
+ Constructor signature now allowed in interfaces as required by PHP >= 5.2.0
  (Jirka Grunt).
+ Relaxed signature compatibility rule: sub-classes allowed.
+ Missing ?> at the end of the file now allowed (Jirka Grunt).
+ Removed the dummy type `number' because it is not a proper PHP type; use
  `float' instead.
+ Exceptions:
  + Added meta-code 'throws' declaration for functions, methods and protos.
  + Checks exceptions inherited from overridden and implemented methods.
  + Checks branches in try...catch statement.
+ In DocBlock: lines missing the leading `*' are ignored, but following lines
  are handled correctly (fix).
+ Standard module: changed `number' with `float'.
+ Modules mysqli, readline, session, standard: calbacks are now `mixed'
  to support for both functions and methods (Jirka Grunt).
+ Updated modules: standard_reflection, spl.

This is only a partial list that reports changes in the 3 last published versions of the program. The complete list is available in the file CHANGES of the distributed package.

 

References

 

The syntax of PHPLint

The language parsed by PHPLint is a (nearly complete) subset of the PHP language. Here the syntax is described through the modified BNF formalism (also known as EBNF) - see www.icosaedro.it/bnf_chk/index.html for more details. The elements whose name begin with "x_" belong to the special PHPLint meta-code and they must always appear inside a multi-line comment /*. .*/. The package (element no. 1) gives the structure of the file being parsed.

NOTE. The official BNF-like syntax of the PHP language is available in the file Zend/zend_language_parser.y of the PHP source package. A more readable version of this syntax converted in EBNF and formatted as HTML is available in the page PHP EBNF Syntax and refers to the PHP version 5.1.1 (the comments are in italian language, but the EBNF is still readable by anyone).
1. package = { text2 | code_block3 | echo_block4 } ;

2. text = "one or more characters excluding the special sequence <?" ;

3. code_block = ( "<?" | "<?php" ) { statement49 | x_function_proto119 | x_class_proto122 } "?>" ;

4. echo_block = "<?=" expr17 { "," expr17 } "?>" ;

5. constant = id10 ;

6. variable = "$" id10 ;

7. func_name = id10 ;

8. class_const = id10 ;

9. class_var = "$" id10 ;

10. id = ( letter11 | "_" ) { letter11 | digit132 | "_" } ;

11. letter = "a".."z" | "A".."Z" | "\x7F".."\xFF" ;

12. static_expr = "FALSE" | "TRUE" | "NULL" | constant5 | literal_int127 | literal_float135 | literal_string138 | [ "+" | "-" ] static_expr12 | static_class_const13 | static_array15 | x_formal_typecast165 static_expr12 ;

13. static_class_const = ( class_name14 | "self" | "parent" ) "::" class_const8 ;

14. class_name = class4_name79 | class5_name97 ;

15. static_array = "array" "(" [ static_array_pair16 { "," static_array_pair16 } [ "," ] ] ")" ;

16. static_array_pair = static_expr12 [ "=>" static_expr12 ] ;

17. expr = expr318 { "or" expr318 } ;

18. expr3 = expr419 { "xor" expr419 } ;

19. expr4 = expr620 { "and" expr620 } ;

20. expr6 = "print" expr620 | expr721 { "?" expr17 ":" expr17 } ;

21. expr7 = expr822 { "||" expr822 } ;

22. expr8 = expr923 { "&&" expr923 } ;

23. expr9 = expr1024 { "|" expr1024 } ;

24. expr10 = expr1125 { "^" expr1125 } ;

25. expr11 = expr1226 { "&" expr1226 } ;

26. expr12 = expr1327 [ ( "==" | "!=" | "<>" | "===" | "!==" ) expr1327 ] ;

27. expr13 = expr1428 [ ( "<" | "<=" | ">" | ">=" ) expr1428 ] ;

28. expr14 = expr1529 [ ( "<<" | ">>" ) expr1529 ] ;

29. expr15 = expr1630 [ ( "+" | "-" | "." ) expr1630 ] ;

30. expr16 = expr1731 [ ( "*" | "/" | "%" ) expr1731 ] ;

31. expr17 = ( "!" | "+" | "-" | "~" | "@" ) expr1731 | expr1832 ;

32. expr18 = ( "++" | "--" ) term33 ;

33. term = "NULL" | "FALSE" | "TRUE" | literal_int127 | "INF" | "NAN" | literal_float135 | literal_string138 | "&" term33 | variable6 [ dereference_var34 ] | constant5 | func_call41 [ dereference_object37 ] | class5_name97 dereference_static35 | "self" dereference_static35 | "parent" dereference_static35 | new43 | clone44 | list39 | isset40 | array47 | x_formal_typecast165 term33 | "(" php_type46 ")" term33 | "(" expr17 ")" ;

34. dereference_var = dereference_array36 | dereference_object37 | assign_op38 expr17 | "++" | "--" | "instanceof" ( class_name14 | "parent" | "self" ) ;

35. dereference_static = "::" ( class_const8 | class_var9 [ dereference_var34 ] | class_func_call42 [ dereference_object37 ] ) ;

36. dereference_array = "[]" "=" expr17 | "[" expr17 "]" [ dereference_var34 ] ;

37. dereference_object = "->" ( class_var9 [ dereference_var34 ] | class_func_call42 [ dereference_object37 ] ) ;

38. assign_op = "=" | "+=" | "-=" | "*=" | "%=" | "/=" | ".=" | "&=" | "|=" | "^=" | "<<=" | ">>=" ;

39. list = "list" "(" [ variable6 [ dereference_var34 ] ] { "," [ variable6 [ dereference_var34 ] ] } ")" "=" expr17 ;

40. isset = "isset" "(" variable6 [ dereference_var34 ] { "," [ variable6 dereference_var34 ] } ")" ;

41. func_call = func_name7 actual_args45 ;

42. class_func_call = id10 actual_args45 ;

43. new = "new" { class4_name79 | class_final_name93 | class_regular_name94 | "self" | "parent" } [ actual_args45 ] ;

44. clone = "clone" term33 ;

45. actual_args = "(" [ expr17 { "," expr17 } ] ")" ;

46. php_type = "boolean" | "int" | "integer" | "float" | "double" | "string" | "array" | "object" ;

47. array = "array" "(" [ element48 { "," element48 } ] ")" ;

48. element = [ expr17 "=>" ] expr17 ;

49. statement = define58 | global106 | static107 | echo108 | func_decl76 | class_decl77 | declare51 | if109 | for112 | foreach114 | while115 | do116 | switch61 | continue59 | break60 | exit117 | return118 | x_require_module_statement53 | require54 | require_once55 | include56 | include_once57 | expr17 ";" | try66 | throw68 | text_block50 | empty_statement69 | compound_statement70 ;

50. text_block = "?>" { text2 | echo_block4 } "<?" ;

51. declare = "declare" "(" directive52 { "," directive52 } ")" statement49 ;

52. directive = id10 "=" static_expr12 ;

53. x_require_module_statement = x_require_module167 x_single_quoted174 x_semicolon145 ;

54. require = "require" expr17 ";" ;

55. require_once = "require_once" expr17 ";" ;

56. include = "include" expr17 ";" ;

57. include_once = "include_once" expr17 ";" ;

58. define = [ x_private80 ] "define" "(" expr17 "," expr17 ")" ;

59. continue = "continue" [ expr17 ] ";" ;

60. break = "break" [ expr17 ] ";" ;

61. switch = "switch" "(" expr17 ")" "{" { case62 | default63 | x_missing_default65 } "}" ;

62. case = "case" static_expr12 ":" { statement49 } [ x_missing_break64 ] ;

63. default = "default" ":" { statement49 } [ x_missing_break64 ] ;

64. x_missing_break = x_missing_break64 x_semicolon145 ;

65. x_missing_default = x_missing_default65 x_colon155 ;

66. try = "try" "{" { statement49 } "}" catch67 { catch67 } ;

67. catch = "catch" "(" { class_final_name93 | class_regular_name94 } variable6 ")" "{" { statement49 } "}" ;

68. throw = "throw" expr17 ";" ;

69. empty_statement = ";" ;

70. compound_statement = "{" { statement49 } "}" ;

71. function = signature72 [ x_thrown_exceptions126 ] "{" { statement49 } "}" ;

72. signature = [ x_type166 ] "function" [ "&" ] func_name7 "(" [ formal_args73 ] ")" ;

73. formal_args = x_args160 | arg74 { "," arg74 } [ x_comma161 x_args160 ] ;

74. arg = [ x_type166 | php5_type_hint75 ] [ "&" ] variable6 [ "=" static_expr12 ] ;

75. php5_type_hint = "array" | "object" | class_name14 | "self" | "parent" ;

76. func_decl = [ x_private80 ] function71 ;

77. class_decl = class478 | class587 ;

78. class4 = [ x_private80 ] [ x_final81 ] [ x_abstract82 ] "class" class4_name79 [ "extends" class4_name79 ] "{" { property485 | method486 x_method_proto120 } "}" ;

79. class4_name = id10 ;

80. x_private = "private" ;

81. x_final = "final" ;

82. x_abstract = "abstract" ;

83. x_visibility = "public" | "protected" | "private" ;

84. x_static = "static" ;

85. property4 = [ x_visibility83 ] "var" [ x_type166 ] variable6 [ "=" static_expr12 ] { "," variable6 [ "=" static_expr12 ] } ";" ;

86. method4 = { x_abstract82 | x_visibility83 | x_static84 | x_final81 } function71 ;

87. class5 = class_final88 | class_regular89 | class_abstract91 | class_interface92 ;

88. class_final = [ x_private80 ] "final" "class" class_final_name93 class_body90 ;

89. class_regular = [ x_private80 ] "class" class_regular_name94 class_body90 ;

90. class_body = [ extends98 ] [ implements99 ] "{" { const5102 | property5103 | method5104 x_method_proto120 } "}" ;

91. class_abstract = [ x_private80 ] "abstract" "class" class_abstract_name95 [ extends98 ] [ implements99 ] "{" { const5102 | property5103 | method5104 | abstract_method105 } "}" ;

92. class_interface = [ x_private80 ] "interface" class_interface_name96 [ "extends" class_interface_name96 { "," class_interface_name96 } ] "{" { public_const5101 | [ "static" ] signature72 [ x_thrown_exceptions126 ] ";" } "}" ;

93. class_final_name = id10 ;

94. class_regular_name = id10 ;

95. class_abstract_name = id10 ;

96. class_interface_name = id10 ;

97. class5_name = class_final_name93 | class_regular_name94 | class_abstract_name95 | class_interface_name96 ;

98. extends = "extends" [ class_regular_name94 | class_abstract_name95 ] ;

99. implements = "implements" class_interface_name96 { "," class_interface_name96 } ;

100. visibility = "public" | "protected" | "private" ;

101. public_const5 = "const" constant5 "=" static_expr12 { "," constant5 "=" static_expr12 } ";" ;

102. const5 = [ x_visibility83 ] public_const5101 ;

103. property5 = [ visibility100 ] [ "static" ] [ x_type166 ] variable6 [ "=" static_expr12 ] { "," variable6 [ "=" static_expr12 ] } ";" ;

104. method5 = { visibility100 | "static" | "final" } function71 ;

105. abstract_method = "abstract" { visibility100 | "static" } signature72 [ x_thrown_exceptions126 ] ";" ;

106. global = "global" variable6 { "," variable6 } ";" ;

107. static = "static" [ x_type166 ] variable6 [ "=" static_expr12 ] { "," variable6 [ "=" static_expr12 ] } ";" ;

108. echo = "echo" expr17 { "," expr17 } ";" ;

109. if = "if" "(" expr17 ")" statement49 { elseif110 } [ else111 ] ;

110. elseif = "elseif" "(" expr17 ")" statement49 ;

111. else = "else" statement49 ;

112. for = "for" "(" [ expr_list113 ] ";" [ expr_list113 ] ";" [ expr_list113 ] ")" statement49 ;

113. expr_list = expr17 { "," expr17 } ;

114. foreach = "foreach" "(" expr17 "as" [ variable6 "=>" ] [ "&" ] variable6 ")" statement49 ;

115. while = "while" "(" expr17 ")" statement49 ;

116. do = "do" statement49 "while" "(" expr17 ")" ";" ;

117. exit = ( "exit" | "die" ) [ "(" [ expr17 ] ")" ] ";" ;

118. return = "return" [ expr17 ] ";" ;

119. x_function_proto = x_forward144 [ x_private80 ] x_signature123 [ x_thrown_exceptions126 ] x_semicolon145 ;

120. x_method_proto = x_forward144 x_method121 ;

121. x_method = { x_visibility83 | x_abstract82 | x_final81 | x_static84 } x_signature123 [ x_thrown_exceptions126 ] x_semicolon145 ;

122. x_class_proto = x_forward144 { x_private80 | x_abstract82 } x_class146 x_name164 [ x_extends147 x_name164 ] [ x_implements148 x_name164 { x_comma161 x_name164 } ] x_lbrace153 { x_method121 } x_rbrace154 ;

123. x_signature = x_type166 x_function156 [ x_by_ref157 ] x_name164 x_lround149 [ x_formal_args124 ] x_rround150 ;

124. x_formal_args = x_args160 | x_arg125 { x_comma161 x_arg125 } [ x_comma161 x_args160 ] ;

125. x_arg = x_type166 [ x_by_ref157 ] x_variable173 [ x_eq159 ] ;

126. x_thrown_exceptions = x_throws158 x_name164 { x_comma161 x_name164 } ;

127. literal_int = literal_int_8128 | literal_int_10129 | literal_int_16130 ;

128. literal_int_8 = "0" digit8131 { digit8131 } ;

129. literal_int_10 = "1".."9" { digit132 } ;

130. literal_int_16 = "0x" digit16133 { digit16133 } ;

131. digit8 = "0".."7" ;

132. digit = "0".."9" ;

133. digit16 = digit132 | "a".."f" | "A".."F" ;

134. digits = digit132 { digit132 } ;

135. literal_float = digits134 ( decimals136 | scale137 | decimals136 scale137 ) ;

136. decimals = "." digits134 ;

137. scale = ( "e" | "E" ) [ "+" | "-" ] digits134 ;

138. literal_string = single_quoted139 | double_quoted140 | here_doc? ;

139. single_quoted = "'" { "\\\\" | "\\'" | "any char except '\\" } "'" ;

140. double_quoted = "\"" { escaped_char141 | escaped_octal142 | escaped_hex143 | variable6 | "any char except \" \\ $" } "\"" ;

141. escaped_char = "\\" ( "n" | "r" | "t" | "\\" | "$" | "{" | "\"" ) ;

142. escaped_octal = "\\" digit8131 [ digit8131 [ digit8131 ] ] ;

143. escaped_hex = "\\" ( "x" | "X" ) digit16133 [ digit16133 ] ;

144. x_forward = "forward" ;

145. x_semicolon = ";" ;

146. x_class = "class" ;

147. x_extends = "extends" ;

148. x_implements = "implements" ;

149. x_lround = "(" ;

150. x_rround = ")" ;

151. x_lsquare = "[" ;

152. x_rsquare = "]" ;

153. x_lbrace = "{" ;

154. x_rbrace = "}" ;

155. x_colon = ":" ;

156. x_function = "function" ;

157. x_by_ref = "&" ;

158. x_throws = "throws" ;

159. x_eq = "=" ;

160. x_args = "args" ;

161. x_comma = "," ;

162. x_self = "self" ;

163. x_parent = "parent" ;

164. x_name = id10 ;

165. x_formal_typecast = x_lround149 x_type166 x_rround150 ;

166. x_type = "void" | "boolean" | "int" | "integer" | "float" | "double" | "string" | x_array_type171 | "resource" | "mixed" | "object" | x_name164 | x_self162 | x_parent163 ;

167. x_require_module = "require_module" ;

168. x_int = "int" ;

169. x_string = "string" ;

170. x_array = "array" ;

171. x_array_type = x_array170 [ x_index172 { x_index172 } x_type166 ] ;

172. x_index = x_lsquare151 [ x_int168 | x_string169 ] x_rsquare152 ;

173. x_variable = "$" id10 ;

174. x_single_quoted = "'" { "\\\\" | "\\'" | "any char except '\\" } "'" ;


The PHPLint Reference Manual ends here.