
In real life, we are everywhere faced with various contracts: when applying for a job, when doing work, when signing mutual agreements, and many others. The legal validity of contracts guarantees our protection of interests and does not allow their violation without consequences, which gives us confidence that the items described in the contract will be fulfilled. This confidence helps us plan time, plan expenses, and plan the necessary resources. And what if the program code will be described by contracts? Interesting? Then welcome under the cat!
Introduction
The very idea of ​​contract programming originated in the 90s with Bertrand Meyer when developing the object-oriented programming language
Eiffel . The essence of Bertrand's idea was that it was necessary to have a tool for describing formal verification and formal specification of code. Such a tool would give specific answers: “the method undertakes to do its work if you fulfill the conditions necessary to call it”. And contracts could not be better suited for this role, because they allowed us to describe what will be received from the system (specification) in case of compliance with preconditions (verification). Since then, there have been many implementations of this programming methodology, both at the level of a specific language and in the form of separate libraries, allowing to set contracts and verify them with external code. Unfortunately, in PHP there is no support for contract programming at the level of the language itself, so the implementation can only be done using third-party libraries.
Contracts in code
Since contract programming was developed for an object-oriented language, it is not difficult to guess that the main working elements for contracts are classes, methods, and properties.
Preconditions
The simplest version of the contract are
preconditions - requirements that must be met before a specific action. Within the framework of OOP, all actions are described by methods in classes, therefore preconditions are applied to methods, and they are checked at the time the method is called, but before the method body itself is executed. The obvious use is to check the validity of the passed parameters to the method, their structure and correctness. That is, using preconditions, we describe in the contract everything that we will not work with. This is great!
')
Not to be unfounded, let's consider an example:
class BankAccount { protected $balance = 0.0; public function deposit($amount) { if ($amount <= 0 || !is_numeric($amount)) { throw new \InvalidArgumentException("Invalid amount of money"); } $this->balance += $amount; } }
We see that the method of replenishing the balance implicitly requires a numerical value of the amount of the amount of replenishment, which must also be strictly greater than zero, otherwise an exception will be thrown. This is a typical precondition in the code. However, it has several drawbacks: we are forced to look for these checks through our eyes and, being in a different class, we cannot quickly assess the presence / absence of such checks. Also, without an explicit contract, we will have to remember that the class code contains the necessary checks for the incoming arguments and we don’t have to worry about them. Another factor: these checks are always performed, both in the development mode and in the combat mode of the application, which has a slight negative effect on the speed of the application.
In terms of the implementation of preconditions, in PHP there is a special construct for verifying assertions -
assert () . Its great advantage is that checks can be disabled in combat mode, replacing the entire command code with a single
NOP . Let's look at how to describe the precondition with this construct:
class BankAccount { protected $balance = 0.0; public function deposit($amount) { assert('$amount>0 && is_numeric($amount); /* Invalid amount of money /*'); $this->balance += $amount; } }
I want to draw attention to the fact that preconditions within the framework of contracts are used to check the logs of the program and are not responsible for the validity of the parameters transmitted from the client. Contracts are only responsible for interaction within the system itself. Therefore, user input should always be filtered using filters, since assertions can be turned off.
Postconditions
The next category of contracts is
post conditions . As the name suggests, this type of check is performed after the method body has been executed, but until control returns to the calling code. For our
deposit
method from the example, we can form the following postcondition: the balance of the account after calling the method should be equal to the previous value of the balance plus the amount of replenishment. The only thing left to do is to describe all this in the form of a statement in the code. But here we are waiting for the first disappointment: how to form this requirement in the code, because we first change the balance in the body of the method itself, and then try to check the statement where the old value of balance is needed. Cloning an object before executing a code and checking post-conditions can help here:
class BankAccount { protected $balance = 0.0; public function deposit($amount) { $__old = clone $this; assert('$amount>0 && is_numeric($amount); /* Invalid amount of money /*'); $this->balance += $amount; assert('$this->balance == $__old->balance+$amount; /* Contract violation /*'); } }
Another disappointment awaits us when describing postconditions for methods that return a value:
class BankAccount { protected $balance = 0.0; public function getBalance() { return $this->balance; } }
How to describe here the contract condition that the method should return the current balance? Since the post-condition is executed after the body of the method, we will stumble on
return
earlier than our test works. Therefore, you will have to change the method code in order to save the result in the
$__result
variable and then compare it with
$this->balance
:
class BankAccount { protected $balance = 0.0; public function getBalance() { $__result = $this->balance; assert('$__result == $this->balance; /* Contract violation /*'); return $__result; } }
And this is for a simple method, not to mention the case when the method is large and has several cusps. As you already guessed, at this stage ideas about using contract programming in a PHP project quickly die, since the language does not support the necessary control structures. But there is a solution! And about him will be written below, have a little patience.
Invariants
It remains for us to consider another important type of contract:
invariants . Invariants are special conditions that describe the integral state of an object. An important feature of invariants is that they are always checked after calling any public method in the class and after calling the constructor. Since the contract determines the state of the object, and public methods - the only way to change the state from the outside, we get the complete specification of the object. For our example, a condition can be a good invariant: an account balance should never be less than zero. However, with invariants in PHP, the situation is even worse than with postconditions: there is no way to easily add a check to all public methods of a class, so that after calling any public method, you can check the necessary condition in the invariant. It is also not possible to refer to the previous state of the
$__old
object and the returned result of
$__result
. There are no contracts without invariants, so for a long time there were no means and methods for implementing this functionality.
New opportunities
Meet PhpDeal - an experimental
DbC -
framework for contract programming in PHP .
Once the
Go! Framework has been developed
! AOP for aspect-oriented programming in PHP , I had thoughts in my head about automatic parameter validation, condition checking, and much, much more. A trigger for creating a project for contract programming was a discussion in
PHP.Internals . Surprisingly, with the help of AOP, the problem was solved in just a couple of actions: it was necessary to describe an aspect that would intercept the execution of methods marked with contract annotations and perform the necessary checks before or after calling the method.
Let's take a look at how contracts can be used with this framework:
use PhpDeal\Annotation as Contract; class Account implements AccountContract { protected $balance = 0.0; public function deposit($amount) { $this->balance += $amount; } public function getBalance() { return $this->balance; } }
As you have noticed, all contracts are described as annotations inside the dock blocks and contain the necessary conditions inside the annotation itself. There is no need to change the original executable code of the class, it remains as clean as the code without contracts.
Preconditions are specified using the
Verify
annotation and determine the checks that will be performed at the time the method is called, but before the method body itself is executed. Preconditions work in the scope of the class method, so they have access to all properties, including private ones, and also have access to the method parameters.
Postconditions are specified by annotation, which has the standard name
Ensure
in terms of contract programming. The code has a similar scope, as the method itself, in addition, the variables
$__old
with the state of the object before the method is executed and the variable
$__result
, which contains the value that was returned from this method.
Thanks to the use of AOP, it became possible to implement even invariants - they are elegantly described in the form of
Invariant
annotations in the class dock and behave similarly to postconditions, but for all methods.
While experimenting with code, I discovered an amazing similarity of contracts with interfaces in PHP. If the standard interface determines the requirements for the standard of interaction with the class, then the contracts allow to describe the requirements for the state of the class instance. Using the description of the contract in the interface, it is possible to describe the requirements for both interaction with the object and the state of the object, which will then be implemented in the class:
use PhpDeal\Annotation as Contract; interface AccountContract { public function deposit($amount); public function getBalance(); }
Then the most interesting begins: when creating a class and determining the desired method, any modern IDE transfers all the annotations from the method description in the interface to the class itself. And this allows the PhpDeal engine to find them and provide automatic verification of contracts in each particular class that implements this interface. For those who want to feel everything with their own hands, you can download the project from the github, install all dependencies using a composer, set up a local web server to this folder and then simply open the code from the
demo folder in your browser
Conclusion
Contract programming in PHP is a completely new paradigm that can be used for defensive programming, to improve the quality of the code and ensure the readability of contracts, defined as requirements and specifications. The big plus of this implementation is that the code of the classes remains readable, the annotations themselves are read as documentation, and also that in combat mode the check can be completely disabled and does not require absolutely no time for additional unnecessary checks in the code. Interesting fact: the frame framework itself contains only a couple of annotations and one aspect class that links these annotations with specific logic.
Thank you for attention!
Related Links:
- Wikipedia - contract programming
- PhpDeal framework for contract programming in PHP
- Framework Go! AOP for aspect-oriented programming in PHP