Completed
Pull Request — master (#8)
by
unknown
02:42
created

ContractCheckerAspect::getMethodArguments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
cc 1
eloc 7
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * PHP Deal framework
4
 *
5
 * @copyright Copyright 2014, Lisachenko Alexander <[email protected]>
6
 *
7
 * This source file is subject to the license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
11
namespace PhpDeal\Aspect;
12
13
use Doctrine\Common\Annotations\Annotation;
14
use Doctrine\Common\Annotations\Reader;
15
use Go\Aop\Aspect;
16
use Go\Aop\Intercept\MethodInvocation;
17
use Go\Lang\Annotation\Around;
18
use Go\Lang\Annotation\Before;
19
use PhpDeal\Annotation as Contract;
20
use PhpDeal\Exception\ContractViolation;
21
22
/**
23
 */
24
class ContractCheckerAspect implements Aspect
25
{
26
27
    /**
28
     * Annotation reader
29
     *
30
     * @var Reader|null
31
     */
32
    private $reader = null;
33
34
    /**
35
     * @var MethodInvocation
36
     */
37
    private $invocation;
38
39
    /**
40
     * Default constructor
41
     *
42
     * @param Reader $reader Annotation reader
43
     */
44
    public function __construct(Reader $reader)
45
    {
46
        $this->reader = $reader;
47
    }
48
49
    /**
50
     * Verifies pre-condition contract for the method
51
     *
52
     * @param MethodInvocation $invocation
53
     * @Before("@execution(PhpDeal\Annotation\Verify)")
54
     *
55
     * @throws ContractViolation
56
     */
57 8
    public function preConditionContract(MethodInvocation $invocation)
58
    {
59 8
        $this->invocation = $invocation;
60 8
        $object = $invocation->getThis();
61 8
        $args   = $this->getMethodArguments($invocation);
62 8
        $scope  = $invocation->getMethod()->getDeclaringClass()->name;
63
64 8
        foreach ($invocation->getMethod()->getAnnotations() as $annotation) {
65 8
            if (!$annotation instanceof Contract\Verify) {
66 1
                continue;
67
            }
68
69 8
            if (!$this->isContractSatisfied($object, $scope, $args, $annotation)) {
70 4
                throw new ContractViolation($invocation, $annotation->value);
71
            };
72
        }
73 3
    }
74
75
    /**
76
     * Verifies post-condition contract for the method
77
     *
78
     * @Around("@execution(PhpDeal\Annotation\Ensure)")
79
     * @param MethodInvocation $invocation
80
     *
81
     * @throws ContractViolation
82
     * @return mixed
83
     */
84 3
    public function postConditionContract(MethodInvocation $invocation)
85
    {
86 3
        $this->invocation = $invocation;
87 3
        $object = $invocation->getThis();
88 3
        $args   = $this->getMethodArguments($invocation);
89 3
        $class  = $invocation->getMethod()->getDeclaringClass();
90 3
        if ($class->isCloneable()) {
91 3
            $args['__old'] = clone $object;
92
        }
93
94 3
        $result = $invocation->proceed();
95 3
        $args['__result'] = $result;
96
97 3
        foreach ($invocation->getMethod()->getAnnotations() as $annotation) {
98 3
            if (!$annotation instanceof Contract\Ensure) {
99
                continue;
100
            }
101
102 3
            if (!$this->isContractSatisfied($object, $class->name, $args, $annotation)) {
103 3
                throw new ContractViolation($invocation, $annotation->value);
104
            };
105
        }
106
107 2
        return $result;
108
    }
109
110
    /**
111
     * Verifies invariants for contract class
112
     *
113
     * @Around("@within(PhpDeal\Annotation\Invariant) && execution(public **->*(*))")
114
     * @param MethodInvocation $invocation
115
     *
116
     * @throws ContractViolation
117
     * @return mixed
118
     */
119 3
    public function invariantContract(MethodInvocation $invocation)
120
    {
121 3
        $this->invocation = $invocation;
122 3
        $object = $invocation->getThis();
123 3
        $args   = $this->getMethodArguments($invocation);
124 3
        $class  = $invocation->getMethod()->getDeclaringClass();
125 3
        if ($class->isCloneable()) {
126 3
            $args['__old'] = clone $object;
127
        }
128
129 3
        $result = $invocation->proceed();
130 3
        $args['__result'] = $result;
131
132 3
        foreach ($this->reader->getClassAnnotations($class) as $annotation) {
133 3
            if (!$annotation instanceof Contract\Invariant) {
134 2
                continue;
135
            }
136
137 3
            if (!$this->isContractSatisfied($object, $class->name, $args, $annotation)) {
138 3
                throw new ContractViolation($invocation, $annotation->value);
139
            };
140
        }
141
142 2
        return $result;
143
    }
144
145
    /**
146
     * Returns a result of contract verification
147
     *
148
     * @param object|string $instance Invocation instance or string for static class
149
     * @param string $scope Scope of method
150
     * @param array $args List of arguments for the method
151
     * @param Annotation $annotation Contract annotation
152
     *
153
     * @return mixed
154
     */
155 14
    private function isContractSatisfied($instance, $scope, array $args, $annotation)
156
    {
157 14
        static $invoker = null;
158 14
        if (!$invoker) {
159
            $invoker = function () {
160 14
                extract(func_get_arg(0));
0 ignored issues
show
Bug introduced by
func_get_arg(0) cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
161
162 14
                return eval('return ' . func_get_arg(1) . '; ?>');
163 1
            };
164
        }
165 14
        $instance = is_object($instance) ? $instance : null;
166
167
        try {
168 14
            $invocationResult = $invoker->bindTo($instance, $scope)->__invoke($args, $annotation->value);
169 4
        } catch (\Exception $e) {
170 4
            throw new ContractViolation($this->invocation, $annotation->value, $e);
171
        }
172
173
        // if $invocationResult is null, $annotation->value didn't throw any exception
174
        // for example - assertion passed (and didn't return bool value)
175 10
        return $invocationResult === null || $invocationResult === true;
176
    }
177
178
    /**
179
     * Returns an associative list of arguments for the method invocation
180
     *
181
     * @param MethodInvocation $invocation
182
     *
183
     * @return array
184
     */
185 14
    private function getMethodArguments(MethodInvocation $invocation)
186
    {
187 14
        $parameters    = $invocation->getMethod()->getParameters();
188 14
        $argumentNames = array_map(function (\ReflectionParameter $parameter) {
189 13
            return $parameter->name;
190 14
        }, $parameters);
191 14
        $parameters    = array_combine($argumentNames, $invocation->getArguments());
192
193 14
        return $parameters;
194
    }
195
}
196