Completed
Push — master ( 96dddc...8ffe3c )
by Alexander
18:25
created

ContractCheckerAspect::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
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
     * Default constructor
36
     *
37
     * @todo Remove injection of reader
38
     * @param Reader $reader Annotation reader
39
     */
40
    public function __construct(Reader $reader)
41
    {
42
        $this->reader = $reader;
43
    }
44
45
    /**
46
     * Verifies pre-condition contract for the method
47
     *
48
     * @param MethodInvocation $invocation
49
     * @Before("@execution(PhpDeal\Annotation\Verify)")
50
     *
51
     * @throws ContractViolation
52
     */
53 3
    public function preConditionContract(MethodInvocation $invocation)
54
    {
55 3
        $object = $invocation->getThis();
56 3
        $args   = $this->getMethodArguments($invocation);
57 3
        $scope  = $invocation->getMethod()->getDeclaringClass()->name;
58
59 3
        foreach ($invocation->getMethod()->getAnnotations() as $annotation) {
60 3
            if (!$annotation instanceof Contract\Verify) {
61 1
                continue;
62
            }
63
64 3
            if (!$this->isContractSatisfied($object, $scope, $args, $annotation)) {
65 3
                throw new ContractViolation($invocation, $annotation->value);
66
            };
67
        }
68 2
    }
69
70
    /**
71
     * Verifies post-condition contract for the method
72
     *
73
     * @Around("@execution(PhpDeal\Annotation\Ensure)")
74
     * @param MethodInvocation $invocation
75
     *
76
     * @throws ContractViolation
77
     * @return mixed
78
     */
79 3
    public function postConditionContract(MethodInvocation $invocation)
80
    {
81 3
        $object = $invocation->getThis();
82 3
        $args   = $this->getMethodArguments($invocation);
83 3
        $class  = $invocation->getMethod()->getDeclaringClass();
84 3
        if ($class->isCloneable()) {
85 3
            $args['__old'] = clone $object;
86
        }
87
88 3
        $result = $invocation->proceed();
89 3
        $args['__result'] = $result;
90
91 3
        foreach ($invocation->getMethod()->getAnnotations() as $annotation) {
92 3
            if (!$annotation instanceof Contract\Ensure) {
93
                continue;
94
            }
95
96 3
            if (!$this->isContractSatisfied($object, $class->name, $args, $annotation)) {
97 3
                throw new ContractViolation($invocation, $annotation->value);
98
            };
99
        }
100
101 2
        return $result;
102
    }
103
104
    /**
105
     * Verifies invariants for contract class
106
     *
107
     * @Around("@within(PhpDeal\Annotation\Invariant) && execution(public **->*(*))")
108
     * @param MethodInvocation $invocation
109
     *
110
     * @throws ContractViolation
111
     * @return mixed
112
     */
113 3
    public function invariantContract(MethodInvocation $invocation)
114
    {
115 3
        $object = $invocation->getThis();
116 3
        $args   = $this->getMethodArguments($invocation);
117 3
        $class  = $invocation->getMethod()->getDeclaringClass();
118 3
        if ($class->isCloneable()) {
119 3
            $args['__old'] = clone $object;
120
        }
121
122 3
        $result = $invocation->proceed();
123 3
        $args['__result'] = $result;
124
125
        // TODO: Do not use reader directly and pack annotation information into reflection
126 3
        foreach ($this->reader->getClassAnnotations($class) as $annotation) {
127 3
            if (!$annotation instanceof Contract\Invariant) {
128 2
                continue;
129
            }
130
131 3
            if (!$this->isContractSatisfied($object, $class->name, $args, $annotation)) {
132 3
                throw new ContractViolation($invocation, $annotation->value);
133
            };
134
        }
135
136 2
        return $result;
137
    }
138
139
    /**
140
     * Returns a result of contract verification
141
     *
142
     * @param object|string $instance Invocation instance or string for static class
143
     * @param string $scope Scope of method
144
     * @param array $args List of arguments for the method
145
     * @param Annotation $annotation Contract annotation
146
     *
147
     * @return mixed
148
     */
149 9
    private function isContractSatisfied($instance, $scope, array $args, $annotation)
150
    {
151 9
        static $invoker = null;
152 9
        if (!$invoker) {
153
            $invoker = function () {
154 9
                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...
155
156 9
                return eval('return ' . func_get_arg(1) . '; ?>');
157 1
            };
158
        }
159 9
        $instance = is_object($instance) ? $instance : null;
160
161 9
        return $invoker->bindTo($instance, $scope)->__invoke($args, $annotation->value);
162
    }
163
164
    /**
165
     * Returns an associative list of arguments for the method invocation
166
     *
167
     * @param MethodInvocation $invocation
168
     *
169
     * @return array
170
     */
171 9
    private function getMethodArguments(MethodInvocation $invocation)
172
    {
173 9
        $parameters    = $invocation->getMethod()->getParameters();
174 9
        $argumentNames = array_map(function (\ReflectionParameter $parameter) {
175 8
            return $parameter->name;
176 9
        }, $parameters);
177 9
        $parameters    = array_combine($argumentNames, $invocation->getArguments());
178
179 9
        return $parameters;
180
    }
181
}
182