rfc:dbc2
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
rfc:dbc2 [2015/02/10 06:50] – yohgaki | rfc:dbc2 [2015/02/10 12:52] – krakjoe | ||
---|---|---|---|
Line 2: | Line 2: | ||
* Version: 0.1 | * Version: 0.1 | ||
* Date: 2015-02-10 | * Date: 2015-02-10 | ||
- | * Author: Yasuo Ohgaki < | + | * Author: Yasuo Ohgaki < |
* Status: Draft | * Status: Draft | ||
* First Published at: http:// | * First Published at: http:// | ||
- | ===== Important Note==== | + | ===== Important Note ===== |
- | This RFC is a part of " | + | This RFC is an alternative approach to " |
http:// | http:// | ||
- | There are many way to achieve DbC. This RFC proposes DbC as function/ | + | Contracts can be defined without modification |
- | ===== Introduction ===== | + | Other advantages of a native implementation, |
- | Design by Contract (DbC) or Contract Programming is powerful program design concept based on **Contracts** that | + | |
+ | | ||
+ | | ||
+ | | ||
+ | * handling of contract inheritance | ||
- | - Define **precondition** contract for methods/ | + | All of the above applies to PHP. |
- | - Define **postcondition** contract for methods/ | + | |
- | - Define **invariant** contract for methods/ | + | |
- | These contracts are evaluated development time only. Therefore, there is **no performance penalty** with DbC. | + | ===== Introduction ===== |
- | * http:// | + | The D manual contains the following succinct definition of contracts: |
- | * http:// | + | |
- | ====DbC Changes | + | > The idea of a contract is simple - it's just an expression that must evaluate to true. If it does not, the contract |
- | ==CalleR and CalleE responsibility and validation: | + | This should be easily comprehensible, it further validates |
- | * With modularized design without DbC, the more code is consolidated, | + | |
- | * With DbC, parameter checks are done as **precondition**. Passing valid parameter to a function is __calleR__ responsibility. | + | |
- | ==Exit value validation:== | + | ===== Proposal ===== |
- | * Without native DbC, return value validity cannot be checked without scattered assert() for every place function is used. \\ i.e. Programmers has to write " | + | |
- | * With native DbC, return value validity is checked as **postcondition**. It does not require scattered assert(). | + | |
- | ==Class state validation:== | + | Support for the following contracts will be introduced: |
- | * With native DbC, **invariant** condition may be defined. **Invariant** allows programmers consolidate class state which must be TRUE for always. \\ e.g. $account_balance >= 0 | + | |
- | **precondition** | + | * precondition |
+ | * postcondition | ||
+ | * invariant | ||
- | DbC reverses the responsibility caller | + | ===== Pre and Post Condition ===== |
- | DbC encourages users to control inputs/ | + | These contracts should be defined after the function declaration, |
- | ====Readability of Code / Testing Code==== | + | 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. |
- | Since contracts are written in function/method definition, it's much easier to find function/method spec compared to conditions written in UnitTests. It also enables contract check for running applications operated by humans. | + | < |
+ | function | ||
+ | require($a > 0) | ||
+ | require($b > 0) | ||
+ | return($ret, | ||
+ | { | ||
+ | return $a + $b; | ||
+ | } | ||
+ | </code> | ||
- | DbC is __NOT__ a complement of UnitTest, but it provide additional way of testing programs. Programs should | + | 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, " | ||
+ | return $ret; | ||
+ | } | ||
+ | </ | ||
- | ====General Concern of Design by Contract==== | + | ===== Invariant ===== |
- | Some may concern that lack of checks under production environment. However, all inputs to programs should validate input parameter validity right after input parameters are passed to program. | + | Invariant contracts are declared using **require** inside class body. Multiple contracts |
- | This is basic principle of secure programs. Users should not DbC everywhere. User should have proper defense in depth as security measure. Security standard like "ISO 27000" or security guideline like " | + | < |
+ | class Accumulator { | ||
+ | private $sum = 0; | ||
+ | |||
+ | function add(int $a) | ||
+ | require($a > 0) | ||
+ | { | ||
+ | $this-> | ||
+ | } | ||
+ | requre($this-> | ||
+ | } | ||
+ | </ | ||
- | ===== Proposal ===== | + | this code is going to be evaluated as |
- | Introduce native contracts " | + | < |
+ | class Accumulator { | ||
+ | private $sum = 0; | ||
+ | |||
+ | function add(int $a) | ||
+ | { | ||
+ | assert($a > 0); | ||
+ | assert($this-> | ||
+ | $this->sum += $a; | ||
+ | assert($this-> | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
- | While new usage of require, return may seem strange at first, users can use familiar PHP syntax for DbC definitions. | + | **Invariants are not evaluated when object properties are changed from outside the class scope.** |
- | * require(assert-expr [,' | + | ====Contracts Inheritance Rules==== |
- | * return(assert-expr [,' | + | |
- | **require**, **return** behave just like assert(). | + | Contracts are constant, this has the following implications: |
+ | * a derived class must satisfy invariants contracts of it's parent | ||
+ | * a derived class overriding a method must satisfy the pre and post condition contracts of it's prototype. | ||
- | ====Example==== | + | Thus, given the following |
- | <code php> | + | |
- | class Product { | + | |
- | protected $inventory; | + | |
- | require($this-> | + | |
- | // User is supposed to check inventory first | + | < |
- | | + | class Child { |
- | require($quantity | + | require ($this->age < 18); |
- | { | + | |
- | | + | |
- | } | + | require(somethingAbout($input)) { |
+ | /* ... */ | ||
+ | | ||
- | | + | |
- | function sell($quantity) | + | |
- | require($quantity > 0) // Precondition | + | |
- | return($> | + | |
- | { | + | |
- | return $this> | + | |
- | } | + | |
} | } | ||
- | </ | ||
+ | class Monster extends Child { | ||
+ | require ($this-> | ||
- | ====Longer Class Definition Example - need better one==== | + | |
- | <code php> | + | require(somethingElseAbout($input)) { |
- | class MySession2 extends SessionHandler { | + | |
- | public $path; | + | |
- | require(is_string($this-> | + | |
- | + | ||
- | | + | |
- | require(strlen($path) > 3 && strlen($name) > 3); | + | |
- | return(is_bool($> | + | |
- | | + | |
- | | + | |
- | $path = sys_get_temp_dir(); | + | |
- | } | + | |
- | $this-> | + | |
- | return true; | + | |
} | } | ||
- | | + | |
- | return(is_bool($> | + | |
- | { | + | |
- | return true; | + | |
- | } | + | |
- | + | ||
- | public function read($id) | + | |
- | return(is_string($> | + | |
- | { | + | |
- | return @file_get_contents($this-> | + | |
- | } | + | |
- | + | ||
- | public function write($id, $data) | + | |
- | require(strlen($id) > 10 && strlen($data) > 10) | + | |
- | return(is_bool($> | + | |
- | { | + | |
- | return file_put_contents($this-> | + | |
- | } | + | |
- | + | ||
- | public function destroy($id) | + | |
- | require(strlen($id) > 10) | + | |
- | return(is_bool($> | + | |
- | { | + | |
- | @unlink($this-> | + | |
- | } | + | |
- | + | ||
- | public function gc($maxlifetime) | + | |
- | require($maxfiletime > 60) | + | |
- | return($> | + | |
- | { | + | |
- | foreach (glob($this-> | + | |
- | if (filemtime($filename) + $maxlifetime < time()) { | + | |
- | @unlink($filename); | + | |
- | } | + | |
- | } | + | |
- | return true; | + | |
- | } | + | |
} | } | ||
</ | </ | ||
+ | //Monster// must not break **any** contract in //Child//. | ||
====Execution Control==== | ====Execution Control==== | ||
- | Introduce | + | A new " |
- | A method/ | + | * dbc=on |
+ | * dbc=off | ||
+ | * dbc=zero_cost - don't generate code for contracts. This may be set only in php.ini and can't be changed through ini_set(). | ||
- | ===Basic Execution=== | + | The default value if " |
- | require() - executed before function body | + | ===Contracts Evaluation Order=== |
- | return() - executed upon return | + | |
- | invariant - executed after require() and before return(). There are few exceptions explained later. | + | |
- | ==Development mode: dbc=On == | + | If "dbc" is set to " |
- | - require() | + | - all require() |
- | - class invariant | + | - all require() contracts (invariants) defined for this class (in defined oredrer) |
- | - method() | + | - method |
- | - class invariant | + | - all require() contracts (invariants) defined for this class (in defined oredrer) |
- | - return() | + | - all return() |
- | ==Production mode: dbc=Off == | + | **Invariant and Special Methods** |
- | | + | * < |
- | + | * < | |
- | ==Execution details when DbC is enabled== | + | |
- | + | ||
- | =Invariant and Special Methods= | + | |
- | + | ||
- | | + | |
- | * __destruct()/ | + | |
- | + | ||
- | =Class Inheritance= | + | |
- | + | ||
- | * When parent class methods are called, DbC conditions are executed | + | |
- | * Special methods execution exception is the same | + | |
- | + | ||
- | =Abstract Class= | + | |
- | + | ||
- | * The same as usual class. | + | |
- | + | ||
- | =Trait= | + | |
- | + | ||
- | * The same as usual class. | + | |
- | + | ||
- | =Interface= | + | |
- | + | ||
- | * Cannot define DbC contracts. | + | |
+ | **Static Call** | ||
+ | * Only pre and post conditions are executed. | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
Line 221: | Line 186: | ||
==== To Opcache ==== | ==== To Opcache ==== | ||
- | + | Opcache will have to be extended to support contracts and store them in shared memory. | |
- | Dmitry, could you write impact when discussion is finished? | + | |
==== New Constants ==== | ==== New Constants ==== | ||
Line 229: | Line 192: | ||
==== php.ini Defaults ==== | ==== php.ini Defaults ==== | ||
- | |||
dbc=Off for all (INI_ALL) | dbc=Off for all (INI_ALL) | ||
Line 237: | Line 199: | ||
===== Open Issues ===== | ===== Open Issues ===== | ||
+ | * Contracts inheritance rules | ||
+ | * Consider introduction of **static require()** as class invariant for static methods | ||
* Need to discuss syntax | * Need to discuss syntax | ||
* How to manage votes for 2 RFCs | * How to manage votes for 2 RFCs | ||
- | |||
===== Unaffected PHP Functionality ===== | ===== Unaffected PHP Functionality ===== |
rfc/dbc2.txt · Last modified: 2018/03/01 23:19 by carusogabriel