Skip to content

Commit

Permalink
Merge pull request #988 from avant1/improve-custom-matchers-registration
Browse files Browse the repository at this point in the history
Improvements on custom matchers registration
  • Loading branch information
ciaranmcnulty committed Aug 29, 2016
2 parents 6dfe44b + ebcbc06 commit d754cb1
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 8 deletions.
13 changes: 13 additions & 0 deletions docs/cookbook/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,19 @@ array of extension classes:
extensions:
- PhpSpec\Symfony2Extension\Extension
Custom matchers
---------------

You may want to make custom matchers available in all specs.
Custom matchers can be registered by extension, but there is a simplier way: use the ``matchers`` setting and
provide an array of matcher classes. Each of them must implement ``PhpSpec\Matcher\Matcher`` interface:

.. code-block:: yaml
matchers:
- Acme\Matchers\ValidJsonMatcher
- Acme\Matchers\PositiveIntegerMatcher
Bootstrapping
-------------

Expand Down
2 changes: 2 additions & 0 deletions docs/cookbook/matchers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ instead of verifying output. You use the matchers prefixed by ``should`` or
matchers have aliases which you can use to make your specifications easy to
read.

Custom matchers classes can be registered in :doc:`configuration<cookbook/configuration>`.

Identity Matcher
----------------

Expand Down
1 change: 1 addition & 0 deletions features/bootstrap/ApplicationContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function setupApplication()

$this->application = new Application('2.1-dev');
$this->application->setAutoExit(false);
$this->application->setTerminalDimensions(130, 30);

$this->tester = new ApplicationTester($this->application);

Expand Down
9 changes: 9 additions & 0 deletions features/matchers/developer_uses_custom_matcher.feature
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,12 @@ Feature: Developer uses custom matcher
"""
When I run phpspec
Then the suite should pass

Scenario: Developer adds class that is not Matcher to custom matchers list
Given the config file contains:
"""
matchers:
- ArrayObject
"""
When I run phpspec
Then I should see "Custom matcher ArrayObject must implement PhpSpec\Matcher\Matcher interface, but it does not"
40 changes: 32 additions & 8 deletions src/PhpSpec/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

namespace PhpSpec\Console;

use PhpSpec\Exception\Configuration\InvalidConfigurationException;
use PhpSpec\Loader\StreamWrapper;
use PhpSpec\Matcher\Matcher;
use PhpSpec\Process\Context\JsonExecutionContext;
use PhpSpec\ServiceContainer;
use Symfony\Component\Console\Application as BaseApplication;
Expand Down Expand Up @@ -143,30 +145,52 @@ protected function loadConfigurationFile(InputInterface $input, IndexedServiceCo
}
}
elseif ('matchers' === $key && is_array($val)) {
foreach ($val as $class) {
$container->define(sprintf('matchers.%s', $class), function () use ($class) {
return new $class();
}, ['matchers']);
}
$this->registerCustomMatchers($container, $val);
}
else {
$container->setParam($key, $val);
}
}
}

private function registerCustomMatchers(IndexedServiceContainer $container, array $matchersClassnames)
{
foreach ($matchersClassnames as $class) {
$this->ensureIsValidMatcherClass($class);

$container->define(sprintf('matchers.%s', $class), function () use ($class) {
return new $class();
}, ['matchers']);
}
}

private function ensureIsValidMatcherClass($class)
{
if (!class_exists($class)) {
throw new InvalidConfigurationException(sprintf('Custom matcher %s does not exist.', $class));
}

if (!is_subclass_of($class, Matcher::class)) {
throw new InvalidConfigurationException(sprintf(
'Custom matcher %s must implement %s interface, but it does not.',
$class,
Matcher::class
));
}
}

private function loadExtension(ServiceContainer $container, $extensionClass, $config)
{
if (!class_exists($extensionClass)) {
throw new RuntimeException(sprintf('Extension class `%s` does not exist.', $extensionClass));
throw new InvalidConfigurationException(sprintf('Extension class `%s` does not exist.', $extensionClass));
}

if (!is_array($config)) {
throw new RuntimeException('Extension configuration must be an array or null.');
throw new InvalidConfigurationException('Extension configuration must be an array or null.');
}

if (!is_a($extensionClass, Extension::class, true)) {
throw new RuntimeException(sprintf('Extension class `%s` must implement Extension interface', $extensionClass));
throw new InvalidConfigurationException(sprintf('Extension class `%s` must implement Extension interface', $extensionClass));
}

(new $extensionClass)->load($container, $config);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of PhpSpec, A php toolset to drive emergent
* design by specification.
*
* (c) Marcello Duarte <marcello.duarte@gmail.com>
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace PhpSpec\Exception\Configuration;


use PhpSpec\Exception\Exception;

class InvalidConfigurationException extends Exception
{

}

0 comments on commit d754cb1

Please sign in to comment.