This RFC is part of “Design by Contract Introduction” RFC
This RFC is an alternative approach to “Native DbC support” RFC.
Contracts can be defined without modification to the language, however, the D documentation calls the resulting implementation “clumsy and inconsistent”.
Other advantages of a native implementation, also taken from the D manual:
All of the above applies to PHP.
The D manual contains the following succinct definition of contracts:
The idea of a contract is simple - it's just an expression that must evaluate to true. If it does not, the contract is broken, and by definition, the program has a bug in it. Contracts form part of the specification for a program, moving it from the documentation to the code itself. And as every programmer knows, documentation tends to be incomplete, out of date, wrong, or non-existent. Moving the contracts into the code makes them verifiable against the program.
This should be easily comprehensible, it further validates the argument that the best implementation is a native one, so that the programmer really can “move the specification from the documentation to the code”.
Support for the following contracts will be introduced:
These contracts should be defined after the function declaration, but before the function body.
Multiple precondition and postcondition contracts may be used. The expressions are just a regular PHP expressions. They are evaluated in the function scope, and can access arguments and other scope variables, and return value (via a reserved name). Pre and post-conditions can't be defined for abstract methods and methods defined in interfaces.
function add(int $a, int $b) : int require($a > 0) require($b > 0) return($ret, $ret > 0, "something wrong") { return $a + $b; }
this code is going to be evaluated as
function add(int $a, int $b) : int { assert($a > 0); assert($b > 0); $ret = $a + $b; assert($ret > 0, "something wrong"); return $ret; }
Invariant contracts are declared using require inside class body. Multiple invariant contracts may be used. They may access object or static properties through $this and self. Invariant contracts may be used for classes, abstract classes and traits, but not for interfaces.
class Accumulator { private $sum = 0; function add(int $a) require($a > 0) { $this->sum += $a; } require($this->sum > 0, "overflow detected"); }
this code is going to be evaluated as
class Accumulator { private $sum = 0; function add(int $a) { assert($a > 0); assert($this->sum > 0, "overflow detected"); $this->sum += $a; assert($this->sum > 0, "overflow detected"); } }
Invariant contracts are not evaluated when object properties are changed from outside the class scope.
Contracts are constant, this has the following implications:
Thus, given the following code:
class Child { require ($this->age < 18); public function someMethod($input) require(somethingAbout($input)) { /* ... */ } /* ... */ } class Monster extends Child { require ($this->odour == OBNOXIOUS); public function someMethod($input) require(somethingElseAbout($input)) { /* ... */ } /* ... */ }
Monster must not break any contract in Child.
A new “dbc” INI switch is going to be introduced. It may get the following values:
The default value is "off".
If “dbc” is set to “on”, the order of contracts validation is the following:
Invariant and Special Methods
Static Call
None
None
None
Opcache will have to be extended to support contracts and store them in shared memory.
None
dbc=Off for all (INI_ALL)
This RFC does not affect any existing features
Documentation systems may adopt native DbC syntax for documentation purpose.
This project requires a 2/3 majority
Links to any external patches and tests go here.
If there is no patch, make it clear who will create a patch, or whether a volunteer to help with implementation is needed.
Make it clear if the patch is intended to be the final patch, or is just a prototype.
After the project is implemented, this section should contain
Keep this updated with features that were discussed on the mail lists.