Updated: 2019-01-25
This (not so) brief tutorial explains how to use the PHPLint program, how to use the PHPLint Standard Library, and how to make your programs PHPLint-compliant. We assume here the basic installation of the PHPLint package has been done as explained in the README.htm file.
Create a file named hello.php under the PHPLint directory using your preferred text editor containing this source code:
<?php echo "Hello, world!\n";
then open a terminal, change the directory to the PHPLint directory, and type this command to execute the program above:
(Linux) ./php hello.php (Windows) php hello.php
This command starts the php (Linux) or php.bat (Windows) script located under the PHPLint directory, which in turn starts the PHP CLI interpreter, which in turn loads and executes our program, finally displaying what we expect to see.
To validate the source with PHPLint, type this command:
(Linux) ./phpl hello.php (Windows) phpl hello.php
This command starts the phpl (Linux) or phpl.bat script under the same directory of PHPLint, which in turn starts the PHPLint program, and finally the PHPLint program parses our source line by line, character by character displaying the following report:
BEGIN parsing of hello.php 1: <?php 2: echo "Hello, world!\n"; END parsing of hello.php Overall test results: 0 errors, 0 warnings.
So, validation of the source code passed with zero errors and zero warning messages. Errors are issues you must fix. Warnings are issues PHPLint cannot decide whether they are or are not actual errors, but that you should fix anyway.
Every value under PHP has a specific type. PHPLint pushes this concept even further: every variable and every expression must have a statically evaluable type. PHPLint supports the following types: null, boolean, int, float, string, resource, array, object, CLASS, void.
Name | Literal values | Description |
---|---|---|
null | NULL | There is no point to declare something of this type, as it can take only one value: NULL. The NULL value, though, is a bit special and can be assigned to other types too; see below. |
The null type has only one possible value, that is the constant NULL. There is no point in declaring one variable of this type as it can take only a single value: NULL. The NULL value is a bit special, though, because it can be assigned to several "nullable" type we will explain next. | ||
boolean |
FALSE
TRUE |
Under PHPLint there is no automatic conversion from other types to boolean, so zero and one are not equivalent to false and true; you must always write explicit expressions generating proper boolean values. |
int | 0377 255 0xff -98765432 | Integer values are internally represented as signes binary words, 2-complement, 32-bits (on 32-bits processors) or 64-bits (on 64-bits processors). |
float | 3.14 -1.234e5 | Are floating-point numbers internally represented using the IEEE 754 format. |
string |
"abc\r\n"
"" NULL |
A string is a sequence of zero or more bytes. PHPLint also allows the NULL value to be assigned to a variable of type string. |
resource |
fopen("data.txt", "rb")
NULL |
A resource is any internal data structure whose content is known only to the PHP engine. PHPLint also allows the NULL value to be assigned to a variable of this type. |
array
mixed[] string[int] int[string] |
array()
array() array("a","b","c") array("one"=>1,"two"=>2) NULL |
The general syntax of an array type is E[K], where E is the type of the elements and K is the type of the keys (either int or string). PHPLint also allows the NULL value to be assigned to a variable of this type. The bare array is simply a synonym for mixed[] which is an array of values of any type with keys that can be both int and string. |
object
CLASS_NAME |
new Exception("error!")
new MyClass() NULL |
object represents any object of any class. Actually, such a base class does not really exist in PHP; it is an internal abstraction of PHPLint. |
mixed | Any of the types above. Under PHPLint you can't do pretty nothing with a value of such type. | |
void | Special type that can be used under PHPLint only to declare the return type for functions and methods that do not return anything. |
The simplest way to discover how PHPLint recognizes the type of an expression, is to write a simple test program. Create a file named types.php under the PHPLint directory containing this source code:
<?php if( 3.14 );
By executing this program, it simply does not display anything: PHP will evaluate the expression under the if(EXPR) statement as TRUE because any number different from zero evaluates like "true" inside if(), and then it will execute an empty statement, so basically doing nothing. Things go in a completely different way if you validate this source with PHPLint:
BEGIN parsing of types.php 1: <?php 2: if( 3.14 ); if( 3.14 ); \_ HERE ==== 2: ERROR: found expression of type float, expected type is boolean. Remember that 0.0 evaluates to FALSE and any other value evaluates to TRUE. END parsing of types.php Overall test results: 1 errors, 0 warnings.
PHPLint is more strict on how types can be used, and always pretends the expression under the if(EXPR) be of boolean type, while 3.14 it is not. This is one of the cases in which a perfectly acceptable and working PHP program could be rejected by PHPLint. And in fact, most PHP programs, even those that are proved being perfectly working, but that were not written with PHPLint in mind, fail the PHPLint validation.
Vice-versa, a source code that passes PHPLint validation is always a valid PHP program, that is it can be parsed, compiled and executed by PHP.
The error message above tells us PHPLint detected the type of the expression being float, which is expected as 3.14 is a floating-point number. You may try with some other expression too see how PHPLint behaves, for example:
Basic type | Sample expressions | Notes |
---|---|---|
null | NULL | The null type has only one possible value: NULL. |
boolean | 3.14 > 0 12 + 34 != 0 && 2 <= 3 |
Here PHPLint is happy: no error. |
int | 12 + 0x34 - 075 | When both integers and floating point numbers are involved,
integers are promoted to float, so the final result is float. |
float | -0.3141592e1 * 3 6 / 2 |
Note that 6/2 gives exactly int 3 at run-time, but in general division may return either int or float depending on the current values of the operands, so PHPLint in general assumes a division returns "mixed"; to get an int result, you may use a type-cast like in (int)($a/$b) or a (float) type-cast if the intended result has to be float. |
string | "test" . "ting" | |
mixed | $_GET["name"] | The "mixed" type does not really exist in PHP; it is a fictional type you may use under PHPLint for any variable that could contain anything. In this case, $_GET[] could be a string or an array of strings, which are two completely different kind of types under PHPLint. |
The PHPLint program knows nothing about the hundreds and hundreds of functions, classes and other items provided by the PHP libraries, not even functions as common as trim() or fopen() are recognized. Lets try modifying our types.php program above by introducing, for example, the trim() function:
<?php if( trim("something") );
Validation fails with a quite surprising error:
BEGIN parsing of types.php 1: <?php 2: if( trim("something") ); if( trim("something") ); \_ HERE ==== 2: ERROR: unresolved function trim END parsing of types.php Overall test results: 1 errors, 0 warnings.
"Unresolved function trim" means the PHPLint program knows nothing about that function, so it has no idea of what that function may return. Note that PHPLint here does not complain about the type of the expression inside the if() statement simply because it already complained being unable to detect the type of the expression in the first place, which is "unknown" (yes, there is also an "unknown" type inside the PHPLint program, but it is only for internal use of the program just to track down these errors).
To make PHPLint aware the trim() function does actually exist, which types of arguments it takes, how many of them, and which type of value it returns, what you need to do is to add a very specially formatted comment inside the source code:
<?php /*. require_module 'core'; .*/ if( trim("something") );
This special comment contains PHPLint meta-code which has meaning only for PHPLint and is otherwise ignored by PHP at run-time. Note that there is exactly one dot after the opening /* and another dot before the closing */. Note that the name of the module to include is enclosed between single quotes, NOT double quotes. In this case, the require_module PHPLint meta-code statement tells to PHPLint we are willing to use the core PHP module, whose exact content is defined in the modules/core.php file. These modules files ARE NOT ACTUAL PHP programs; they are instead stubs of PHP-like source code only PHPLint may read and understand. The core module contains the definitions of hundreds of constants, functions and classes any PHP program needs.
Validation now gives us the expected result:
BEGIN parsing of types.php 1: <?php 2: /*. require_module 'core'; .*/ 3: if( trim("something") ); if( trim("something") ); \_ HERE ==== 3: ERROR: found expression of type string, expected type is boolean. Remember that the empty string "", the string "0" and the NULL string all evaluate to FALSE and any other string evaluates to TRUE. END parsing of types.php ==== ?:?: notice: required module core.php Overall test results: 1 errors, 0 warnings.
No surprise here: PHPLint now knows about the core module, now knows about the trim() function, and can happily inform us that the result of the expression is a string, and that strings are not boolean values. As an extra bonus, PHPLint inform us about how PHP would evaluate that expression at run-time just in case you had to execute the program despite the outcome of the validation.
By looking at the modules/ directory, you may note several other interesting modules you may want to use next, each module corresponding to some specific PHP extension. Among these, we note: array, date, dom, file, gd, hash, iconv, json, math, mbstring, mysqli, pcre, pdo, pgsql, session, sockets, sqlite, and many others.
Of particular interest is the standard module, which in turn requires array, core, crypt, date, file, locale, math, networking, phpinfo, standard_reflection, spl. This standard module alone covers all needs of most the applications and could be handy if you are in an hurry, although it is not not recommended to do so for the reason explained below:
Create a new test program named functions.php under the PHPLint base directory containing this code:
<?php /*. require_module 'core'; require_module 'spl'; .*/ /** * Skeleton of a function that adds two integer numbers. * @param int $a First term to add. * @param int $b Second term to add. * @return int Resulting sum. * @throws RuntimeException Integer precision overflow. */ function add($a, $b) { $sum = $a + $b; if( ! is_int($sum) ) throw new RuntimeException("overflow"); return $sum; } echo add(1, 2); echo add(PHP_INT_MAX, PHP_INT_MAX);
The add($a,$b) function takes two arguments of type int and retrieves their sum. Note the so-called DocBlock (documentation block) preceeding the actual function. A DocBlock is a multi-line comment starting with the special symbol /**: two asterisks tells to PHPLint this multi-line comment has a special meaning as it is, in fact, a DocBlock. PHPLint carefully parses the content of a DocBlock, and it is particularly interested into parsing the lines beginning with the AT character @; those lines are followed by some special keyword, possibly some argument and then possibly a comment. Lets look at these lines more in details:
@param int $a
tells there is a parameter (aka "argument") named $a of type int. PHPLint now knowns the type of $a and checks it is used properly. Moreover, now PHPLint knows this function takes int as a first argument of the function and will reject anything else is passed to it.@param int $b
tells the same about the $b argument.@return int
tells this function returns a value of type int. Functions that do not return any value should be declared returning "void". If the @return declarations is omitted, usually PHPLint can detect the returned type from the first return statement found inside the function.@throws RuntimeException
tells this function may throw the RuntimeException exception imported from the spl module. In our case this could happen if the arguments are very big numbers whose sum exceeds the int capacity. Note that int is 4 bytes long on 32-bits machines, and 8 bytes long on 64-bits machines.
Note the $sum local variable inside the function; its type is detected to be int by PHPLint because it is the sum of two int numbers, and (normally) a sum of two int numbers gives back an int value. But at run-time, things could go in the wrong way, the sum could exceed the int capacity, and PHP might promote the result to a float value, but with reduced precision, that is the result is rounded to the closest value a float can hold. PHPLint cannot prevent all that from happening at run-time, so we added a test to check at run-time the actual type of the result.
Validate and execute this program to see what happens:
3PHP Fatal error: Uncaught RuntimeException: overflow in C:\wamp\phplint\functions.php:19 Stack trace: #0 C:\wamp\phplint\functions.php(25): add(2147483647, 2147483647) #1 {main} thrown in C:\wamp\phplint\functions.php on line 19
Q: Could you guess the word size of the machine where I run this test?
R: 32.
Use require_once to include other packages.
The alternatives require
, include
and
include_once
are not reliable ways to include code.
include
is commonly used to include stubs of HTML code;
often is used to build the header and the footer of a WEB page, for
example, but PHPLint does not parse recursively these files. The path of the
required package must be absolute: this is the only safe way to ensure
the referred file will be found either by PHPLint and by PHP, independently
from any other external parameter or configuration. The magic constant
__DIR__
comes into help here, because allows to write
a path relative to the current source file:
require_once __DIR__ . "/../../AnotherPackage.php";
You may also use constants, provided that the resulting expression be statically evaluable:
const LIBS = __DIR__ . "/../../mylibs", PKGS = __DIR__ . "/packages"; require_once LIBS . "/MyClass1.php"; require_once LIBS . "/MyClass2.php"; require_once LIBS . "/MyClass3.php"; require_once PKGS . "/MyPackage1.php";
In PHP a variable can hold any type of value. So, for example, you can assign to a variable $foo a number, then later you can assign to the same variable a string or also any other type of data. A given function may return several types of values depending on the arguments that are passed. Programs that use this "polymorphic" behavior are difficult to read and difficult to understand, and often they require the interpreter to do some expensive conversion at run-time with performance penalties.
The PHPLint point of view is completely different. Each variable,
each function argument, and each value returned by a function must have
a well defined type. PHPLint does not require the type of every thing be
explicitly defined, since in most of the cases this type can be guessed from
the code itself. For example, by assigning 123 to a variable clearly makes that
variable of the type int
, and returning a string from a function
clearly makes this function of the type string
. Nevertheless,
there are cases where a type must be explicitly indicated, as we will see
in the examples below.
Some PHP programmers rely on the fact that an un-initialized variable behave like the zero number or the empty string, depending on the context where this variable appears. That's not a good programming style. Every variable must have a value (and then, a type) before being used. PHPLint complains with an error if a variable used in an expression is unknown. Sometimes PHPLint also complains that a variable might not be initialized depending on the order of execution of the statements (this is called flow execution analysis).
If a variable has been determined to belong to some specific type, it must be used according to its type. You cannot assign a string to a variable already known to be of type int
:
$i = 123; $i = "hello"; # <== ERROR
The superglobal arrays $_GET $_POST and $_REQUEST have structure
mixed[string]
that is arrays of arbitrary values with
keys of type string
. Typically these elements are strings, but in some
cases they can be also arrays of strings, or array of arrays of strings,
and so on. Your program must ensure the values gathered from these array
be of the expected type. To ensure that, a value type-cast is needed
at run-time:
# Example: acquiring the login mask: $name = (string) $_POST["name"]; $pass = (string) $_POST["pass"]; # Example: acquiring optional URL parameter: $chapter = 0; # show table of contents by default if( isset($_GET["chapter_number"]) ) $chapter = (int) $_GET["chapter_number"];
By the way, these value type-cast applied to a mixed
value
make PHPLint aware of the actual type expected by the program and make the
program safer because data coming from the remote client could be arbitrary
values.
PHP already has "types", because at runtime every value and every variable
belongs to a well specific type. To the proper PHP types, PHPLint adds the
mixed
type, which does not really exist in PHP but is used
extensively in its official documentation to indicate "any type of value".
Types under PHPLint. Arrows indicate assignment compatibility.
Under PHPLint, mixed
is a box
inside which any type of data can be boxed. Variables and parameters
of this type can carry any type of data. mixed
values
can be assigned to other mixed
variables,
or can be compared with the strict comparison operators ===
and !==
but nothing really useful can be made until the value
they contain is extracted to some specific type, that is, gets unboxed.
Unboxing, under PHPLint, requires to apply either one of the PHP value-conversion operators, for example
$name = (int) $_GET["name"];
or apply the magic PHPLint type-cast function cast(T,V)
:
$name = cast("string", $_GET["name"]);
Thre are at least two important differences between PHP value-conversion
operators and the PHPLint cast()
magic function.
One difference is that the PHP's value-conversion operators
(TYPE)
actually
convert mixed
to a value of the specified type, while the
cast(T,V)
type-cast magic function merely checks at runtime
the actual type and throws CastException
if it does not match
those expected, then returns the unboxed value, unmodified.
The other
difference is that the cast(T,V)
function can check any type
of data PHPLint can understand, including arrays (with specified keys and
elements) and objects. The cast()
function is implemented in the
cast.php
package available under the stdlib
directory.
The cast()
magic function serves to two purposes, one at
validation time, and one at runtime.
Take for example this statement:
$names = cast("string[string]", $_GET);
It has two effects:
$names
is an array of strings with keys of type string. This resolves
the problem from the point of view of the static validation.
cast()
function will check
the value $_GET
element by element and key
by key, and will throw CastException
if it does not
match the expected type, so rejecting the invalid value.
This resolves the safety and security problems
that might arise if unexpected types of values enter the program.
As we already said, arrays under PHPLint may have a well defined type for the
key and for the elements. To specify the structure of the array, PHPLint
introduces the special notation E[K]
where E
is
the type of the element and K
is the type of the key (that is,
int
or string
). So, for example, a list of names
with integer index is represented by
string[int]
while an associative array name-to-object might be
Person[string]
The empty key is also allowed, which means unspecified key integer and/or
string; if, moreover, the type of the elements if mixed
,
we end with the more generic type of array:
mixed[]
Several indeces can be specified. A rotational matrix:
float[int][int]
These types can be used in several contexts, lets see some examples. Explicitly declaring the type of a variable:
/*. resource[int] .*/ $files = NULL;
/*. string[int] .*/ $names = array();
Formal-typecast to specify the structure of an empty array or the NULL value, with the same effect we had above:
$files = /*. (resource[int]) .*/ NULL;
$names = /*. (string[int]) .*/ array();
Defining the signature of a function (here using a DocBlock):
/** * Returns the array of strings in alphabetical order. * @param string[int] $a Unordered array of strings. * @return string[int] Same strings, but in alphabetical order. */ function mySort($a){ if( $a === NULL ) return NULL; for($i = count($a)-1; $i >= 1; $i--){ for($j = $i-1; $j >= 0; $j--){ if( strcmp($a[$j], $a[$i]) > 0 ){ $t = $a[$i]; $a[$i] = $a[$j]; $a[$j] = $t; } } } return $a; }
From the examples above, emerges how some types allows NULL as a value,
and nothing prevent, at runtime, to access an entry of the array that does not
exists. That's why it is strongly recommended to always require the errors.php
package available under the stdlib
directory:
require_once __DIR__ . "/errors.php";
That package sets the maximum level of error reporting and then turns any error, even the smallest E_NOTICE, into an exception that terminates the program. Regular functions may throw ErrorException if their signature triggers any error; language constructs, for example array index selectors, throw InternalException instead. This dramatically improves the safety and the security of a program how can be clearly seen from the following examples:
<?php require_once __DIR__ . "/errors.php"; /*. string[int] .*/ $a = array("a"); echo $a[3]; Output: Uncaught InternalException: E_NOTICE: Undefined offset: 3 in C:\wamp\phplint\tes t.php:4 Stack trace: #0 C:\wamp\phplint\test.php(4): phplint_error_handler(8, 'Undefined offse...', ' C:\\wamp\\phplint...', 4, Array) #1 {main}
FIXME: And what happens accessing a NULL array? See these PHP bugs: https://bugs.php.net/bug.php?id=65484, https://bugs.php.net/bug.php?id=62769.
NULL should always have a formal type-cast.
The null type is for internal use only of PHPLint. It has only one
value: NULL
. This value can be assigned to variables of type
string, array, resource and object.
PHPLint tries to be smart while parsing the real meaning of a NULL value. For example, by assigning NULL to a variable already known to be a string it guesses that NULL has to be string as well; by returning NULL from a function which is already known to return an object of some class, PHPLint assumes that NULL has to be an object as well. But what if the assignment is made to a brand new variable of still unknown type? And what if the returned type from the current function has not been determined yet? In these case PHPLint allows to apply a formal type-cast to the NULL in the same way we already seen for the formal type-cast applied to the empty array:
return /*. (string) .*/ NULL;
Note that, apart the /*. .*/
symbols, this formal type-cast
is similar to a PHP value type-cast, where the type name is enclosed between
round parenthesis.
Some functions of the standard library, for example those that normally return
a resource on success, may return FALSE to indicate an error: these special
returned values must be checked with the ===
or the
!==
operators. For example, the fopen() function returns
FALSE if the file cannot be opened for the required operation, so you must
check for a possible error:
WRONG CODE | Right code |
---|---|
The ! operator cannot be appliedto a value of the type resource: if( ! $f = fopen("myFile.txt", "r") ){ die("error opening the file"); } |
if( ($f = @fopen("myFile.txt", "r")) === FALSE ){ die("error opening the file"); }or even better: $f = @fopen("myFile.txt", "r"); if( $f === FALSE ){ die("error opening the file"); } |
You have 3 options here:
$f = @fopen(...); if( $f === FALSE ) die("failed opening");
or even using the PHPLint meta-code with the same effect:/** * Reads the data. * @return boolean True on success, false on failure. * @triggers E_WARNING Failed accessing the file. */ function read_data() { $f = fopen(...); if( $f === FALSE ) return FALSE; $s = fread(...); if( $s === FALSE ) return FALSE ... if( ! fclose() ) return FALSE; return TRUE; }
Correct detection and handling of each error is then left to your code.function read_data() /*. triggers E_WARNING .*/ { $f = fopen(...); if( $f === FALSE ) return FALSE; $s = fread(...); if( $s === FALSE ) return FALSE ... if( ! fclose() ) return FALSE return TRUE; }
As an alternative, you may want to handle exceptions locally by catching them and then returning some special value if something went wrong:require_once __DIR__ . "/stdlib/errors.php"; /** * Reads the data. * @throws ErrorException Failed accessing the file. */ function read_data() { $f = fopen(...); $s = fread(...); fclose(); }
Now its PHP itself that checks for any error condition at runtime: if an error happens, the function stops, an exception of type ErrorException is thrown and then returned to the caller; the caller, in turn, may or may not handle the exception our read_data() function may throw. Note that PHPLint tracks down the propagation of each exception through the code, statement by statement and function by function, so any exception must be either handled locally or must be propagated to caller.require_once __DIR__ . "/stdlib/errors.php"; /** * Reads the data. * @return boolean True on success, false on failure. */ function read_data() { try { $f = fopen(...); $s = fread(...); fclose(); } catch(ErrorException $e){ error_log("failed accessing the file: $e"); return FALSE; } return TRUE; }
All the properties of a class MUST be declared. Moreover, assign to them a type and/or an initial value. As you might guess at this point, providing an initial value lets PHPLint to determine its type, otherwise PHPLint relies on annotations or PHP 7.4 type-hint to detect its type. Example:
class MyClass { public $num = 123; public $arr = array(1, 2, 3); /** * @var string[int] */ private $arr2; private ? MyClass $related_object; // PHP 7.4 only! function __construct($first = "") { $this->arr2 = array($first); } }
Note that the array $arr2 lacks its initial value, so an explicit declaration of type is required. Remember that in this case the PHP interpreter assigns NULL as initial value.
Properties cannot be added dynamically at run-time to an object. If you need to store a variable number of data inside an object, use a property of the type array.
PHPLint promotes a clean programming style where everything must be written exactly as defined, including: namespaces, keywords, named constants, function names, class names, method names.
Entity | PHP | PHPLint |
---|---|---|
Keywords of the language, as
const function class if() while() etc. |
Case-insensitive. | Case-sensitive. |
Namespaces. |
Case-sensitive under Linux,
case-insensitive under Windows. |
Case-sensitive. |
Constant names, like in
const PI=3.14; define("PI", 3.14); |
Case-sensitive. | Case-sensitive. |
Variables, like $n = 123; | Case-sensitive. | Case-sensitive. |
Function names. | Case-insensitive. | Case-sensitive. |
Class names | Case-insensitive. | Case-sensitive. |
Methods names | Case-insensitive. | Case-sensitive. |
PHPLint complains with an "error" if any of such things is written down arbitrarily mixing uppercase and lowercase letters that do not match the definition.
Good | BAD - mixing case |
---|---|
use it\icosaedro\bignumbers\BigFloat; if ( ! isset($_POST['price']) ) die("missing 'price' field"); $s = trim( (string) $_POST['price'] ); if ( ! BigFloat::isValid($s) ) die("invalid syntax in price: $s"); $price = new BigFloat($s); |
Use it\icosAedro\BIGnumbers\bigfloat; If ( ! IsSet($_POST['price']) ) Die("missing 'price' field"); $s = Trim( (String) $_POST['price'] ); If ( ! bigFloat::isvalid($s) ) DIE("invalid syntax in price: $s"); $price = new BIGfloat($s); |
Before the code be actually executed, the PHP interpreter scans all its source looking for functions, classes and methods, so that they can appear in any order. (This is not the case for constants and variables, that must be defined and assigned in the order also in PHP).
By the contrary, PHPLint is a single-pass parser, so it needs to read the definitions before the usage. PHPLint raises an error if a function or a class gets used before being defined. Take care to sort your program in a bottom-up order: low-level functions, classes and methods first, high-level ones next.
Typical order of the declarations |
---|
<?php # This package namespace: namespace com\mycompany\dbtools; # Required PHP extensions: /*. require_module 'standard' require_module 'session'; require_module 'mysqli'; .*/ # Required packages: require_once __DIR_ . "/autoload.php"; require_once __DIR_ . "/Common.php"; # "Use" declarations: use com\mycompany\dbtools\DbAbstractionLayer; use com\mycompany\dbtools\InputValidator; use it\icosaedro\bignumbers\BigFloat; # Constants: const QUERY_MAX_EXECUTION_TIME_MS = 2000; # Functions and classes: ... # Main body of the program: ... |
The reference manual describes a feature of PHPLint meta-code named "prototypes declarations" or even "forward declarations", see chapter Recursive declarations. Prototypes can be also used to relax the strict bottom-up order of the declarations. The use of this solution should be avoided, and prototypes should be restricted to those cases in which the intrinsic recursive nature of the declarations requires them (example: function A calls B, and function B calls A).
PHPLint expects the expression giving the value of a constant be statically determinable. In any other case a variable is more appropriate. Moreover, some programmers take advantage from the fact that constants "lives" in the global namespace, so you can get their value simply writing their name:
# WRONG CODE: define("MY_IMGS", $_SERVER['DOCUMENT_ROOT'] . "/imgs"); if ( PHP_OS == 'WINNT' ) define("ROOT", "C:\\"); else define("ROOT", "/"); function f() { echo "MY_IMGS=", MY_IMGS, " ROOT=", ROOT; }
You should try to submit the code above to PHPLint: it will complain that the constant MY_IMGS cannot be statically evaluated, and ROOT is re-defined. Since these values are determined at run-time, you should use two variables instead:
# Right code: $MY_IMGS = $_SERVER['DOCUMENT_ROOT'] . "/imgs"; if ( PHP_OS === 'WINNT' ) $ROOT = "C:\\"; else $ROOT = "/"; function f() { echo "my_imgs=", $GLOBALS['MY_IMGS'], " root=", $GLOBALS['ROOT']; }
Never use the weak comparison operators
< <= == != >= >
with strings, because they are unreliable.
Apply this simple conversion rule:
$a OP $b ==> strcmp($a, $b) OP 0
where OP is the comparison operator. Use === and !== for bite-wise strict equality/inequality comparison.
Don't write functions that "return the result on success or FALSE on failure" because mixing types that are different prevent PHPLint from doing its job and make the code harder to read and to debug. Here there is a list of possible alternatives:
Return a special value outside the range of the expected values. For
example, often the numbers involved in a program are positive values
accounting for some amount of things, so a negative value can be used
to indicate an error. Moreover, in PHPLint the types string array
object resource
and mixed
allow NULL
as a special value. This table summarizes the typical values used to this
aim that are also compliant with the type model of PHPLint:
Return type |
Value returned on error |
---|---|
int |
-1, 0 or a negative value |
float |
NAN, INF or a negative value (see also is_finite(), is_nan()) |
string |
NULL or empty string "" |
resource |
NULL |
array |
NULL |
object |
NULL |
/** * Traditional "polymorphic" version * @param string $fn * @return string|FALSE */ function read_data($fn) { $f = fopen($fn, "r"); if( $f === FALSE ) return FALSE; /* here: read $data */ return $data; } if(($data = read_data("data.dat")) === FALSE) $data = get_default_data(); |
/** * PHPLint compliant version * @param string $fn * @param string &$data * @return bool */ function read_data($fn, /*. return .*/ &$data) { $f = @fopen($fn, "r"); if( $f === FALSE ) return FALSE; /* here: read $data */ return TRUE; } if( ! read_data("data.dat", $data) ) $data = get_default_data(); |
read_data()
above might
return the default data if the file cannot be read:
/** * Error resolved internally * @param string $fn * @return string */ function read_data($fn) { $f = @fopen($fn, "r"); if( $f === FALSE ) return get_default_data(); /* here: read $data */ return $data; } $data = read_data("data.dat");
function last_char_of(/*. string .*/ $s) { $l = strlen($s); if( $l == 0 ) return $s; return substr($s, $l-1); }
For example, this table mixes strings, numbers and boolean values:
# WRONG: $people = array( # Name Age Married "Gery", 34, FALSE, "Sara", 23, TRUE, "John", 56, TRUE); echo "Married persons younger than 30: "; for($i = 0; $i < count($people); $i += 3) if( $people[$i+2] and $people[$i+1] < 30 ) echo $people[$i], " ";
PHPLint cannot parse effectively such a code, and neither humans can
understand it very well. The solution to the problem requires to introduce a
class Person
where all the data about a person are stored. The
resulting code might look similar to this one, that can be validated
by PHPLint:
# Right: class Person { public /*. string .*/ $name; public $age = 0; public $married = FALSE; function __construct(/*. string .*/ $name, /*. int .*/ $age, /*. bool .*/ $married) { $this->name = $name; $this->age = $age; $this->married = $married; } } $people = array( new Person("Gery", 34, FALSE), new Person("Sara", 23, TRUE), new Person("John", 56, FALSE) ); echo "Married persons younger than 30: "; foreach($people as $p) if( $p->married and $p->age < 30 ) echo $p->name, " ";
Ok, I agree: this second version of the same program is longer, but the first one remembers to me the old times of the BASIC when the arrays were the only data structure available. Moreover, trying the first example while writing this document, I made a mistake with the offset of the index and the program did not work properly; the second version, instead, worked perfectly just at the first run.
Sometimes programs need to check at run-time their configuration file
php.ini
for some parameter. All the parameters declared
here are available through the function ini_get($param)
where
$param
is the name of the parameter. The value returned by this
function is always a string or the NULL value. For those parameters that
are simple flags, the value returned is the empty string ""
or
"0"
for FALSE/No/Off, and "1"
for TRUE/Yes/On. The
other parameters return a string, although they can be actually numbers. The
right way to handle this in PHPLint is shown in the following examples,
that may be useful in actual applications:
if( ini_get("magic_quotes_gpc") === "1" or ini_get("magic_quotes_runtime") === "1") exit("ERROR: please disable magic quotes in php.ini"); if( ini_get("file_uploads") !== "1" ) exit("ERROR: please enable file upload in php.ini"); /** * Converts size in bytes, possibly with scale factor. * Converts a numeric value from the php.ini, possibly * containing some scale factors as K, M and G. * Example taken from the PHP manual. * @param string $s Encode size in bytes, possibly with scale factor. * @return int Number of bytes. */ function return_bytes($s) { $v = (int) $s; $last = strtolower($s[strlen($s)-1]); switch($last) { // The 'G' modifier is available since PHP 5.1.0 case 'g': $v *= 1024; /*. missing_break; .*/ case 'm': $v *= 1024; /*. missing_break; .*/ case 'k': $v *= 1024; /*. missing_break; .*/ /*. missing_default: .*/ } return $v; } $upload_max_filesize = return_bytes( trim( ini_get("upload_max_filesize" ) ) ); $post_max_size = return_bytes( trim( ini_get("post_max_size" ) ) ); $max_upload = min($upload_max_filesize, $post_max_size); echo "Max uploadable file size is $max_upload bytes.";
PHP allows the special syntax list($x,$y)=EXPR;
where EXPR
is an expression generating an array,
typically the value returned from a function or the special
language construct each()
. Never use these syntaxes,
because PHPLint cannot determine the types of the values $x and $y.
Rather, assign to an array, then use the resulting elements.
WRONG CODE | Right code |
---|---|
$a = array(1, 2, 3); reset($a); while( list($k, $v) = each($a) ){ echo $k, $v; } |
$a = array(1, 2, 3); foreach( $a as $k => $v ){ echo $k, $v; } |
For example, this function may be useful to measure with precision the time elapsed:
function elapsed($a) { $b = microtime(); list($b_dec, $b_sec) = explode(" ", $b); list($a_dec, $a_sec) = explode(" ", $a); return ((float)$b_sec - (float)$a_sec) + ((float)$b_dec - (float)$a_dec); } $start = (string) microtime(); /**** here do something time-consuming ****/ $e = elapsed($start); if( $e > 1.0 ) echo "WARNING: elapsed time $e s";
Note the presence of two list()
constructs. That code
can be easily converted to the following PHPLint-compliant code, where
the result of the explode()
function is assigned to
two arrays; some meta-code was also added:
/*.float.*/ function elapsed(/*.string.*/ $start) { $a = explode(" ", $start); $b = explode(" ", (string) microtime()); return ((float)$b[1] - (float)$a[1]) + ((float)$b[0] - (float)$a[0]); }
This syntax is invalid:
$f = fopen(...) or die(...);
because die()
does not return a boolean value (actually, it
does not return anything at all). Use the longer form we shown above. The same
holds for exit()
, actually a synonym of die()
.
For example
$obj = new $class();
because $class
might be any string, without any relation
with the known classes; this source is difficult to understand for the
human reader of the source, and impossible to check at all for PHPLint.
Consider to use an abstract class instead. PHP 5 also introduced the
interfaces, intended just to address elegantly these problems. Adding these
"glue-classes" makes the code more readable and PHPLint helps to
keep the complexity under control.
Returning to the example above, if $obj has to be an instance of some class
dynamically determined at run-time, certainly these classes are in some way
related, i.e. them exhibit the same interface. This interface (i.e. a common
set of constants, properties and methods) will be used in the following code.
Two classes that share the same interface must have a common ancestor,
that may be an abstract class
or an interface
.
The example below illustrates this scheme:
interface Ancestor { function doThis(); function doThat(); } class ConcreteClass1 implements Ancestor { public function doThis() { /* ...implementation... */ } public function doThat() { /* ...implementation... */ } } class ConcreteClass2 implements Ancestor { public function doThis() { /* ...implementation... */ } public function doThat() { /* ...implementation... */ } } # Declare the variable $obj to be a generic Ancestor. # This says to PHPLint that $obj is an object that # implements "Ancestor": $obj = /*. (Ancestor) .*/ NULL; if( we_need_ConcreteClass1 ) $obj = new ConcreteClass1(); else /* we need ConcreteClass2 instead */ $obj = new ConcreteClass2(); # Now we can use $obj according to the interface as specified # for Ancestor, whichever its actual implementation may be: $obj->doThis(); $obj->doThat(); # The same strategy can be used also inside the functions: function doThisAndThat(/*. Ancestor .*/ $obj) { $obj->doThis(); $obj->doThat(); } doThisAndThat($obj);
The advantage of using abstract classes and interfaces is that the PHP interpreter, the PHPLint validator and humans reading the source can understand the meaning of the source and detect possible violations of the "contract" rules in the extended and implemented classes.
DocBlocks apart, PHPLint also understands another kind of annotations that is shorter
and that may be preferable in some cases, for example for little private
functions that do not really need to be documented in detail.
These special annotations are name "PHPLint meta-code", and are again
multi-line comments /*. .*/
surrounding the PHPLint meta-code.
We have already seen these special comment at the beginning of this document,
used for the require_module
meta-code statement:
<?php /*. require_module 'standard'; .*/ /*. string .*/ function get_param(/*. string .*/ $name, $max_len=20) { if( ! isset( $_REQUEST[$name] ) ) return NULL; $s = (string) $_REQUEST[$name]; if( strlen($s) > $max_len ) $s = substr($s, $max_len); return $s; } ?>
Note that this time
the argument $max_len
does not require a type, since its
initial value already provides to PHPLint the correct answer: int.
Although in DocBlocks usually all the parameters are listed in order to
describe their meaning in detail, from the point of view of PHPLint
the parameters that have a default value can be omitted because the type
is exactly the type of the default value assigned.