phplint@icosaedro.it
www.icosaedro.it/phplint/
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
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.
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.
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.
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:
standard
, mysql
,
session
, etc.). These modules may or may not be present in
your installation of the PHP interpreter, as their presence depends on how
the PHP interpreter was built from the sources.
require_once
.
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".
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:
For example, on my system this program displays:<?php print_r( get_loaded_extensions() ); ?>
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: .
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.
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.
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
.
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.
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 .*/
:
void
is a special type returned by functions that... do not return a value.boolean
is a boolean value, i.e. FALSE
or TRUE
.int
is an integer number, like -123
.float
is a floating-point number, like 1.234
.string
is a string like "abc"
, possibly NULL
.array
is an array, like array("a", "b")
, possibly NULL
.resource
is an handle to a resource, like a file descriptor, possibly NULL
.object
is a generic object, possibly NULL
.CLASS_NAME
is an object of the named class, possibly NULL
. Declaring new classes is the only way to create
new data types in PHPLint.mixed
is any type of value.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.
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 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:
integer is allowed as alternative name for int.$count = $count + $incr; if( is_float($count) ) trigger_error("overflow on \$count", E_USER_ERROR);
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.
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}"
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.
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 |
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 :-)
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()
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
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(
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(
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(
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(
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(
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
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.
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 { ... }
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.
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.
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() ){ ... }
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.
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.
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.
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:
return RHS;
is checked
against the function type (LHS).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.
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
isTRUE
because the string is automatically converted into an integer number;
NULL == ""
isTRUE
because theNULL
value is equivalent to the empty string;
array() == NULL
isTRUE
;
0.57 - 0.56 == 0.1
isFALSE
.
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.
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).
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) |
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) |
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. |
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) .*/ CLASS_NAME ) .*/ |
null | The NULL value can be formally converted to any
type for which PHPLint allows the NULL value (see chapter
Types). |
/*. (bool) .*/ 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.
There are only these predefined constants:
/*. boolean .*/ FALSE
/*. boolean .*/ TRUE
Logical values. PHPLint accepts the lower-case letters versionfalse
andtrue
.
/*. 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 versionnull
.
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
$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
/*. array[string]mixed .*/ $_GET
/*. array[string]mixed .*/ $_POST
/*. array[string]mixed .*/ $_REQUEST
/*. array[string]mixed .*/ $_COOKIE
[]
". 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
[]
" (upload of two
or more files).
/*. array[string]string .*/ $_ENV
/*. array[string]mixed .*/ $_SESSION
/*. string .*/ $php_errormsg
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:
new ClassName()
. The declaration
of a class gives to the new operator the informations required to
allocate the object in memory, initializing the non-static variables and
linking the proper non-static functions and inherited functions.
$obj->var
.
ClassName::$var
.
$obj->func()
.
ClassName::func()
. PHP 4 does not
actually support the static functions, but every function of a class can
be called either statically dereferencing the class name, or non-statically
dereferencing an object; the responsability to use a function in the proper
way is left to the programmer.
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.
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:
::
and ->
respectively).
The new visibility attributes are:
public
protected
/*. protected .*/ var $limited_access_var = 123; /*. protected int .*/ function GetNo(){ ... }
private
/*. 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.
PHP 4 does not provide support for class constants.
Constants can be declared using the define()
statement
at global scope.
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 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.
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, soparent::f()
is the original method, while$this->f()
(if non-static) orCLASSNAME::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 usingparent::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).
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"
Properties cannot be overridden (PHPLint restriction).
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
void
if none is
returned;
/*. 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 .*/){} }
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.
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.
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());
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 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).
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:
where:visibility [static] [ /*. x_type .*/ ] $name1 = STATICEXPR1, $name2 = STATICEXPR2, $name3 = STATICEXPR3;
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 topublic /*. 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 asself::$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. |
A method can be declared using a syntax similar to this one:
[visibility] [static] [final] /*. x_type .*/ function methodName(arguments) { // the code here }
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.
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:
/*.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 likeItems::printList_sort()
, and not simplyItems::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 toprintList()
.
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, soparent::f()
is the original method, while$this->f()
(if non-static) orself::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 usingparent::__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).
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. |
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.
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);
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.
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 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.
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. }
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:File B.php:<?php /*. forward class A{} .*/ require_once 'B.php'; class A { /*. B .*/ function m(){} }
We note that:<?php /*. forward class B{} .*/ require_once 'A.php'; class B { /*. A .*/ function n(){} }
require_once
statements give to PHPLint (and to PHP as well) the
exact loading sequence of all the classes involved. Moreover, the client
packages can load any of the two files or even both in any order, since the
require_once
statement ensures all the required files be loaded.
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.
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
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 {}
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(); }
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 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:
throws
declaration must
list all the exceptions the function is allowed to throw. This is required
because this function can be called before its source be parsed.
throws
declaration can be
used to list the exceptions planned to be added later but that are still not
thrown in the current version of the library. In the rare case of recursive
functions, it may be required to specify in advance the exceptions thrown in
order to make PHPLint aware of the exceptions that can be thrown but that
PHPLint would not detect automatically until it has finished parsing the
function itself.
throws
declaration must list
all the exceptions the method is allowed to throw. This is required because
this method can be called before its source be parsed.
throws
declaration can list
more exceptions that are planned to be added later but that are still not
thrown in the current version of the library. The throws
declaration may be required also when the method recursively calls itself.
throw
declaration must list all the exceptions the methods implementing this
interface may throw. Actual implementations may also add more specialized
exceptions, provided that these exceptions be extensions of those expected.
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:
Exception translation.
The new method might throw a new exception, provided that this new exception be
an extension of any of the exceptions already raised by the original method.
For example, we may define a new exception that will be thrown if an
IOException
occurs:
class OutOfBoundDueToIOException extends OutOfBoundException {} class MyListOnFile { /*. string .*/ function getElement(/*. int .*/ $index) { try { try_to_read_the_element_from_file } catch( IOException $e ){ throw new OutOfBoundDueToIOException( $e->getMessage() ); } } }
In newest releases of PHP, the Exception class also implements a `previous' exception property with which you can also report to the caller the original exception that was thrown:
throw new OutOfBoundDueToIOException($e->getMessage(), $e->getCode(), $e);
More specialized method.
We may create a new method with another name; this new method can now throw
any exception it needs. The original method should then
be disabled calling die()
and made deprecated:
class MyListOnFile { /** @deprecated Please use getElementFromFile() instead. */ /*. string .*/ function getElement(/*. int .*/ $index) { die("this method disabled; use getElementFromFile instead"); } /*. string .*/ function getElementFromFile(/*. int .*/ $index) /*. throws IOException .*/ { try_to_read_the_element_from_file } }
Simply die if the caller cannot handle the exception.
Simply call die()
when the new exception occurs:
class MyListOnFile { /*. string .*/ function getElement(/*. int .*/ $index) { try { try_to_read_the_element_from_file } catch( IOException $e ){ die( $e->getMessage() ); } } }
The rationale behind this radical choice is that client of the
MyList
interface cannot be aware of the new exception this
particular implementation can throw, so dying is the only logical choice.
Move any new exception you may need in the constructor of the new class.
Typically, constructors are not implementation of another constructor
(the only exception can be a constructor defined inside an interface,
but usually there is no any need to do that). So, a constructor can throw
any new exception it needs. In our example, the constructor of
the MyListOnFile
might read in advance all the data from the
file, and then the getElement
method reduces to a bare access
to the CPU memory, so it does not need to throw IOException
anymore:
class MyListOnFile { /. void .*/ function __construct(/*. string .*/ $filepath) /*. throws IOException, OutOfBoundException .*/ { ... } /*. string .*/ function getElement(/*. int .*/ $index) /*. throws OutOfBoundException .*/ { read_the_element_from_memory } }
The features of the PHPLint Documentator are described in the
separated document www.icosaedro.it/phplint/documentator.html
.
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 require
d_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
--help
--php-version 4
--php-version 5
(default)--modules-path DIR1:DIR2:DIR3
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)return
EXPR;
statements are not
signaled as error because prototypes are not actual code to be executed.
trigger_error();
statement may raise error codes that are
otherwise forbidden in regular user's packages, as E_DEPRECATED
.
--no-is-module
, so
several modules can be parsed at once with the flag enabled.
--packages-path DIR1:DIR2:DIR3
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
require_once
statement that appears
at scope level zero.--parse-phpdoc
(default)--no-parse-phpdoc
/******/
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
Error detection:
--ctrl-check
(default)--no-ctrl-check
--ascii-ext-check
(default)--no-ascii-ext-check
--print-notices
(default)--no-print-notices
--print-warnings
(default)--no-print-warnings
--print-errors
(default)--no-print-errors
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 beFATAL ERROR
,ERROR
,Warning
ornotice
.
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-line-numbers
(default)--no-print-line-numbers
--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
--print-path absolute
(default)--print-path relative
--print-path shortest
shortest
automatically
choose the shortest representation between the absolute and the relative
path.
--report-unused
(default)--no-report-unused
--print-context
(default)--no-print-context
\_HERE
".
--tab-size N
(default: 8)Exit status. The program exits with a status code of 1 on error or fatal error, or 0 if only warnings and notices occurred.
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
optionsdummy.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
In these examples we will use the phpl
script defined in the
previous chapter Usage.
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.
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->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->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->subject = "Just testing the class MimeMultipartMail"; $m->AddHeader("From: Umberto Salsi <phplint@icosaedro.it>"); $m->AddHeader("Errors-To: phplint@icosaedro.it"); $m->AddPart("text/html", NULL, "<html><body>This is a test.</body></html>"); $m->AddPart("image/jpeg", "test.jpg", file_get_contents("test.jpg")); $m->Send("a_guy@somewhere.com"); $m->Send("another.guy@domain.it"); </pre></blockquote> .*/ ?>
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
to$a
operator$b
strcmp($a, $b)
operator0
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.
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.
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.
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.
`shell command`
."${VAR}"
and expressions "${EXPR}"
.<script, <%, <%=
.echo $obj;
is ok if the method
$obj->__toString()
is defined.See www.php.net/manual/en/language.types.string.php.$s = <<<'EOT' bla bla bla EOT;
$$x
and $x->$y
.$x()
. Use
call_user_func()
instead.case EXPR
, for example
case someFunc(): ...
. PHPLint allows only expressions whose
value can be statically determinated. This restriction is intentional
(noting prevent PHPLint to be modified to allows generic expressions) because
this is the behavior of most of the compiled languages for performance reasons.
{ }
is allowed only with structured
staments, and it is not allowed alone. For example, this is not allowed:
echo "hello"; { echo " world"; }
include*()
and require*()
statements
to return
something just like the included code were a function.
This is not allowes by PHPLint; use regular functions or static methods.
unset()
. For example, you should not
unset($this)
nor unset($this->property)
.define("aaaà", "aaaà");
get documented as aaaà =
"aaa\xC3\xA0"
. By default PHPLint raises a Warning if non-ASCII
characters appears in IDs or literal strings. Still waiting to see how binary
and textual strings will be implemented in PHP 6: non-ASCII printable chars in
binary strings should be rendered with \xHH codes, while textual strings should
be rendered according to the encoding of the HTML output.
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
.
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.
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.
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.
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.
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.
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.
Case-sensitive.
Case-sensitive.
Case-insensitive.
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.
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.
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'; .*/
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:
$ 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 --helpwhere 200XXXXX obviously is the current version of PHPLint.
If you want to be involved in the PHPLint development:
$ tar xvzf phplint-200XXXXX.tar.gz $ cd phplint-200XXXXX $ ./configure $ make $ src/phplint --helpwhere 200XXXXX obviously is the current version of PHPLint.
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:
$ 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 --helpwhere 200XXXXX obviously is the current version of PHPLint.
If you want to be involved in the PHPLint development:
$ tar xvzf phplint-200XXXXX.tar.gz $ cd phplint-200XXXXX $ ./configure $ make $ src/phplint --helpwhere 200XXXXX obviously is the current version of PHPLint.
Dowload here the PHPLint package, choosing between the original M2 version or the pure-C version as explained above:
phplint-0.8_20090517.tar.gz
(306 KB).
Includes: M2 source, manuals, tutorial.
phplint-pure-c-0.8_20090517.tar.gz
(345 KB).
Includes: C source, manuals, tutorial. Now it works under both GCC 3 and GCC 4.
Warning. Because of the way the literal strings of characters are
encoded, this pure-C source will work only on 32-bit, little-endian processors
as the i386 family of processors. For other 32-bit processors you will need
to change accordingly the macro INTSTR in the file missing.c
,
specifying the correct byte ordering of the machine word.
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.
www.icosaedro.it/phplint/
www.php.net
www.zend.com
A bit outdated (it does not cover PHP 5) but it explains the fundamentals at the base of the PHP language. Very interesting the section about the application techniques and the section about the development of PHP extensions, those that about the PHPlint terminology are called "modules". A "must read" for every PHP programmer.
www.phptr.com
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 '\\"
} "'"
;