beberlei/assert

适用于业务模型输入验证的轻量级断言库。

v3.3.2 2021-12-16 21:41 UTC

README

Build Status Code Coverage GitHub issues

PHP Version Stable Version

Total Downloads Monthly Downloads Daily Downloads

A simple php library which contains assertions and guard methods for input validation (not filtering!) in business-model, libraries and application low-level code. The library can be used to implement pre-/post-conditions on input data.

Idea is to reduce the amount of code for implementing assertions in your model and also simplify the code paths to implement assertions. When assertions fail, an exception is thrown, removing the necessity for if-clauses in your code.

The library is not using Symfony or Zend Validators for a reason: The checks have to be low-level, fast, non-object-oriented code to be used everywhere necessary. Using any of the two libraries requires instantiation of several objects, using a locale component, translations, you name it. Its too much bloat.

安装

使用 Composer

composer require beberlei/assert

示例用法

<?php
use Assert\Assertion;

function duplicateFile($file, $times)
{
    Assertion::file($file);
    Assertion::digit($times);

    for ($i = 0; $i < $times; $i++) {
        copy($file, $file . $i);
    }
}

实时使用 Azure Blob Storage

<?php
public function putBlob($containerName = '', $blobName = '', $localFileName = '', $metadata = array(), $leaseId = null, $additionalHeaders = array())
{
    Assertion::notEmpty($containerName, 'Container name is not specified');
    self::assertValidContainerName($containerName);
    Assertion::notEmpty($blobName, 'Blob name is not specified.');
    Assertion::notEmpty($localFileName, 'Local file name is not specified.');
    Assertion::file($localFileName, 'Local file name is not specified.');
    self::assertValidRootContainerBlobName($containerName, $blobName);

    // Check file size
    if (filesize($localFileName) >= self::MAX_BLOB_SIZE) {
        return $this->putLargeBlob($containerName, $blobName, $localFileName, $metadata, $leaseId, $additionalHeaders);
    }

    // Put the data to Windows Azure Storage
    return $this->putBlobData($containerName, $blobName, file_get_contents($localFileName), $metadata, $leaseId, $additionalHeaders);
}

NullOr helper

A helper method (Assertion::nullOr*) is provided to check if a value is null OR holds for the assertion

<?php
Assertion::nullOrMax(null, 42); // success
Assertion::nullOrMax(1, 42);    // success
Assertion::nullOrMax(1337, 42); // exception

All helper

The Assertion::all* method checks if all provided values hold for the assertion. It will throw an exception of the assertion does not hold for one of the values

<?php
Assertion::allIsInstanceOf(array(new \stdClass, new \stdClass), 'stdClass'); // success
Assertion::allIsInstanceOf(array(new \stdClass, new \stdClass), 'PDO');      // exception

Assert::that() Chaining

Using the static API on values is very verbose when checking values against multiple assertions. 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
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 the "nullOr" or "all" helper respectively.

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, they collect all errors and only trigger the exception when the method verifyNow() is called on the Assert\SoftAssertion object.

<?php
Assert::lazy()
    ->that(10, 'foo')->string()
    ->that(null, 'bar')->notEmpty()
    ->that('string', 'baz')->isArray()
    ->verifyNow();

The method that($value, $propertyPath) requires a property path (name), so that you know how to differentiate the errors afterwards.

On failure verifyNow() will throw an exception Assert\\LazyAssertionException with a combined message

The following 3 assertions failed:
1) foo: Value "10" expected to be string, type integer given.
2) bar: Value "<NULL>" is empty, but non empty value was expected.
3) baz: Value "string" is not an array.

You can also retrieve all the AssertionFailedExceptions by calling getErrorExceptions(). This can be useful for example to build a failure response for the user.

For those looking to capture multiple errors on a single value when using a lazy assertion chain, you may follow your call to that with tryAll to run all assertions against the value, and capture all of the resulting failed assertion error messages. Here's an example

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

以上展示了如何使用此功能来精细调整报告失败的行为,但要使捕获所有失败更加容易,您也可以在以下断言之前调用tryAll。这有助于减少方法调用,并且具有与上面相同的行为。

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

功能构造函数

以下函数作为Assert静态构造函数的别名存在

  • Assert\that()
  • Assert\thatAll()
  • Assert\thatNullOr()
  • Assert\lazy()

使用功能或静态构造函数完全是个人喜好。

注意:功能构造函数与Assertion扩展不兼容。然而,可以简单地以指向扩展类的方式重新创建这些函数。

断言列表

<?php
use Assert\Assertion;

