Skip to content

Commit

Permalink
Merge pull request #8 from pdaw/master
Browse files Browse the repository at this point in the history
Integration with beberlei/assert library, resolves #3
  • Loading branch information
lisachenko committed Mar 20, 2016
2 parents 7c03232 + 48f613b commit 83ac96f
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/vendor/
composer.lock
/tests/cache/
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,23 @@ NOTE! The code in the invariant may not call any public non-static members of th
indirectly. Doing so will result in a stack overflow, as the invariant will wind up being called in an
infinitely recursive manner.

Integration with assertion library
----------

To enhance capabilities of contracts, it's possible to use [assertion library](https://github.com/beberlei/assert).
```php
/**
* Deposits fixed amount of money to the account
*
* @param float $amount
*
* @Contract\Ensure("Assert\Assertion::integer($this->balance)")
*/
public function deposit($amount)
{
$this->balance += $amount;
}
```

[More assertions](https://github.com/beberlei/assert#list-of-assertions)

3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "lisachenko/php-deal",
"description": "Design by Contract framework for PHP",
"require": {
"goaop/framework": "~1.0|~2.0@dev"
"goaop/framework": "~1.0|~2.0@dev",
"beberlei/assert": "^2.4"
},
"require-dev": {
"symfony/console": "~2.7|~3.0"
Expand Down
42 changes: 29 additions & 13 deletions src/Aspect/ContractCheckerAspect.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Go\Lang\Annotation\Before;
use PhpDeal\Annotation as Contract;
use PhpDeal\Exception\ContractViolation;
use DomainException;

/**
*/
Expand Down Expand Up @@ -60,9 +61,11 @@ public function preConditionContract(MethodInvocation $invocation)
continue;
}

if (!$this->isContractSatisfied($object, $scope, $args, $annotation)) {
throw new ContractViolation($invocation, $annotation->value);
};
try {
$this->ensureContractSatisfied($object, $scope, $args, $annotation);
} catch (DomainException $e) {
throw new ContractViolation($invocation, $annotation->value, $e->getPrevious());
}
}
}

Expand Down Expand Up @@ -92,9 +95,11 @@ public function postConditionContract(MethodInvocation $invocation)
continue;
}

if (!$this->isContractSatisfied($object, $class->name, $args, $annotation)) {
throw new ContractViolation($invocation, $annotation->value);
};
try {
$this->ensureContractSatisfied($object, $class->name, $args, $annotation);
} catch (DomainException $e) {
throw new ContractViolation($invocation, $annotation->value, $e->getPrevious());
}
}

return $result;
Expand Down Expand Up @@ -126,9 +131,11 @@ public function invariantContract(MethodInvocation $invocation)
continue;
}

if (!$this->isContractSatisfied($object, $class->name, $args, $annotation)) {
throw new ContractViolation($invocation, $annotation->value);
};
try {
$this->ensureContractSatisfied($object, $class->name, $args, $annotation);
} catch (DomainException $e) {
throw new ContractViolation($invocation, $annotation->value, $e->getPrevious());
}
}

