rfc:dbc
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
rfc:dbc [2015/02/08 14:05] – francois | rfc:dbc [2018/03/01 23:19] (current) – Typo "Under Discussion" carusogabriel | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Implementing Design by Contract ====== | ====== PHP RFC: Implementing Design by Contract ====== | ||
- | * Version: 0.3 | + | * Version: 0.4 |
- | * Date: 2015-02-08 | + | * Date: 2015-02-09 |
* Author: François Laupretre < | * Author: François Laupretre < | ||
- | * Status: | + | * Status: |
* First Published at: http:// | * First Published at: http:// | ||
+ | |||
+ | This RFC is waiting for the decisions that will be made about scalar | ||
+ | type hinting. The reason is that the design and syntax | ||
+ | decisions that will be made about scalar type hinting heavily impact the | ||
+ | contents of this RFC. Proposal is subject to be changed according scalar type | ||
+ | hinting implementation. | ||
===== Preamble ===== | ===== Preamble ===== | ||
+ | |||
+ | This RFC is part of " | ||
+ | |||
+ | * https:// | ||
+ | |||
+ | There is alternative implementation proposal by " | ||
+ | |||
+ | * https:// | ||
+ | |||
The original idea of introducing DbC in PHP comes from Yasuo Ohgaki | The original idea of introducing DbC in PHP comes from Yasuo Ohgaki | ||
Line 14: | Line 29: | ||
doc comments. This is the present document. | doc comments. This is the present document. | ||
- | Unfortunately, | + | While we agree on the concept, Yasuo is preferring |
- | prefer a a D-like syntax | + | IMO, adopting |
- | I disagree with this choice for various reasons, the main one being that | + | best way to include |
- | it would make DbC-enabled PHP code | + | |
- | incompatible with PHP 5 interpreters. This would, IMO, make it a no-go for a lot of developers | + | |
- | before several years. | + | |
- | + | ||
- | So, I am publishing this RFC under my single name, with due credits for the | + | |
- | original idea. I hope the discussion will bring a consensus. | + | |
===== Introduction ===== | ===== Introduction ===== | ||
Line 28: | Line 37: | ||
For more than 10 years (since PHP 5 was released), the PHP core community has | For more than 10 years (since PHP 5 was released), the PHP core community has | ||
seen a lot of discussions about strict vs loose typing, type hinting and | seen a lot of discussions about strict vs loose typing, type hinting and | ||
- | related features. | + | related features. Through these discussions, |
+ | them as early as possible. Strictifying types is an approach but, unfortunately, | ||
+ | so well with PHP as a loose-typed language. | ||
- | To summarize years of flame wars, developers argue that strict typing and type hinting | + | This RFC proposes an alternative approach, already present in several |
- | will make their source code cleaner and easier to debug. On the other side, these | + | languages, named 'Design by Contract' |
- | features must remain optional and compatible with 'basic' | + | |
- | The debate generally dies in endless discussions about the concept | + | |
- | ' | + | |
- | With this RFC, we propose an alternative approach, already present in several | + | Here is the definition of a contract, according |
- | languages, named ' | + | |
- | Note: This is NOT a replacement for strict typing, while subjects are closely related. | + | The idea of a contract |
- | This is just another approach. | + | to true. If it does not, the contract is broken, and by definition, the program |
- | You may see it as a replacement or as a supplement | + | has a bug in it. Contracts form part of the specification for a program, moving |
- | hinting, depending on your own position on the subject. | + | it from the documentation |
+ | | ||
+ | Moving the contracts into the code makes them verifiable against | ||
- | We won't detail | + | For more info on the DbC theory, use the links in the 'reference' |
- | the reference section below. To make it very short, 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/ | + | |
- | phase only. In production phase, DbC checks are turned off. | + | |
- | Note: Depending on languages, | + | An important point in DbC theory is that contracts |
- | concepts we support in this RFC. | + | A global switch allows to turn DbC checks off when the software goes to production. |
- | So, the most important points are : | + | So, what we need to retain |
- | * DbC constraints can be extremely detailed | + | * DbC constraints can be highly sophisticated |
+ | * As they are checked at runtime, DbC constraints can check types AND values. | ||
* DbC checks must not handle checks that must always run, even in production. Validating user input, for instance, must remain out of DbC constraints. | * DbC checks must not 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' | + | * DbC and 'Test Driven Development' |
===== Examples ===== | ===== Examples ===== | ||
- | First, an example of a function defining input, inline | + | First, an example of a function defining input and output constraints |
(' | (' | ||
Line 71: | Line 77: | ||
* This function computes the area of a triangle using Heron' | * This function computes the area of a triangle using Heron' | ||
* | * | ||
- | * @param | + | * @param |
* @requires ($a >= 0) | * @requires ($a >= 0) | ||
- | * @param | + | * @param |
* @requires ($b >= 0) | * @requires ($b >= 0) | ||
- | * @param | + | * @param |
* @requires ($c >= 0) | * @requires ($c >= 0) | ||
* @requires ($a <= ($b+$c)) | * @requires ($a <= ($b+$c)) | ||
Line 81: | Line 87: | ||
* @requires ($c <= ($a+$b)) | * @requires ($c <= ($a+$b)) | ||
* | * | ||
- | * @return | + | * @return |
* @ensures ($> >= 0) | * @ensures ($> >= 0) | ||
*/ | */ | ||
Line 88: | Line 94: | ||
{ | { | ||
$halfPerimeter = ($a + $b + $c) / 2; | $halfPerimeter = ($a + $b + $c) / 2; | ||
- | |||
- | // @assert ($halfPerimeter >= 0) | ||
return sqrt($halfPerimeter | return sqrt($halfPerimeter | ||
Line 98: | Line 102: | ||
</ | </ | ||
- | With such a definition, this line: | + | Then : |
<code php> | <code php> | ||
- | $area=triangleArea(10,2,3); | + | $area=triangleArea(4,2,3); |
- | </code> | + | -> OK |
- | will raise: | + | $area=triangleArea(' |
+ | -> PHP Fatal error: triangleArea: | ||
- | < | + | $area=triangleArea(10, |
- | PHP Fatal error: triangleArea: | + | -> PHP Fatal error: triangleArea: |
</ | </ | ||
Line 138: | Line 143: | ||
Note that we didn't provide any constraint on $count input, as this | Note that we didn't provide any constraint on $count input, as this | ||
parameter is used for output only. | parameter is used for output only. | ||
+ | |||
+ | Finally, we rewrite the first example as a class : | ||
+ | |||
+ | <code php> | ||
+ | <?php | ||
+ | /** | ||
+ | * @invariant ($this-> | ||
+ | * @invariant ($this-> | ||
+ | * @invariant ($this-> | ||
+ | */ | ||
+ | |||
+ | class triangle | ||
+ | { | ||
+ | /*-- Properties */ | ||
+ | |||
+ | /** @var number Side lengths */ | ||
+ | |||
+ | private $a,$b,$c; | ||
+ | |||
+ | //--------- | ||
+ | /** | ||
+ | * @param number $a Length of 1st side | ||
+ | * @param number $b Length of 2nd side | ||
+ | * @param number $c Length of 3rd side | ||
+ | * | ||
+ | * No need to repeat constraints on values as they are checked by class invariants. | ||
+ | */ | ||
+ | |||
+ | public function __construct($a, | ||
+ | { | ||
+ | $this-> | ||
+ | $this-> | ||
+ | $this-> | ||
+ | } | ||
+ | |||
+ | //--------- | ||
+ | /** | ||
+ | * Compute area of a triangle | ||
+ | * | ||
+ | * This function computes the area of a triangle using Heron' | ||
+ | * | ||
+ | * @return number The triangle area | ||
+ | * @ensures ($> >= 0) | ||
+ | */ | ||
+ | |||
+ | public function area() | ||
+ | { | ||
+ | $halfPerimeter = ($this-> | ||
+ | |||
+ | return sqrt($halfPerimeter | ||
+ | * ($halfPerimeter - $this-> | ||
+ | * ($halfPerimeter - $this-> | ||
+ | * ($halfPerimeter - $this-> | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | and check DbC constraints : | ||
+ | |||
+ | <code php> | ||
+ | $t= new triangle(4, | ||
+ | -> OK | ||
+ | |||
+ | $t=new triangle(' | ||
+ | -> PHP Fatal error: triangle:: | ||
+ | |||
+ | $area=triangleArea(10, | ||
+ | -> PHP Fatal error: triangle: DbC invariant violation (($this-> | ||
+ | </ | ||
===== Proposal ===== | ===== Proposal ===== | ||
- | DbC typically | + | DbC defines three constraint types : |
* pre-conditions: | * pre-conditions: | ||
* post-conditions: | * post-conditions: | ||
- | * class invariants: Constraints on class properties. In PHP, two subtypes exist : constraints on static properties and constraint on dynamic (instance) | + | * class invariants: Constraints on class properties. |
In this document, we propose a mechanism to implement these constraints in the PHP world. | In this document, we propose a mechanism to implement these constraints in the PHP world. | ||
Line 151: | Line 224: | ||
==== Syntax ==== | ==== Syntax ==== | ||
- | We propose to include the DbC directives | + | We propose to include the DbC directives in phpdoc blocks. Here are the |
- | constraints will be included | + | main reasons, that make it, in my opinion, a better choice than every other syntaxes |
- | inline assertions will be included in plain comments. | + | proposed so far : |
- | The benefits are : | + | |
- | + | * Phpdoc comments, while not perfect, have always played the role of annotations in PHP. ' | |
- | | + | * DbC can use a great part of the already-written |
- | * A lot of PHP code is already documented using phpdoc. So, unchanged code will already benefit from DbC. | + | |
- | * phpDocumentor will easily take advantage of the extensions DbC is bringing to phpdoc syntax | + | |
- | * PHP IDEs already | + | |
+ | Note: Some people on the mailing list are religiously opposed to including information | ||
+ | in phpdoc blocks, despite the fact that thousands of people already use them | ||
+ | for this purpose. The reason is that the parser cannot handle that. I agree, but | ||
+ | that's not a task for the parser, that's a task for an external tool. We just need | ||
+ | the hooks. | ||
+ | |||
==== Side effects ==== | ==== Side effects ==== | ||
Line 170: | Line 246: | ||
the developer' | the developer' | ||
- | ==== Pre-conditions | + | ==== DbC types ==== |
- | These conditions | + | DbC types are an extension and formalization of the pre-existing phpdoc |
- | arguments have been received, but before starting executing the function body. | + | argument/ |
- | Pre-conditions | + | DbC types are not present |
- | Argument types are used first and explicit assertions supplement argument | + | This is a PHP-specific addition to enhance simplicity |
- | with additional | + | seen as built-in |
- | Argument types are checked before explicit assertions, meaning that | + | Here are the main benefits of defining a set of DbC types : |
- | explicit assertions can assume correct | + | |
- | === Argument | + | * PHP is_xxx() functions are not as intuitive as they may seem, as they are based on zval types (an equivalent of strict type checks). They are not appropriate for people who just want to accept a limited set of type juggling (accepting a numeric string from a DB, for instance). Unfortunately, |
+ | * As it was already said, tons of source code already contains argument return/ | ||
+ | * Readability is a key point too: just compare a type like ' | ||
+ | * DbC types allow static analysis, which is practically impossible with conditions. | ||
+ | * A lot of other analyzis/ | ||
- | Argument type syntax is an extension and formalization of pre-existing phpdoc | + | DbC types are used to check : |
- | argument types. phpdoc accepts almost any string as argument type. DbC applies | + | |
- | a real meaning on these types, reusing the types commonly used in phpdoc blocks. | + | |
- | + | ||
- | Argument types are not present in original DbC syntax (like Eiffel or D implementation). | + | |
- | This is a PHP-specific addition to enhance simplicity and readability. Argument types | + | |
- | are just shortcuts as they could be replaced by explicit assertions. | + | |
- | + | ||
- | Readability is the key point here: just compare a type like ' | + | |
- | the PHP code to check the same ! | + | |
- | + | ||
- | Argument | + | |
* arguments sent to a function | * arguments sent to a function | ||
- | * arguments passed by ref returned by the function | + | * arguments passed by ref returned by a function |
* the function' | * the function' | ||
* the type of class properties | * the type of class properties | ||
- | == Syntax == | + | === Syntax |
- | Argument | + | DbC types don' |
- | Here is a pseudo-grammar of argument | + | Here is a pseudo-grammar of DbC types : |
< | < | ||
- | phpdoc-line = " | + | dbc-type = compound-type |
compound-type = type, { " | compound-type = type, { " | ||
type = " | type = " | ||
- | | " | + | | " |
+ | | " | ||
+ | | "float!" | ||
| " | | " | ||
+ | | " | ||
| array-type | | array-type | ||
| " | | " | ||
Line 224: | Line 295: | ||
| " | | " | ||
| " | | " | ||
+ | | " | ||
array-type = " | array-type = " | ||
Line 235: | Line 307: | ||
</ | </ | ||
- | Every types are detailed below. | + | === DbC types vs zval types === |
- | == DbC types vs zval types == | + | DbC types follow |
- | + | PHP API type juggling | |
- | DbC types have specific rules to match PHP zvals. These rules are less permissive than | + | |
- | PHP API type juggling, but more than previously-proposed strict typing. Actually, | + | |
these types try to be a more intuitive compromise between both. | these types try to be a more intuitive compromise between both. | ||
- | As everything related to intuition, the choices made here are sometimes | + | Strict typing is sometimes |
+ | strict types. | ||
Note that the benefit of DbC, here, is that we can match depending on zval values, as | Note that the benefit of DbC, here, is that we can match depending on zval values, as | ||
we don't care about performance. | we don't care about performance. | ||
- | |||
- | Here is the table of possible matches between zval types/ | ||
- | types : | ||
^ ^ Zval type ^^^^^^^^ | ^ ^ Zval type ^^^^^^^^ | ||
^ DbC type ^ IS_NULL ^ IS_LONG ^ IS_DOUBLE ^ IS_BOOL(1) ^ IS_ARRAY ^ IS_OBJECT ^ IS_STRING ^ IS_RESOURCE ^ | ^ DbC type ^ IS_NULL ^ IS_LONG ^ IS_DOUBLE ^ IS_BOOL(1) ^ IS_ARRAY ^ IS_OBJECT ^ IS_STRING ^ IS_RESOURCE ^ | ||
^ integer | ^ integer | ||
- | ^ float | + | ^ integer! |
+ | ^ number | ||
+ | ^ float! | ||
^ string | ^ string | ||
+ | ^ string! | ||
^ array | No | ^ array | No | ||
- | ^ callable | + | ^ callable |
^ object | ^ object | ||
^ resource | ^ resource | ||
Line 264: | Line 335: | ||
^ mixed | Yes | Yes | Yes | Yes | ^ mixed | Yes | Yes | Yes | Yes | ||
^ boolean | ^ boolean | ||
+ | ^ boolean! | ||
* (1) IS_TRUE/ | * (1) IS_TRUE/ | ||
Line 271: | Line 343: | ||
* (5) only if is_callable(arg, | * (5) only if is_callable(arg, | ||
* (6) only if class defines a %%__%%toString() method\\ | * (6) only if class defines a %%__%%toString() method\\ | ||
- | * (7) O is false, 1 is true. Other values don't match (not sure we shouldn' | + | * (7) O is false, 1 is true. Other values don't match (to be discussed) |
- | You may note that this is much more restrictive that PHP native type juggling. | + | === DbC types === |
== integer == | == integer == | ||
Line 284: | Line 356: | ||
Synonyms: ' | Synonyms: ' | ||
- | == float == | + | == integer! == |
+ | |||
+ | A zval-type-based integer value, positive or negative. | ||
+ | |||
+ | Note: This type is equivalent to is_int($arg). | ||
+ | |||
+ | Synonyms: ' | ||
+ | |||
+ | == number | ||
Any value that returns true through is_numeric(). | Any value that returns true through is_numeric(). | ||
Line 290: | Line 370: | ||
Equivalent to ' | Equivalent to ' | ||
- | Synonyms: ' | + | Synonyms: ' |
+ | |||
+ | == float! == | ||
+ | |||
+ | A zval-type-based float value. | ||
+ | |||
+ | Note: This type is equivalent to is_float($arg). | ||
== string == | == string == | ||
Line 296: | Line 382: | ||
An entity that can be represented by a string. Numeric values are accepted as strings, | An entity that can be represented by a string. Numeric values are accepted as strings, | ||
as well as objects whose class defines a __toString() method. | as well as objects whose class defines a __toString() method. | ||
+ | |||
+ | == string! == | ||
+ | |||
+ | Accepts IS_STRING zvals and objects whose class defines a __toString() method. | ||
== array == | == array == | ||
Line 314: | Line 404: | ||
== callable == | == callable == | ||
- | A string or array returning true through ' | + | A string, object |
Please consult the [[http:// | Please consult the [[http:// | ||
Line 358: | Line 448: | ||
== scalar == | == scalar == | ||
- | Shortcut for 'integer|float|boolean|string' | + | Shortcut for 'numeric|boolean|string' |
Equivalent to ' | Equivalent to ' | ||
Line 395: | Line 485: | ||
Synonyms: ' | Synonyms: ' | ||
+ | |||
+ | == boolean! == | ||
+ | |||
+ | Accepts IS_BOOL zvals only (IS_TRUE/ | ||
+ | |||
+ | Synonyms: ' | ||
+ | |||
+ | ==== Pre-conditions ==== | ||
+ | |||
+ | These conditions are checked at the beginning of a function or method, after | ||
+ | arguments have been received, but before starting executing the function body. | ||
+ | |||
+ | Pre-conditions are expressed in two forms : argument types, and explicit assertions. | ||
+ | Argument types are used first and explicit assertions supplement argument types | ||
+ | with additional conditions (like conditions between arguments). | ||
+ | |||
+ | Argument types are checked before explicit assertions, meaning that | ||
+ | explicit assertions can assume correct types. | ||
=== Optional arguments === | === Optional arguments === | ||
Line 443: | Line 551: | ||
states that a subclass can override pre-conditions only if it loosens them. | states that a subclass can override pre-conditions only if it loosens them. | ||
- | The logic we implement is in the spirit of the way we manage | + | The logic we implement is in the spirit of the way PHP handles |
* Function pre-conditions are checked. If the function does not define any pre-condition, | * Function pre-conditions are checked. If the function does not define any pre-condition, | ||
Line 492: | Line 600: | ||
Note that an argument passed by reference can have a ' | Note that an argument passed by reference can have a ' | ||
- | its input type, a ' | + | its input type and/ |
In the str_replace() example above, we don't define an input type for $count | In the str_replace() example above, we don't define an input type for $count | ||
because it is undefined. | because it is undefined. | ||
Line 573: | Line 681: | ||
The same mechanism is used as with pre/ | The same mechanism is used as with pre/ | ||
checked only if explicitely called using ' | checked only if explicitely called using ' | ||
- | |||
- | ==== Inline assertions ==== | ||
- | |||
- | === Syntax === | ||
- | |||
- | These assertions can appear anywhere PHP code is valid. | ||
- | |||
- | < | ||
- | // @assert < | ||
- | </ | ||
- | |||
- | === Scope === | ||
- | |||
- | < | ||
==== Nested calls ==== | ==== Nested calls ==== | ||
Line 603: | Line 697: | ||
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
- | PHP 7. Backporting to PHP 5 is possible if implemented as a separate extension. | + | As the plan is to implement this in a separate extension, it should be availbale for PHP 5 ans PHP 7. |
===== RFC Impact ===== | ===== RFC Impact ===== | ||
Line 612: | Line 706: | ||
==== To Existing Extensions ==== | ==== To Existing Extensions ==== | ||
- | < | + | None |
==== To Opcache ==== | ==== To Opcache ==== | ||
Line 624: | Line 718: | ||
==== php.ini Defaults ==== | ==== php.ini Defaults ==== | ||
- | dbc.enforce : boolean | + | A boolean |
* php.ini-development value: true | * php.ini-development value: true | ||
Line 641: | Line 735: | ||
- Extend type syntax (define a syntax for ranges, enums, etc) | - Extend type syntax (define a syntax for ranges, enums, etc) | ||
- Implement static-only class constraints (to be called before and after executing a static or dynamic public method) | - Implement static-only class constraints (to be called before and after executing a static or dynamic public method) | ||
- | - Extend DbC to interfaces | + | - Extend DbC to interfaces and traits |
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
Line 649: | Line 743: | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
- | Not sure this should be implemented in the PHP core. A Zend extension | + | This should be implemented in a Zend extension, |
- | if possible. An additional benefit, in this case, would be to add the feature to PHP 7 and PHP 5. | + | |
- | + | ||
- | That's why I asked Derick if we could implement this as an addtional feature to xdebug. | + | |
===== References ===== | ===== References ===== | ||
Line 662: | Line 753: | ||
[[http:// | [[http:// | ||
+ | [[https:// |
rfc/dbc.1423404346.txt.gz · Last modified: 2017/09/22 13:28 (external edit)