Plugins
Leo was designed to make adding functionality easy. All of Leo's functionality is derived from the Assertion class.
Accessing the Assertion instance
Leo holds a single reference to an instance of Assertion
. This ensures that all plugins are extending the same thing.
<?php
use Peridot\Leo\Leo;
$assertion = Leo::assertion();
?>
Adding methods
Methods can be added and overwritten using the ->addMethod
method of the Assertion
class.
<?php
$assertion->addMethod('hello', function ($name) {
print "hello $name";
});
?>
Methods added this way can then be accessed on the assertion instance.
<?php
$assertion->hello('everybody');
?>
Adding properties
Part of Leo's power is it's ability to build expressive language chains. The ->addProperty
method can be used to register properties that are lazily evaluated.
<?php
$assertion->addProperty('dom', function() use ($driver) {
$dom = $driver->get($this->getActual());
$this->flag('dom', $dom)
return $this;
});
?>
The function used to create the property will be evaluated when the property is accessed.
<?php
expect('http://app.com')->dom->to->have->element('.some-css-class');
?>
Memoization
If you know your property is going to always return the same result, you can cache that result by setting the memoize
argument to true when
adding a property.
<?php
$assertion->addProperty('db', function () {
//some expensive creation operation
$db = new EntityManager();
//setup schema? load mappings?
return $db;
}, true); //memoize set to true, so the db is cached and the same instance is always returned
expect('table')->in->db->to->have->record('id'); //db is cached so it wont have to be created multiple times
?>
Flags
Flags can be used to set arbitrary state on the assertion. They are useful for carrying state through an assertion chain, and can be useful for changing behavior based on some condition.
This is exactly how assertions are able to convey a negated state via the not
language chain.
<?php
$assertion->addProperty('not', function () {
return $this->flag('not', true);
});
?>
Using Assertion::extend
As a convenience, the Assertion
class contains an extend
method that is useful for leveraging packaged plugins. It accepts either a callable
or the path to a file that returns a callable.
<?php
$assertion->extend(function ($assertion) {
//add methods and properties
});
$assertion->extend(new InvokableObjectWithPluginThings());
//plugin.php
return function($assertion) {
//add methods and properties
};
// peridot.php/bootstrap.php
Leo::assertion()->extend('plugin.php');
?>
Matchers
Most assertion methods in Leo are implemented by leveraging matchers - that is classes that implement MatcherInterface.
Matchers are the classes used to determine if an actual value matches some criteria, and they are also responsible for defining templates for failure messages.
When a method added via Assertion::addMethod
returns a matcher, Leo will automatically perform an assertion using that matcher when the method is called:
<?php
$assertion->addMethod('equal', function ($expected, $message = "") {
$this->flag('message', $message);
return new EqualMatcher($expected);
});
//assertion will be performed against EqualMatcher
$assertion->equal(1, 2);
?>
Most matchers only have to implement two methods when extending the AbstractMatcher. Here is the code behind the core EqualMatcher.
<?php
namespace Peridot\Leo\Matcher;
use Peridot\Leo\Matcher\Template\ArrayTemplate;
use Peridot\Leo\Matcher\Template\TemplateInterface;
class EqualMatcher extends AbstractMatcher
{
public function doMatch($actual)
{
return $this->expected == $actual;
}
public function getDefaultTemplate()
{
return new ArrayTemplate([
'default' => 'Expected {{expected}}, got {{actual}}',
'negated' => 'Expected {{expected}} not to equal {{actual}}'
]);
}
}
?>
You can browse all the core matchers on GitHub.
Templates
The ArrayTemplate is an instance of TemplateInterface. Templates are used by matchers to provide default and negated failure messages for when a match fails. Matchers leverage templates via their getTemplate
, setTemplate
, and getDefaultTemplate
methods.
Formatters
Formatters use match results together with templates to return human readable strings for the results of a match. All formatters should implement FormatterInterface. For most cases, the Formatter that ships with Leo should suffice.
Responders
Responders are the last stop for a Leo assertion. Responders are in charge of doing something with the formatted results of a match. All responders should implement ResponderInterface. The default ExceptionResponder responds by throwing an exception with the formatted message.
<?php
namespace Peridot\Leo\Responder;
use Exception;
use Peridot\Leo\Formatter\FormatterInterface;
use Peridot\Leo\Matcher\Match;
use Peridot\Leo\Matcher\Template\TemplateInterface;
class ExceptionResponder implements ResponderInterface
{
protected $formatter;
public function __construct(FormatterInterface $formatter)
{
$this->formatter = $formatter;
}
public function respond(Match $match, TemplateInterface $template, $message = "")
{
if ($match->isMatch()) {
return;
}
$this->formatter->setMatch($match);
$message = ($message) ? $message : $this->formatter->getMessage($template);
throw new Exception($message);
}
}
?>
Using your own formatters and responders
You can replace the default formatter and responder via the static methods Leo::setFormatter
and Leo::setResponder
.
Example
LeoHttpFoundation is a simple plugin that extends Leo to simplify testing HttpFoundation based applications. Browse the source for some examples on extending Leo.