Assertion::alnum(mixed $value);
Assertion::base64(string $value);
Assertion::between(mixed $value, mixed $lowerLimit, mixed $upperLimit);
Assertion::betweenExclusive(mixed $value, mixed $lowerLimit, mixed $upperLimit);
Assertion::betweenLength(mixed $value, int $minLength, int $maxLength);
Assertion::boolean(mixed $value);
Assertion::choice(mixed $value, array $choices);
Assertion::choicesNotEmpty(array $values, array $choices);
Assertion::classExists(mixed $value);
Assertion::contains(mixed $string, string $needle);
Assertion::count(array|Countable|ResourceBundle|SimpleXMLElement $countable, int $count);
Assertion::date(string $value, string $format);
Assertion::defined(mixed $constant);
Assertion::digit(mixed $value);
Assertion::directory(string $value);
Assertion::e164(string $value);
Assertion::email(mixed $value);
Assertion::endsWith(mixed $string, string $needle);
Assertion::eq(mixed $value, mixed $value2);
Assertion::eqArraySubset(mixed $value, mixed $value2);
Assertion::extensionLoaded(mixed $value);
Assertion::extensionVersion(string $extension, string $operator, mixed $version);
Assertion::false(mixed $value);
Assertion::file(string $value);
Assertion::float(mixed $value);
Assertion::greaterOrEqualThan(mixed $value, mixed $limit);
Assertion::greaterThan(mixed $value, mixed $limit);
Assertion::implementsInterface(mixed $class, string $interfaceName);
Assertion::inArray(mixed $value, array $choices);
Assertion::integer(mixed $value);
Assertion::integerish(mixed $value);
Assertion::interfaceExists(mixed $value);
Assertion::ip(string $value, int $flag = null);
Assertion::ipv4(string $value, int $flag = null);
Assertion::ipv6(string $value, int $flag = null);
Assertion::isArray(mixed $value);
Assertion::isArrayAccessible(mixed $value);
Assertion::isCallable(mixed $value);
Assertion::isCountable(array|Countable|ResourceBundle|SimpleXMLElement $value);
Assertion::isInstanceOf(mixed $value, string $className);
Assertion::isJsonString(mixed $value);
Assertion::isObject(mixed $value);
Assertion::isResource(mixed $value);
Assertion::isTraversable(mixed $value);
Assertion::keyExists(mixed $value, string|int $key);
Assertion::keyIsset(mixed $value, string|int $key);
Assertion::keyNotExists(mixed $value, string|int $key);
Assertion::length(mixed $value, int $length);
Assertion::lessOrEqualThan(mixed $value, mixed $limit);
Assertion::lessThan(mixed $value, mixed $limit);
Assertion::max(mixed $value, mixed $maxValue);
Assertion::maxCount(array|Countable|ResourceBundle|SimpleXMLElement $countable, int $count);
Assertion::maxLength(mixed $value, int $maxLength);
Assertion::methodExists(string $value, mixed $object);
Assertion::min(mixed $value, mixed $minValue);
Assertion::minCount(array|Countable|ResourceBundle|SimpleXMLElement $countable, int $count);
Assertion::minLength(mixed $value, int $minLength);
Assertion::noContent(mixed $value);
Assertion::notBlank(mixed $value);
Assertion::notContains(mixed $string, string $needle);
Assertion::notEmpty(mixed $value);
Assertion::notEmptyKey(mixed $value, string|int $key);
Assertion::notEq(mixed $value1, mixed $value2);
Assertion::notInArray(mixed $value, array $choices);
Assertion::notIsInstanceOf(mixed $value, string $className);
Assertion::notNull(mixed $value);
Assertion::notRegex(mixed $value, string $pattern);
Assertion::notSame(mixed $value1, mixed $value2);
Assertion::null(mixed $value);
Assertion::numeric(mixed $value);
Assertion::objectOrClass(mixed $value);
Assertion::phpVersion(string $operator, mixed $version);
Assertion::propertiesExist(mixed $value, array $properties);
Assertion::propertyExists(mixed $value, string $property);
Assertion::range(mixed $value, mixed $minValue, mixed $maxValue);
Assertion::readable(string $value);
Assertion::regex(mixed $value, string $pattern);
Assertion::same(mixed $value, mixed $value2);
Assertion::satisfy(mixed $value, callable $callback);
Assertion::scalar(mixed $value);
Assertion::startsWith(mixed $string, string $needle);
Assertion::string(mixed $value);
Assertion::subclassOf(mixed $value, string $className);
Assertion::true(mixed $value);
Assertion::uniqueValues(array $values);
Assertion::url(mixed $value);
Assertion::uuid(string $value);
Assertion::version(string $version1, string $operator, string $version2);
Assertion::writeable(string $value);

记住:当需要配置参数时,它总是传递在值之后。值总是第一个参数。

异常与错误处理

如果任何断言失败,将抛出Assert\AssertionFailedException。您可以将一个名为$message的参数传递给任何断言来控制异常消息。每个异常都包含默认消息和默认消息代码。

<?php
use Assert\Assertion;
use Assert\AssertionFailedException;

try {
    Assertion::integer($value, "The pressure of gas is measured in integers.");
} catch(AssertionFailedException $e) {
    // error handling
    $e->getValue(); // the value that caused the failure
    $e->getConstraints(); // the additional constraints of the assertion.
}

Assert\AssertionFailedException只是一个接口,默认实现是Assert\InvalidArgumentException,它扩展了SPL的InvalidArgumentException。您可以在包级别上更改使用的异常。

自定义异常消息

您可以将回调作为消息参数传递,这样只有在断言失败时,您才能构建自己的消息,而不是在每次运行测试时。

回调将提供一个参数数组,这些参数用于断言。

由于某些断言调用其他断言,因此您的回调需要检查数组以确定哪个断言失败。

该数组将包含一个名为::assertion的键,它指示哪个断言失败。

回调应返回用作异常消息的字符串。

您自己的断言类

为了保护您的库免受Assert中可能存在的错误、误解或BC中断的影响,您应该引入基于库/项目的断言子类,在那里您可以覆盖抛出的异常。

此外,您还可以覆盖Assert\Assertion::stringify()方法,以在错误处理期间提供自己对类型的自定义解释。

<?php
namespace MyProject;

use Assert\Assertion as BaseAssertion;

class Assertion extends BaseAssertion
{
    protected static $exceptionClass = 'MyProject\AssertionFailedException';
}

从V2.9.2版本开始,延迟断言现在可以访问您自己的断言类中存在的任何其他断言。

贡献

有关详细信息,请参阅CONTRIBUTING