Completed
Pull Request — master (#35)
by
unknown
14:05
created

AbstractContractAspect::ensureContracts()   B

Complexity

Conditions 9
Paths 32

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 9.0608

Importance

Changes 0
Metric Value
dl 0
loc 44
ccs 20
cts 22
cp 0.9091
rs 7.6604
c 0
b 0
f 0
cc 9
nc 32
nop 5
crap 9.0608
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * PHP Deal framework
6
 *
7
 * @copyright Copyright 2019, Lisachenko Alexander <[email protected]>
8
 *
9
 * This source file is subject to the license that is bundled
10
 * with this source code in the file LICENSE.
11
 */
12
13
namespace PhpDeal\Aspect;
14
15
use Doctrine\Common\Annotations\Annotation;
16
use Doctrine\Common\Annotations\Reader;
17
use DomainException;
18
use Go\Aop\Intercept\MethodInvocation;
19
use PhpDeal\Exception\ContractViolation;
20
21
abstract class AbstractContractAspect
22
{
23
    /**
24
     * @var Reader
25
     */
26
    protected $reader;
27
28
    /**
29
     * @param Reader $reader Annotation reader
30
     */
31
    public function __construct(Reader $reader)
32
    {
33
        $this->reader = $reader;
34
    }
35
36
    /**
37
     * Returns an associative list of arguments for the method invocation
38
     *
39
     * @param MethodInvocation $invocation
40
     * @return array
41
     * @throws \ReflectionException
42
     */
43 29
    protected function fetchMethodArguments(MethodInvocation $invocation): array
44
    {
45 29
        $result         = [];
46 29
        $parameters     = $invocation->getMethod()->getParameters();
47 29
        $argumentValues = $invocation->getArguments();
48
49
        // Number of arguments can be less than number of parameters because of default values
50 29
        foreach ($parameters as $parameterIndex => $reflectionParameter) {
51 29
            $hasArgumentValue = \array_key_exists($parameterIndex, $argumentValues);
52 29
            $argumentValue    = $hasArgumentValue ? $argumentValues[$parameterIndex] : null;
53 29
            if (!$hasArgumentValue && $reflectionParameter->isDefaultValueAvailable()) {
54
                $argumentValue = $reflectionParameter->getDefaultValue();
55
            }
56 29
            $result[$reflectionParameter->name] = $argumentValue;
57
        }
58
59 29
        return $result;
60
    }
61
62
    /**
63
     * Performs verification of contracts for given invocation
64
     *
65
     * @param MethodInvocation $invocation Current invocation
66
     * @param array|Annotation[] $contracts Contract annotation
67
     * @param object|string $instance Invocation instance or string for static class
68
     * @param string $scope Scope of method
69
     * @param array $args List of arguments for the method
70
     *
71
     * @throws DomainException
72
     * @throws ContractViolation
73
     */
74 29
    protected function ensureContracts(
75
        MethodInvocation $invocation,
76
        array $contracts,
77
        $instance,
78
        string $scope,
79
        array $args
80
    ): void {
81 29
        static $invoker = null;
82 29
        if (!$invoker) {
83
            $invoker = function () {
84 29
                $args = \func_get_arg(0);
85 29
                \extract($args, EXTR_OVERWRITE);
86
87 29
                return eval('return ' . \func_get_arg(1) . '; ?>');
88 2
            };
89
        }
90
91 29
        $instance     = \is_object($instance) ? $instance : null;
92 29
        $boundInvoker = $invoker->bindTo($instance, $scope);
93
94 29
        foreach ($contracts as $contract) {
95 29
            $contractExpression = $contract->value;
96
            try {
97 29
                $invocationResult = $boundInvoker->__invoke($args, $contractExpression);
98
99 28
                if ($invocationResult === false) {
100 27
                    throw new ContractViolation($invocation, $contractExpression);
101
                }
102
103
                // we accept as a result only true or null
104
                // null may be a result of assertions from beberlei/assert which passed
105 17
                if ($invocationResult !== null && $invocationResult !== true) {
106
                    $errorMessage = 'Invalid return value received from the assertion body,'
107
                        . ' only boolean or void can be returned';
108 17
                    throw new DomainException($errorMessage);
109
                }
110 28
            } catch (\Error $internalError) {
111
                // PHP-7 friendly interceptor for fatal errors
112
                throw new ContractViolation($invocation, $contractExpression, $internalError);
113 28
            } catch (\Exception $internalException) {
114 28
                throw new ContractViolation($invocation, $contractExpression, $internalException);
115
            }
116
        }
117 2
    }
118
}
119