PHPLint Tutorial

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.

Contents

Hello, world!

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.

You may put your programs anywhere in your hard disk In the examples of this tutorial, sample programs are always assumed to be saved under the PHPLint base directory to make easier to type-in the path of the commands and to make easier to include files from the stdlib/ directory. Advanced users may want to add the PHPLint directory to their PATH environment variable so that the php and the phpl scripts be readily available from any working directory without the need to type boring long paths.

Data types

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.

Practicing with expressions and data types

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 typeSample expressionsNotes
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.

Requiring PHP modules

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:

Be spare in which modules you require Parsing modules adds a bit of overhead to the PHPLint program, has it has to open, read and parse all the content of each required module, and some are quite big. Moreover, PHPLint tracks down which required modules are then actually used, and reports with a notice those that are not. Finally, PHPLint reports in the generated documentation which modules (aka "extensions") your program relies on, helping users to deploy your program and configure their installation properly according to the requirements. For these reasons, it is recommended to include all and only those modules your program actually needs.

Defining your own functions, or DocBlocks basics

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.

Integer arithmetic overflow cannot be prevented by PHPLint Although in some very special simple case like that above a validator program could (at least in theory) detect a possible overflow problem, in general this detection cannot be done "statically" because the exact value of an expression is known only at run-time. In most programming languages (C, C++, Java) integer overflow simply gives wrong values; in other systems (C#) programmer may, if they want, detect these events at run-time; in PHP the result becomes an approximated floating point number. In any case and on any system, it is the programmer responsibility to properly handle these events. So we must live with this issue by carefully coding our programs in such a way that overflows never happen, or check at run-time the result of each expression otherwise.

Requiring and including other packages

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";
The expression giving the require_once path MUST be statically evaluable Do not use variables or functions inside the expression, because the resulting expression cannot be evaluated by PHPLint "statically". For example, DO NOT use expressions like these:
require_once $my_package; /* WRONG */
require_once $_GET['pkg']; /* WRONG */
require_once dirname(__FILE__) . "/MyPackage.php"; /* WRONG */
By the way, note that dirname(__FILE__) is just __DIR__, so there in no need to use a function.
Always import the modules and the packages your source depends on PHPLint will complains with an error if the source code refers to any constant, variable, function or class he does not know, so you must really add a /*. require_module 'MODULE'; .*/ PHPLint meta-code statement or a require_once PACKAGE; PHP statement for EACH module and EACH package your source code depends on. This also implies two important things:
 1. Each source file can be validated by PHPLint separately by its own.
 2. You can require blindly each package and it will resolve automatically its dependencies.
An useful alternative is the class autoloading mechanism described in the reference manual; class autoloading saves you from the boring task to list all the required packages, and it is more efficient at runtime.

The Type will be with you, always

Variables have a statically assigned type

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.

Variables must be initialized with a value before being used

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).

The type of a variable cannot change

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

$_GET $_POST and $_REQUEST are arrays of mixed values

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.

Type model of PHPLint in detail

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.


The mixed type

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:

  1. At validation time, tells to PHPLint that $names is an array of strings with keys of type string. This resolves the problem from the point of view of the static validation.
  2. At runtime, the 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.

The array type

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 mostly always requires a type-cast

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.

Handling errors

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 applied
to 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");
}

Errors must be always explicitly handled. In the examples above the function fopen() as any other I/O function could raise errors or warnings or even notices for several reasons. PHPLint knows that and reports a "Warning" for each un-handled error that could be raised at run-time.

You have 3 options here:

Classes

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.

Other things you should know

PHPLint is case-sensitive

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.


GoodBAD - 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);

Declarations first, usage next

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).

Constants must be... constant!

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'];
}

String comparisons should be made using strcmp()

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.

Functions must always return only one type of value

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:

Do not mix elements of different types in arrays

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.

Proper usage of ini_get()

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.";

Do not use each() and list() to assign a list of variables

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]);
}

die() is a statement, not a function!

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().

Do not use "variable name" classes

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.

Advanced topics

Function signature with PHPLint meta-code

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.