AbstractContractAspect   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 98
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 2

Test Coverage

Coverage 83.33%

Importance

Changes 0
Metric Value
wmc 15
lcom 0
cbo 2
dl 0
loc 98
ccs 30
cts 36
cp 0.8333
rs 10
c 0
b 0
f 0

3 Methods

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