Download the PHP package ntwalibas/contracts without Composer
On this page you can find all versions of the php package ntwalibas/contracts. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download ntwalibas/contracts
More information about ntwalibas/contracts
Files in ntwalibas/contracts
Package contracts
Short Description Contracts libray, a design by contract libray.
License MIT
Homepage https://github.com/ntwalibas/contracts
Informations about the package contracts
Contracts - an assertion library
STATUS: Full test coverage, more predicates are the next goal and a better documentation.
This library is somewhat production ready. Please don't use it yet for sensitive data validation like in the money related domain.
Contracts is a library to help you write quite intersting assertions. Initially, I was looking at making design by contract possible in PHP but due to the nature of task, I ended up settling with assertions but kept the name.
It is quite powerful because it implements first-order predicate calculus in an intuitive way, a feature I have failed to find in many assertion and validation librairies. Heck, you can even implement your own predicates.
It is still in its early stages (many native predicates are not implemented) but it is meant to grow!
Install
The library is available on packagist and installable via composer.
Concepts
You generally write propositions that will be evaluated when you want to use them. Each proposition is made of predicates that can joined by logical operators such as AND
, OR
, IMPLIES
and EQUIVALENT
. The NOT
logical operator is not implement and is rather replaced by negated predicates.
Usage
Usage is fairly simple: pass your predicates or quantifiers to the AssertThat
funtion or Assert::That
static method and it will throw an AssertionFailedException
exception if there was any failure.
So let's understand first the idea of predicates, quantifiers and computations.
Predicates
A predicate is simply a function that returns true
or false
. In our case, the said functions will actually be methods on specific objects.
In predicate calculus, a predicate will have variables that take different values for evaluation. The same goes for us here, with a few syntactical differences. An example shall illustrate.
Assume we want to evaluate whether a given number is greater than 18. So how do we do that?
Notice the first convention: $age
is not a constant by the way we understand it in PHP. But we consider it a constant here because once its value has been set, it won't change after the predicate has recieved.
The next thing you might ask is why is it const**x**
instead of const
? That's because const
is a PHP keyword. Infact, whenever you are about to use any method or function provided by the library and it is a PHP keyword or on the list of reserved words, it will be followed by an x
.
Now back to the library constants
. A natural question here might be: what does the library consider a variable? Again an example.
Assume we have an array that maps users' name to their age and we want to know whether they're adult or minors. Using "variables" we can loop over the said array and check using the predicate.
And this is the concept of variables: a variable is simply a symbol that can take on different values within an execution context. The execution context here is the foreach
loop. Here, the age will keep changing as the loop executes. Use the setOperand(string $symbol, mixed $value)
to update the value of the variable represented by the given symbol.
Note: we'll later see a better way to check if all the users here as adults using quantifiers.
More on variables
I. A helper is provided if you want to declare a new variable instead of using a constant (for some reason).
II. Objects and array allow for an extra feature: assume you have tied a particular symbol to an object or an array. You can access methods (without arguments - such as getters) on objects or array elements referenced by a key (must be a string.)
Object example:
Array example:
All the predicates
Contracts provide different predicates grouped by data types. There predicates that make sense only for numbers, others for arrays and so on. Helpers are provided so you don't have to instantiate the classes that implement those predicates yourself. Here, the classes Variable
and Constant
will instantiate all the predicates (and more) so you can get started using them. But this is not recommended because it carries a certain overhead you might not need. If for a given specific case you just want to work with numbers (integers and floating point numbers included), use the Number
helper. And the same goes for all the other data types. Bellow is the list of all the helpers.
Some time you might want a combination that is not provided natively by the library. This is easily achieved as follows:
You must pass the Operators
instance as the first argument in the chain whether you intend to use logical operators or not because it does another job not done by predicates. The rest can be passed in any order. StringPredicates
could have come before NumberPredicates
without any problem.
From thereon, you can use varx
as before.
Logical operators
You can combine predicates by using logical operators like so:
In fact, if the second predicate will reuse the operand of the first predicate (in this case $age
), there is no need to have constx($age)
or varx($age)
before it. So the following is equally correct and brief:
The following logical operators are available:
Computations
At times you might want to perform computations on the variables (constants) passed to the predicates before actually running the predicates. That's when computations enter the picture.
Assume the user gave their year of birth and you want to know whether they're adults:
Computations are meant to simplify things when you want to make transformations on the operand to pass to the constraint without polluting your business logic with extra computations.
Quantifiers
Contracts provide two quantifiers at the moment: ForAll
and ThereExists
.
ForAll
Use ForAll
to make sure all the elements in a traversable obey a given predicate. Back to our example before: assume we want to make sure all our users are legally adults. This is how we would do it:
ThereExists
The principle is the same as for the ForAll
quantifier.
Quantifier combination
You can pass one quantifier to another as you would pass a predicate.
Note that with the example above, it is true that for all the elements in the said set, you can always find one other element in the same set (which is just the same element) such that their division will equal one.
Assert
To run assertions, just pass your predicates or quantifiers to the AssertThat
function or Assert::That
static method. An additional argument is required to document the assertion.
Indeed, you can also pass an array of predicates/quantifiers as the first argument in case you want to group assertions hence make sure to provide a key that identify each assertion to make sense of the message when an exception is thrown upon failure.
To name an assertion, pass an array with the structure ["assertionId" => "predicate|quantifier"]
as the first argument to AssertThat
. If you passed a predicate or quantifier directly without an assertion ID, it will be given the ID unnamed-assertion
.
Conclusion
Th library provides quite a nice API that is intuitive but as usual something had to be sacrificied. In this case, the performance will be lower than in most other "lightweight" assertion/validation libraries. Still I suspect the impact will not be noticeable not to mention this statement is based off the fact that the __call
method is used and that will be the performance bottleneck.
Author
Ntwali Bashige - [email protected] - http://twitter.com/nbashige
License
Contracts is licensed under MIT
, see LICENSE file.
Acknowledgment
Internally, Contracts uses internally dissect for properly evaluating boolean expressions.
Next
- Strive for full test coverage,
- Write more predicates and computations,
- Replace the boolean expression parser by a small recursive descent parser to eliminate the dependency on dissect,
- Find a way to reply less on
__call
.
Contributions are welcome. Send a pull request if you have something to add. Tests are especially welcome!