rfc:dbc

This is an old revision of the document!


PHP RFC: Native Design by Contract support

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>

References

Rejected Features

rfc/dbc.1423142106.txt.gz · Last modified: 2017/09/22 13:28 (external edit)