In addition to AOP, appserver also offers Design-by-Contract out of the box, which is another interesting architectural approach.
First introduced by Bertram Meyer in combination with his design of the Eiffel programming language, Design by Contract allows you to define formal, precise and verifiable interface specifications of software components.
Design by Contract extends the ordinary definition of classes and interfaces by allowing the possibility of defining so called contracts
(hence the name).
These contracts allow every public method to specify preconditions
, which they need to function, and one or more post-conditions
to ensure the method results are correct.
Additionally, one can state so called invariants
, which define an atomic state of integrity for the structure itself.
Such contract elements are checked during program runtime and allow for true domain logic fail-fast programming.
Besides fail-fast behavior, which would be an exception, our implementation offers PSR-3 compatible logging, as a reaction on contract breach.
The most basic type of contract is one used all the time and is already integrated into most IDEs: annotation based type hinting. If following common syntax we can make use of these annotations to enforce strong typing at method call and return point.
We currently support the
@param
and@return
annotations as type hints (scalar and class/interface based), including special features liketyped arrays
using e.g.\stdClass[]
.
Review the following example.
<?php
/**
* Add string to storage. Will return resulting storage size
*
* @param string $string String to add
*
* @return integer
*/
public function addString($string)
{
// ...
}
As stated in the comments, this example method will add a string to a storage and return the resulting storage size. Using Design by Contract, both the parameter type and the return type will be enforced.
This is possible for scalar types as well as for complex ones and offers an addition for typed arrays
.
Review the following example.
<?php
/**
* Will return an array of all strings currently stored
*
* @return string[]
*/
public function getStrings()
{
// ...
}
A more complex use case is writing out execution constraints within contracts. This can be done using the mentioned condition clauses.
The following example expands on the addString
code snippet above.
<?php
/**
* Add string to storage. Will return resulting storage size
*
* @param string $string String to add
*
* @return integer
*
* @Ensures("$this->stringExists($string)")
*/
public function addString($string)
{
// ...
}
The resulting behavior doesn't only check for basic parameter and return type, but also ensures that the passed string variable is added to the storage (assuming the existence of a stringExists
method).
This constraint on the execution of the addString
method is, by now, solely based on its implementation and can not be changed during runtime. But, we can also add a constraint through the method parameters as shown below.
<?php
/**
* Add a string shorter than six characters to storage. Will return resulting storage size
*
* @param string $string Short string to add
*
* @return integer
*
* @Requires("strlen($string) <= 6")
*
* @Ensures("$this->stringExists($string)")
*/
public function addShortString($string)
{
// ...
}
Assuming that our storage can only handle strings equally long or shorter than six characters, we can constrain the length of our input parameter. The precondition constraint guards our storage by enforcing proper string length and at the same time assures that the string will be stored, when it does have the proper length.
A constraint based contract for the execution of a method is born.
As the Design by Contract principle is based on the concept of contracts within the business world we have to consider another element.
The already mentioned invariant
as a global constraint to possibilities of interaction.
In the financial market this would be laws about taxes and contract clauses. In the programming world, this is a valid state an object has to maintain during its lifecycle.
The invariant describes a valid state an object must have at defined moments in its lifetime.
For our implementation these defined moments are:
__construct
method)These invariants are stated using the @Invariant annotation. Below is a basic example continuing with our StringStorage scenario:
<?php
/**
* Class which is used to store string literals
*
* @Invariant("$this->onlyContainsStrings()")
*/
class StringStorage
{
// ...
}
The annotation above would result in a constraint for the content of the storage (given onlyContainsStrings
exists) that checks that there are only strings within the storage on every relevant public access to an object of the class StringStorage
.
The examples above already give a glimpse of how to use Design by Contract within your code. Here, some are some more rules and guidelines.
First of all, syntax.
The syntax of an annotation is very simple and follows Doctrine
syntax and is composed as follows:
@<AnnotationName>("<Constraint>")
The constraint itself MUST be a valid conditional PHP expression which evaluates to the boolean true
or false
or any value castable to a boolean.
The code within the constraints can make use of variables and properties of the containing structures based on their specific scope. These constraint scopes are listed below:
Annotation | Scope | Description |
---|---|---|
@Requires |
method | Shares the scope of the method the annotation belongs to |
@Ensures |
method | Shares the scope of the method the annotation belongs to |
@Invariant |
structure | Consider as called from a parameter-less private method within the structure itself |
There are two more variables, which are available to add to constraints, above and beyond the variables and properties visible within the original structure definition.
Variable name | Usable within | Description |
---|---|---|
$dgResult |
@Ensures |
Contains the result of the method the annotation belongs to. Can be used to constrain the actual method result |
$dgOld |
@Ensures |
Contains a copy of the object instance before method execution, can be used to test changes to the object's state |
Design by Contract is fully integrated into the appserver.io infrastructure's autoloading capabilities. This is due to the heavy modifications it requires for structure definitions and the applied caching in the file system.
To change any Design by Contract behavior, the change must be made within the class loader configuration.
The class loader configuration can be found within any context.xml
file.
A context.xml
file can be found:
etc/appserver/conf.d/context.xml
file.META-INF
directory.The local file can override or extend the global file's configuration, so be certain of where you make any changes or additions. Any changes made to a context.xml file will either have a global effect or will only affect the webapp it is located in.
Below is an example of the configuration appserver.io ships with.
The attributes of the classLoader
element are mandatory and need not be changed for any configuration changes.
One only might change them to use alternative implementations or bootstrapping processes.
<classLoader
name="DgClassLoader"
interface="ClassLoaderInterface"
type="AppserverIo\Appserver\Core\DgClassLoader"
factory="AppserverIo\Appserver\Core\DgClassLoaderFactory">
<params>
<param name="environment" type="string">production</param>
<param name="enforcementLevel" type="integer">7</param>
<param name="typeSafety" type="boolean">1</param>
<param name="processing" type="string">logging</param>
</params>
<directories>
<directory enforced="true">/common/classes</directory>
<directory enforced="true">/WEB-INF/classes</directory>
<directory enforced="true">/META-INF/classes</directory>
</directories>
</classLoader>
Most important are the param
elements.
See their meaning and options as listed below.
Param name | Options | Description |
---|---|---|
environment |
production|development | Whether or not contracted definitions get cached. Recommended is production, as an appserver restart will clear the cache by force, therefore the cache does not hinder development |
enforcementLevel |
[ 1 - 7 ] | A bitmask similar to the Linux user right notation. This will be used to switch on (or off) enforcement features. The bitmask can be written as invariants post-conditions preconditions , so the default 7 results in all contract elements being enforced whereas 1 would only enforce preconditions |
typeSafety |
0|1 | Whether or not annotation based type hints get enforced as described above |
processing |
logging|exception | Which kind of reaction a contract or type safety breach triggers. logging will result in a message stored within the system logger. exception will result in an exception of the type \AppserverIo\Psr\MetaobjectProtocol\Dbc\ContractExceptionInterface . Throw point is the method in which the contract was breached |
Adding a directory
element we can specify which directory should be loaded by our class loader.
This is common configuration for every class loader type.
A feature of the appserver class loader is ability to toggle the enforcement of Design by Contract for each one of the defined directories.
If the attribute enforced
is set to FALSE, no enforcement will take place for the mentioned directory.
Please note that by clicking "Load Comments", your browser will establish a connection with servers from Disqus.
For more information, please visit our privacy statement.