mixteer / reshi
Reshi 库,一个断言库。
Requires
- php: >=5.3.0
This package is not auto-updated.
Last update: 2024-09-28 16:31:08 UTC
README
"如果它不可能发生,请使用断言来确保它不会" -- A. Hunt 和 D. Thomas 在《实用程序员》一书中。
Reshi 是一个断言库,旨在检查你对函数输入参数施加的约束。它的理念来自于《实用程序员》第四十二章第三节中的断言编程。
你使用 Reshi 来确保你认为永远不会发生的事件在真的发生时不会造成损害。请参阅同一章节中的“死程序不会说谎”。
安装
该库可在 packagist 上找到,并通过 composer 安装。
{ "require": { "mixteer/reshi": "0.1.*" } }
概念
关于这个库的为何和如何的三个主要概念来自于第四章的《实用程序员》。
-
断言编程 - 使用断言来确保那些不应该发生的事情,以防万一它们真的发生了。
-
死程序不会说谎 - 如果断言失败,库会触发一个
E_USER_ERROR
错误,立即终止程序。你应该有一个错误处理器来记录这个错误,因为它将包含断言失败的详细信息,例如文件名和行号。 -
对异常情况使用异常 - 库在断言失败时不抛出异常,以避免有人捕获异常并对其不采取任何行动,从而使程序继续运行。是的,错误可以被转换为异常,但这假设你有很好的理由这么做。请注意,虽然断言有助于防止罕见的坏事件,但这些事件不是你的业务逻辑的一部分,所以在这里使用异常不是一个好主意。但当库方法被提供错误的参数时,它们会抛出异常,因为这属于它们的业务逻辑。
用法
使用这个库相当简单
<?php $assertThat = new Assertion; $title = "The Pragmatic Programmer"; # Fails if the title is not a string # Using an instance of Assertion $assertThat($title)->isString(); # Using a static method Assertion::assertIsString($title); # Fails if the title is null # Using an instance of Assertion $assertThat($title)->isNotNull(); # Using a static method Assertion::assertIsNotNull($title);
你很可能会在一个类中使用这个方法,并且 $this->assertThat
会暂停问题,因为 PHP 会在这个当前类中查找 assertThat
方法。
有几种方法可以解决这个问题
- 辅助方法
创建一个辅助方法,它返回一个Assertion
实例并使用它。这种方法的问题是它创建了太多的Assertion
对象。所以通常不推荐这种方法。
<?php function assertThat($param) { $assertThat = new Assertion; return $assertThat($param); } class User { protected $name; public function changeName($name) { // Make sure the name is a string and is not empty assertThat($name)->isString(); $assertThat($name)->isNotEmpty(); // !Don't this to check if a string is empty in production code } }
- 具有缓存的辅助方法
我们还可以缓存前一个函数调用的结果,并重用先前构建的对象。这要好得多,因为我们将重用相同的断言对象在多个调用之间。请注意,我们注意更改断言正在处理的参数,否则它会重用旧的一个。
<?php function assertThat($param) { static $assertion = []; if (count($assertion) > 0) { $assertThat = $assertion[0]; $assertThat->changeParameter($param); } else { $assertThat = new Assertion; $assertion[] = $assertThat($param); } return $assertThat; } class User { protected $name; public function changeName($name) { // Make sure the name is a string and is not empty assertThat($name)->isString(); $assertThat($name)->isNotEmpty(); // !Don't this to check if a string is empty in production code } }
- **实现 __call**: **
在这个方法中,我们实现了魔法方法 __call 来拦截“缺失的方法”,执行它并返回我们想要的结果。
<?php class User { protected $name = ""; protected $assertThat = null; public function __construct() { $this->assertThat = new Assertion; } public function changeName($name) { $this->assertThat($name)->isString(); $this->name = $name; } public function getName() { return $this->name; } // Without this method, the code would fail with a message such as the method User::assertThat() could not be found. public function __call($method, $args) { if (is_callable(array($this, $method))) { return call_user_func_array($this->{$method}, $args); } } }
对于重度使用领域模型的开发者来说,理想情况下__call方法应该放在层超类型上,以避免重复。
- 创建一个命名空间函数
假设您正在使用层超类型。您不需要实现__call方法,而是在层超类型类中创建一个函数,并导入它(自PHP 5.6+)。
<?php namespace Domain\Model; use Reshi\Assertion; function assertThat($param) { static $assertion = []; if (count($assertion) > 0) { $assertThat = $assertion[0]; $assertThat->changeParameter($param); } else { $assertThat = new Assertion; $assertion[] = $assertThat($param); } return $assertThat; } class LayerSupertype { protected $id; // Method common to all domain models go here }
用法如下
<?php namespace Domain\Model\Users; use function Domain\Model\assertThat; class User { protected $name = ""; public function changeName($name) { assertThat($name)->isString(); $this->name = $name; } public function getName() { return $this->name; } }
断言
这还不是断言的完整列表,但我们将在文档和代码中尽快添加更多内容。
-
assertIsTrue($param):如果
$param
为假,则停止程序执行。
方法:isTrue()。
静态示例:Assertion::assertIsTrue(true);实例示例:$assertThat(true)->isTrue(); -
assertIsFalse($param):如果
$param
为真,则停止程序执行。
方法:isFalse()。
静态示例:Assertion::assertIsFalse(false);实例示例:$assertThat(true)->isFalse(); -
assertIsInstanceOf($object, string $klass):如果
$object
不是$klass
的实例,则停止程序执行。
方法:isInstanceOf(string $klass)。
静态示例:Assertion::assertIsInstanceOf($user, 'User');实例示例:$assertThat($user)->isInstanceOf('User'); -
assertIsNotInstanceOf($object, string $klass):如果
$object
是$klass
的实例,则停止程序执行。
方法:isNotInstanceOf(string $klass)。
静态示例:Assertion::assertIsNotInstanceOf($user, 'User');实例示例:$assertThat($user)->isNotInstanceOf('User');
目前,查看
Assertion.php
文件将显示所有断言。以下是未记录的断言列表,但我们将逐步记录它们。
- assertIsType($param, string $type)
- assertIsBool($param)
- assertIsInt($param)
- assertIsFloat($param)
- assertIsString($param)
- assertIsArray($param)
- assertIsObject($param)
- assertIsResource($param)
- assertIsCallable($param)
- assertIsNull($param)
- assertIsEmpty($param)
- assertHasCount($array)
- assertEquals($operandOne, $operandTwo)
- assertIsGreaterThan($operandOne, $operandTwo)
- assertIsGreaterThanOrEqualTo($operandOne, $operandTwo)
- assertIsLessThan($operandOne, $operandTwo)
- assertIsLessThanOrEqualTo($operandOne, $operandTwo)
- assertSame($operandOne, $operandTwo)
- assertFileExists(string $file)
- assertHasSameContent(string $fileOne, string $fileTwo)
- assertEqualsFile(string $string, string $file)
- assertStartsWith(string $needle, string $string)
- assertEndsWith(string $needle, string $string)
- assertArrayContains($needle, array $haystack)
- assertArrayContainsOnly(string $type, array $haystack)
- assertArrayContainsOnlyInstancesOf(string $klass, array $haystack)
- assertArrayHasKey($key, array $haystack)
- assertObjectHasAttribute(string $attribute, object $object)
这些静态方法都有实例对应的方法(可能没有相同的“签名”),以及否定对应的方法。
添加新断言
Reshi允许您创建自己的断言。
要添加新断言,您只需实现ReshiConstraint
接口。以下是实现的示例。
- 实现
ReshiConstraint
接口
此接口需要两个方法:evaluate()
,将由静态方法assertThat
调用,以及getName()
,必须返回一个字符串,其内容是约束的名称。此名称包含在失败消息中,以便开发人员可以识别哪个断言失败。
<?php namespace MyConstraints; use Reshi\ReshiConstraint; class HasBeenSet implements ReshiConstraint { const NAME = "HAS_BEEN_SET"; public function __construct() { } public function evaluate($param) { if (isset($param)) { return true; } return false; } public function getName() { return self::NAME; } }
- ** 扩展
Assertion
类**:
您可能想扩展Assertion
类,以便您可以使用具有所有现有方法的统一断言类。
<?php namespace MyAssertions; use Reshi\Assertion; use MyConstraints\HasBeenSet; class MyAssertion extends Assertion { public static function assertHasBeenSert($param) { self::$callFromInside += 1; // This is used for backstracing - must always be there return self::assertThat($param, new HasBeenSet); } public function hasBeenSet() { self::$callFromInside += 1; return self::assertHasBeen($this->param); } public static function assertHasNotBeenSert($param) { self::$callFromInside += 1; // This is used for backstracing - must always be there return self::assertThat($param, new HasBeenSet, false); // Pass false to get the constraint to fial if its evaluation returns true } public function hasNotBeenSet() { self::$callFromInside += 1; return self::assertHasNotBeen($this->param); } }
现在,您可以使用MyAssertion
代替Assertion
,并访问您自己的断言和现有断言。
关于
要求
Reshi已在PHP 5.5上进行了测试,但所有功能应从PHP 5.3开始工作——请参阅composer.json
。欢迎在其他平台上进行测试。请向以下作者之一发送电子邮件。
错误和功能请求
所有错误和功能请求都在 GitHub 上跟踪。
贡献
请向我们发送一个 pull request。
测试
要运行测试
$ phpunit
作者
Ntwali Bashige - ntwali.bashige@gmail.com - http://twitter.com/nbashige
Armando Sudi - https://github.com/ArmandoSudi
许可证
Reshi 在 MIT
许可下发布,请参阅 LICENSE 文件。
致谢
断言灵感来自 PHPUnit。