Skip to content

Commit

Permalink
Use static factory methods instead of functions in the Assert namespa…
Browse files Browse the repository at this point in the history
…ce (#184)

* Add chaining builder factory as a static class

The namespaced methods can not be autoloaded and make customizing
assertion and/or exception classes impossible.

* Replace references to Assert functions with methods in the Assert class

* Allow LazyAssertion to use an alternative exception class

* Allow an alternative lazy assertion exception class to be set in a overridden Assert class

* Allow AssertionChain to use an alternative Assertion class

* Allow an alternative Assertion class to be set in a overridden Assert class

* Add tests for custom assertion and exception classes

* Ignore AssertionChain::setAssertionClassName when generating docs

* Use old setExpectedException method to support builds on PHP < 5.6

* Replace ClassName::class by the FQN in a string literal to support PHP < 5.5

* Convert arrays to traditional syntax to support PHP < 5.4

* Remove class member access on instantiation to support PHP < 5.4

* Reinstate tests for deprecated assertion chain functions

* Adjust documentation to reflect both the old and new fluent interfaces

* Add psr-4 dev autoload root so test helper classes will be autoloaded

* Throw separate exception for non-string class name argument

* Move CustomLazyAssertionException to its own file

* Add tests for LazyAssertion::setExceptionClass

* Move CustomAssertion class to its own file

* Throw separate exception for non-string class name argument

* Add tests for AssertionChain::setAssertionClassName

* Add tests for custom lazy assertion exceptions in try all situations

* Use traditional array syntax

* Update AssertionChainTest.php

Use PHP5.3 compatible array() syntax

* Use traditional array syntax
  • Loading branch information
rquadling committed Nov 14, 2016
2 parents ef8414f + acc8e6d commit a67d496
Show file tree
Hide file tree
Showing 14 changed files with 558 additions and 39 deletions.
33 changes: 21 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,34 +82,39 @@ Assertion::allIsInstanceOf(array(new \stdClass, new \stdClass), 'stdClass'); //
Assertion::allIsInstanceOf(array(new \stdClass, new \stdClass), 'PDO'); // exception
```

### \Assert\that() Chaining
### Assert::that() Chaining

Using the static API on values is very verbose when checking values against multiple assertions.
Starting with 2.0 of Assert there is a much nicer fluent API for assertions, starting
with ``\Assert\that($value)`` and then receiving the assertions you want to call
Starting with 2.6.7 of Assert the `Assert` class provides a much nicer fluent API for assertions, starting
with `Assert::that($value)` and then receiving the assertions you want to call
on the fluent interface. You only have to specify the `$value` once.

```php
<?php
\Assert\that($value)->notEmpty()->integer();
\Assert\that($value)->nullOr()->string()->startsWith("Foo");
\Assert\that($values)->all()->float();
Assert::that($value)->notEmpty()->integer();
Assert::that($value)->nullOr()->string()->startsWith("Foo");
Assert::that($values)->all()->float();
```

There are also two shortcut function ``\Assert\thatNullOr()`` and ``\Assert\thatAll()`` enabling
There are also two shortcut function `Assert::thatNullOr()` and `Assert::thatAll()` enabling
the "nullOr" or "all" helper respectively.

### \Assert\that()
Previously (starting with version 2.0 of Assert) this fluent interface was provided by the functions
`\Assert\that()`, `\Assert\thatNullOr()` and `\Assert\thatAll()` respectively. These functions have
been deprecated in favor of the static methods described above and will be removed in version 3.0 of Assert.

### Lazy Assertions

There are many cases in web development, especially when involving forms, you want to collect several errors
instead of aborting directly on the first error. This is what lazy assertions are for. Their API
works exactly like the fluent ``\Assert\that()`` API, but instead of throwing an Exception directly,
works exactly like the fluent ``Assert::that()`` API, but instead of throwing an Exception directly,
they collect all errors and only trigger the exception when the method
``verifyNow()`` is called on the ``Assert\SoftAssertion`` object.

```php
<?php
\Assert\lazy()
Assert::lazy()
->that(10, 'foo')->string()
->that(null, 'bar')->notEmpty()
->that('string', 'baz')->isArray()
Expand All @@ -135,23 +140,27 @@ you may follow your call to ``that`` with ``tryAll`` to run all assertions again
capture all of the resulting failed assertion error messages. Here's an example:

```php
\Assert\lazy()
Assert::lazy()
->that(10, 'foo')->tryAll()->integer()->between(5, 15)
->that(null, 'foo')->tryAll()->notEmpty()->string()
->verifyAll();
->verifyNow();
```

The above shows how to use this functionality to finely tune the behavior of reporting failures, but to make
catching all failures even easier, you may also call ``tryAll`` before making any assertions like below. This
helps to reduce method calls, and has the same behavior as above.

```php
\Assert\lazy()->tryAll()
Assert::lazy()->tryAll()
->that(10, 'foo')->integer()->between(5, 15)
->that(null, 'foo')->notEmpty()->string()
->verifyNow();
```

### \Assert\lazy()
As with the `Assert` chaining methods lazy assertions were initiated by the function `\Assert\lazy()` that
has been deprecated since version 2.6.7. As of the release of version 3.0 this function will no longer be available.

## List of assertions

```php
Expand Down
2 changes: 1 addition & 1 deletion bin/generate_method_docs.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ function (ReflectionMethod $reflMethod) {
return false;
}

if (in_array($reflMethod->getName(), array('__construct', '__call'))) {
if (in_array($reflMethod->getName(), array('__construct', '__call', 'setAssertionClassName'))) {
return false;
}

Expand Down
5 changes: 5 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
"lib/Assert/functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Assert\\Tests\\": "tests/Assert/Tests"
}
},
"scripts": {
"assert:generate-docs": "php bin/generate_method_docs.php",
"assert:cs-lint": "php-cs-fixer fix --diff --verbose --dry-run",
Expand Down
95 changes: 95 additions & 0 deletions lib/Assert/Assert.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
/**
* Assert
*
* LICENSE
*
* This source file is subject to the MIT license that is bundled
* with this package in the file LICENSE.txt.
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to kontakt@beberlei.de so I can send you a copy immediately.
*/

namespace Assert;

/**
* AssertionChain factory
*/
abstract class Assert
{
/** @var string */
protected static $lazyAssertionExceptionClass = 'Assert\LazyAssertionException';

/** @var string */
protected static $assertionClass = 'Assert\Assertion';

/**
* Start validation on a value, returns {@link AssertionChain}
*
* The invocation of this method starts an assertion chain
* that is happening on the passed value.
*
* @example
*
* Assert::that($value)->notEmpty()->integer();
* Assert::that($value)->nullOr()->string()->startsWith("Foo");
*
* The assertion chain can be stateful, that means be careful when you reuse
* it. You should never pass around the chain.
*
* @param mixed $value
* @param string $defaultMessage
* @param string $defaultPropertyPath
*
* @return \Assert\AssertionChain
*/
public static function that($value, $defaultMessage = null, $defaultPropertyPath = null)
{
$assertionChain = new AssertionChain($value, $defaultMessage, $defaultPropertyPath);
return $assertionChain
->setAssertionClassName(static::$assertionClass)
;
}

/**
* Start validation on a set of values, returns {@link AssertionChain}
*
* @param mixed $values
* @param string $defaultMessage
* @param string $defaultPropertyPath
*
* @return \Assert\AssertionChain
*/
public static function thatAll($values, $defaultMessage = null, $defaultPropertyPath = null)
{
return static::that($values, $defaultMessage, $defaultPropertyPath)->all();
}

/**
* Start validation and allow NULL, returns {@link AssertionChain}
*
* @param mixed $value
* @param string $defaultMessage
* @param string $defaultPropertyPath
*
* @return \Assert\AssertionChain
*/
public static function thatNullOr($value, $defaultMessage = null, $defaultPropertyPath = null)
{
return static::that($value, $defaultMessage, $defaultPropertyPath)->nullOr();
}

/**
* Create a lazy assertion object.
*
* @return \Assert\LazyAssertion
*/
public static function lazy()
{
$lazyAssertion = new LazyAssertion();
return $lazyAssertion
->setExceptionClass(static::$lazyAssertionExceptionClass)
;
}
}
28 changes: 25 additions & 3 deletions lib/Assert/AssertionChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

namespace Assert;

use LogicException;
use ReflectionClass;

/**
Expand Down Expand Up @@ -114,6 +115,9 @@ class AssertionChain
*/
private $all = false;

/** @var string|Assertion Class to use for assertion calls */
private $assertionClassName = 'Assert\Assertion';

public function __construct($value, $defaultMessage = null, $defaultPropertyPath = null)
{
$this->value = $value;
Expand All @@ -135,11 +139,11 @@ public function __call($methodName, $args)
return $this;
}

if (!method_exists('Assert\Assertion', $methodName)) {
if (!method_exists($this->assertionClassName, $methodName)) {
throw new \RuntimeException("Assertion '" . $methodName . "' does not exist.");
}

$reflClass = new ReflectionClass('Assert\Assertion');
$reflClass = new ReflectionClass($this->assertionClassName);
$method = $reflClass->getMethod($methodName);

array_unshift($args, $this->value);
Expand All @@ -163,7 +167,7 @@ public function __call($methodName, $args)
$methodName = 'all' . $methodName;
}

call_user_func_array(array('Assert\Assertion', $methodName), $args);
call_user_func_array(array($this->assertionClassName, $methodName), $args);

return $this;
}
Expand Down Expand Up @@ -193,4 +197,22 @@ public function nullOr()

return $this;
}

/**
* @param string $className
* @return $this
*/
public function setAssertionClassName($className)
{
if (!is_string($className)) {
throw new LogicException('Exception class name must be passed as a string');
}

if ($className !== 'Assert\Assertion' && !is_subclass_of($className, 'Assert\Assertion')) {
throw new LogicException($className . ' is not (a subclass of) Assert\Assertion');
}

$this->assertionClassName = $className;
return $this;
}
}
27 changes: 25 additions & 2 deletions lib/Assert/LazyAssertion.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace Assert;

use LogicException;

/**
* Chaining builder for lazy assertions
*
Expand Down Expand Up @@ -101,11 +103,14 @@ class LazyAssertion
private $currentChain;
private $errors = array();

/** @var string|LazyAssertionException The class to use for exceptions */
private $exceptionClass = 'Assert\LazyAssertionException';

public function that($value, $propertyPath, $defaultMessage = null)
{
$this->currentChainFailed = false;
$this->thisChainTryAll = false;
$this->currentChain = \Assert\that($value, $defaultMessage, $propertyPath);
$this->currentChain = Assert::that($value, $defaultMessage, $propertyPath);

return $this;
}
Expand Down Expand Up @@ -147,9 +152,27 @@ public function __call($method, $args)
public function verifyNow()
{
if ($this->errors) {
throw LazyAssertionException::fromErrors($this->errors);
throw call_user_func(array($this->exceptionClass, 'fromErrors'), $this->errors);
}

return true;
}

/**
* @param string $className
* @return $this
*/
public function setExceptionClass($className)
{
if (!is_string($className)) {
throw new LogicException('Exception class name must be passed as a string');
}

if ($className !== 'Assert\LazyAssertionException' && !is_subclass_of($className, 'Assert\LazyAssertionException')) {
throw new LogicException($className . ' is not (a subclass of) Assert\LazyAssertionException');
}

$this->exceptionClass = $className;
return $this;
}
}
2 changes: 1 addition & 1 deletion lib/Assert/LazyAssertionException.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static function fromErrors(array $errors)
$message .= sprintf("%d) %s: %s\n", $i++, $error->getPropertyPath(), $error->getMessage());
}

return new self($message, $errors);
return new static($message, $errors);
}

public function __construct($message, array $errors)
Expand Down
10 changes: 7 additions & 3 deletions lib/Assert/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@
* @param string $defaultPropertyPath
*
* @return \Assert\AssertionChain
* @deprecated
*/
function that($value, $defaultMessage = null, $defaultPropertyPath = null)
{
return new AssertionChain($value, $defaultMessage, $defaultPropertyPath);
return Assert::that($value, $defaultMessage, $defaultPropertyPath);
}
}

Expand All @@ -49,10 +50,11 @@ function that($value, $defaultMessage = null, $defaultPropertyPath = null)
* @param string $defaultPropertyPath
*
* @return \Assert\AssertionChain
* @deprecated
*/
function thatAll($values, $defaultMessage = null, $defaultPropertyPath = null)
{
return that($values, $defaultMessage, $defaultPropertyPath)->all();
return Assert::that($values, $defaultMessage, $defaultPropertyPath)->all();
}
}

Expand All @@ -65,10 +67,11 @@ function thatAll($values, $defaultMessage = null, $defaultPropertyPath = null)
* @param string $defaultPropertyPath
*
* @return \Assert\AssertionChain
* @deprecated
*/
function thatNullOr($value, $defaultMessage = null, $defaultPropertyPath = null)
{
return that($value, $defaultMessage, $defaultPropertyPath)->nullOr();
return Assert::that($value, $defaultMessage, $defaultPropertyPath)->nullOr();
}
}

Expand All @@ -77,6 +80,7 @@ function thatNullOr($value, $defaultMessage = null, $defaultPropertyPath = null)
* Create a lazy assertion object.
*
* @return \Assert\LazyAssertion
* @deprecated
*/
function lazy()
{
Expand Down

0 comments on commit a67d496

Please sign in to comment.