This is an old revision of the document!
PHP RFC: Native Design by Contract support
- Version: 0.1
- Date: 2015-02-04
- Author: Yasuo Ohgaki yohgaki@ohgaki.net, François Laupretre francois@php.net
- Status: Draft
- First Published at: http://wiki.php.net/rfc/dbc
Introduction
For more than 10 years (roughly since PHP 5 was released), the PHP core community has seen a lot of discussions about strict vs loose typing, type hinting and related features.
To summarize years of flame wars, developers argue that strict typing and type hinting will make their source code cleaner and easier to debug. On the other side, these features must remain optional and compatible with 'basic' loose-typed PHP syntax. And the debate generally dies in endless discussions about the concept of 'number', 'int', 'float'... :).
With this RFC, we propose an alternative approach, already present in several languages, named 'design by contract' (reduced to 'DbC' in the rest of the document).
We won't detail the concept of DbC here, as we provide links in the reference section below. Just note that DbC is a way to define constraints on function arguments, return values, and class properties. The key point is that DbC checks are performed during the development/validation phase only. In production phase, DbC checks are turned off.
So, the most important points are :
- DbC constraints can be extremely detailed as performance is not a problem.
- DbC checks cannot handle checks that must always run, even in production. Validating user input, for instance, must remain out of DbC constraints.
- The DbC and 'Test Driven Development' concepts are closely related, as DbC heavily relies on the quality of test coverage.
Example
First, an example of a function defining input, inline and output constraints :
/** * Compute area of a triangle * * This function computes the area of a triangle using Heron's formula. * * @param float([0:) $a Length of 1st side * @param float([0:) $b Length of 2nd side * @param float([0:) $c Length of 3rd side * @assert.in ($a <= ($b+$c)) * @assert.in ($b <= ($a+$c)) * @assert.in ($c <= ($a+$b)) * * @return float The triangle area * @assert.out ($> >= 0) */ function triangleArea($a, $b, $c) { $halfPerimeter = ($a + $b + $c) / 2; // @assert ($halfPerimeter >= 0) return sqrt($halfPerimeter * ($halfPerimeter - $a) * ($halfPerimeter - $b) * ($halfPerimeter - $c)); }
Notes:
- '$>' means 'return value'.
- 'float([0:)' means 'a floating point number in the range from 0 (included) to infinite', which means 'a positive or null floating point number'.
- I added an '@assert.out' explicit constraint for the example but it would have been easier to change the return type to 'float([0:)'.
You may also note that this code is fully backwards compatible.
Another example with a clone of str_replace() :
/** * Replace all occurrences of the search string with the replacement string * * ... * * @param array(string)|string $search The value being searched for (aka needle) * @param array(string)|string $replace The replacement value that replaces found search values * @param array(string)|string $subject The string or array being searched and replaced on * @param.out int([0:) $count The number of replacements performed * @return array(string)|string * @assert.out (is_array($subject)===is_array($>)) */ function str_replace($search, $replace, $subject, &$count=null) { ...
Note that, while it would be supported, we didn't provide any constraint on $count input, as this parameter is used for output only.
Proposal
DbC typically defines three basic constraint types :
* pre-conditions: checked when entering a function/method. Generally check that passed arguments are valid.
* post-conditions: checked when a function/method exits. Used to check the return type/value and the returned type/value of arguments passed by reference.
* class inveriants: Constraints on class properties. In PHP, two subtypes exist : constraints on static properties and constraint on dynamic (instance) properties.
Syntax
We propose to include the DbC directives in PHP comments. The function/method/class-related constraints will be included in phpdoc blocks (extending the phpdoc syntax), while inline assertions will be included in plain comments.
The benefits are :
- As directives are exclusively contained in comments, the source code remains fully compatible with every past and future PHP interpreter (NO BC break).
- phpdoc blocks already contain arguments and return types. As DbC uses this information, unchanged code will automatically benefit of DbC.
- phpDocumentor will easily take advantage of the extensions DbC is bringing to the phpdoc syntax and will easily generate a more detailed documentation. There is no BC break here as, even using the current version of phpDocumentor, DbC-specific keywords are ignored and the documentation is correctly generated.
- PHP IDEs already use phpdoc blocks. So, it will be easier for them to exploit DbC constraints.
Pre-conditions
Argument types
Syntax
line = "*", "@param", compound-type, name [, free-text] compound-type = type, { "|", type } type = array-type | numeric-type | string-type | object-type | resource-type | scalar-type | simple-type simple-type = "null" | "scalar" | "mixed" | "bool" array-type = "array" [ "(" [ key-type "=>" ] compound-type ")" ] <TODO>
DbC types vs zval types
Optional arguments
When an optional argument is not set by the caller, its input/output types are not checked. This allows to set a default value which does not match the argument's declared input type.
Example :
/** * ... * @param int $flag ... * ... */ function myFunc(..., $flag=null) { if (is_null($flag)) { // Here, we are sure that the parameter was not set by the caller, as // a null value sent by the caller would be refused by DbC input check. ...
Input assertions
line = "*", "@assert.in", php-condition
Inheritance
Post-conditions
Returned type
line = “*”, “@return”, compound-type [, free-text]
Argument return type
This is the return type & value of the arguments passed by reference.
@param.out <compound-type> <name> <free-text>
Output assertions
@assert.out
Inheritance
Class-wide constraints
Static constraints
Syntax
@assert.static
Execution
Scope
Inheritance
Instance constraints
@assert.instance
Execution
Scope
Inheritance
Inline assertions
Syntax
// @assert <condition>
Scope
Exception thrown
ContractException
Backward Incompatible Changes
None
Proposed PHP Version(s)
PHP 7
RFC Impact
To SAPIs
None
To Existing Extensions
Compatibility with Xdebug ? <TODO>
To Opcache
<TODO>
New Constants
None
php.ini Defaults
dbc.enforce : boolean
- php.ini-development value: true
- php.ini-production value: false
Open Issues
Unaffected PHP Functionality
When DbC is off, there's no change in PHP behavior.
Future Scope
- Exceptions: Using the '@throws' keyword, we can raise an error if the function throws an exception of an undeclared class.
- Enforce phpdoc's @var property types
Proposed Voting Choices
Required majority ?
Patches and Tests
Dmitry Stogov volunteered for implementation.
Implementation
<TODO>