return $result;
Expand All @@ -141,10 +148,9 @@ public function invariantContract(MethodInvocation $invocation)
* @param string $scope Scope of method
* @param array $args List of arguments for the method
* @param Annotation $annotation Contract annotation
*
* @return mixed
* @throws DomainException
*/
private function isContractSatisfied($instance, $scope, array $args, $annotation)
private function ensureContractSatisfied($instance, $scope, array $args, $annotation)
{
static $invoker = null;
if (!$invoker) {
Expand All @@ -156,7 +162,17 @@ private function isContractSatisfied($instance, $scope, array $args, $annotation
}
$instance = is_object($instance) ? $instance : null;

return $invoker->bindTo($instance, $scope)->__invoke($args, $annotation->value);
try {
$invocationResult = $invoker->bindTo($instance, $scope)->__invoke($args, $annotation->value);
} catch (\Exception $e) {
throw new DomainException("", 0, $e);
}

// we accept as a result only true or null
// null may be a result of assertions from beberlei/assert which passed
if ($invocationResult !== null && $invocationResult !== true) {
throw new DomainException();
}
}

/**
Expand Down
40 changes: 32 additions & 8 deletions tests/Functional/EnsureContractTest.php
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
<?php
/**
* PHP Deal framework
*
* @copyright Copyright 2014, Lisachenko Alexander <lisachenko.it@gmail.com>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*/

namespace PhpDeal\Functional;

use PhpDeal\Exception\ContractViolation;
use PhpDeal\Stub\EnsureStub;

class EnsureContractTest extends \PHPUnit_Framework_TestCase
{
/**
* @var EnsureStub
*/
private $stub;

public function setUp()
{
parent::setUp();
$this->stub = new EnsureStub();
}

public function tearDown()
{
unset($this->stub);
parent::tearDown();
}

public function testEnsureValid()
{
$ensureStub = new EnsureStub();
$ensureStub->increment(50);
$this->stub->increment(50);
}

/**
* @expectedException \PhpDeal\Exception\ContractViolation
*/
public function testEnsureInvalid()
{
$this->setExpectedException(ContractViolation::class);
$ensureStub = new EnsureStub();
$ensureStub->badIncrement(40);
$this->stub->badIncrement(40);
}

public function testEnsureCanHandleResult()
{
$ensureStub = new EnsureStub();
$ensureStub->returnPrivateValue();
$this->stub->returnPrivateValue();
}
}
46 changes: 36 additions & 10 deletions tests/Functional/InvariantContractTest.php
Original file line number Diff line number Diff line change
@@ -1,29 +1,55 @@
<?php
/**
* PHP Deal framework
*
* @copyright Copyright 2014, Lisachenko Alexander <lisachenko.it@gmail.com>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*/

namespace PhpDeal\Functional;

use PhpDeal\Exception\ContractViolation;
use PhpDeal\Stub\Speed;

class InvariantContractTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Speed
*/
private $stub;

public function setUp()
{
parent::setUp();
$this->stub = new Speed();
}

public function tearDown()
{
unset($this->stub);
parent::tearDown();
}

public function testInvariantValid()
{
$speed = new Speed();
$speed->accelerate(10, 30); // let's have a speed 300m/s
$this->stub->accelerate(10, 30); // let's have a speed 300m/s
}

/**
* @expectedException \PhpDeal\Exception\ContractViolation
*/
public function testInvariantViolated()
{
$this->setExpectedException(ContractViolation::class);
$speed = new Speed();
$speed->accelerate(10, 3e7); // let's have a speed 3*1e8 m/s, faster than light!
$this->stub->accelerate(10, 3e7); // let's have a speed 3*1e8 m/s, faster than light!
}

/**
* @expectedException \PhpDeal\Exception\ContractViolation
*/
public function testInvariantViolatedAfterSeveralMethods()
{
$this->setExpectedException(ContractViolation::class);
$speed = new Speed();
$speed->accelerate(10, 30); // let's have a speed 300m/s
$speed->decelerate(20, 20); // Negative speed?
$this->stub->accelerate(10, 30); // let's have a speed 300m/s
$this->stub->decelerate(20, 20); // Negative speed?
}
}
73 changes: 65 additions & 8 deletions tests/Functional/VerifyContractTest.php
Original file line number Diff line number Diff line change
@@ -1,27 +1,84 @@
<?php
/**
* PHP Deal framework
*
* @copyright Copyright 2014, Lisachenko Alexander <lisachenko.it@gmail.com>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*/

namespace PhpDeal\Functional;

use PhpDeal\Exception\ContractViolation;
use PhpDeal\Stub\VerifyStub;

class VerifyContractTest extends \PHPUnit_Framework_TestCase
{
/**
* @var VerifyStub
*/
private $stub;

public function setUp()
{
parent::setUp();
$this->stub = new VerifyStub();
}

public function tearDown()
{
unset($this->stub);
parent::tearDown();
}

public function testVerifyValid()
{
$verifyStub = new VerifyStub();
$verifyStub->testNumeric(-200);
$this->stub->testNumeric(-200);
}

/**
* @expectedException \PhpDeal\Exception\ContractViolation
*/
public function testVerifyInvalid()
{
$this->setExpectedException(ContractViolation::class);
$verifyStub = new VerifyStub();
$verifyStub->testNumeric('message');
$this->stub->testNumeric('message');
}

public function testAccessToPrivateFields()
{
$verifyStub = new VerifyStub();
$verifyStub->testAccessToPrivateField(50);
$this->stub->testAccessToPrivateField(50);
}

public function testVerifyWithAssertValid()
{
$this->stub->add(100);
}

public function providerVerifyWithAssertInvalid()
{
return [
[
'value' => ""
],
[
'value' => 5.5
],
[
'value' => null
],
[
'value' => []
]
];
}

/**
* @param mixed $value
* @dataProvider providerVerifyWithAssertInvalid
* @expectedException \PhpDeal\Exception\ContractViolation
*/
public function testVerifyWithAssertInvalid($value)
{
$this->stub->add($value);
}
}
13 changes: 13 additions & 0 deletions tests/Stub/VerifyStub.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,17 @@ public function testAccessToPrivateField($variable)
{
return;
}

/**
* Method with contract integrated with beberlei/assert library
*
* @param int $value
* @return bool
*
* @Contract\Verify("Assert\Assertion::integer($value)")
*/
public function add($value)
{
return true;
}
}

0 comments on commit 83ac96f

Please sign in to comment.