-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPbcTrait.php
129 lines (101 loc) · 3.81 KB
/
PbcTrait.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?php
/**
* Trait PbcTrait
*
* This trait provides basic design by contract functionality.
* Therefor it has to rely on several things.
* First of all, these attributes have to exist in the class using it:
*
* $visibility Will contain the initial visibility of class methods
*
* $invariant Contains the class invariant
* Example:
* private $invariant = array(
* '$this->test1 === "test1"'
* );
*
* $preconditions Contains the methods's preconditions
* Example:
* protected $preconditions = array(
* 'methodA' => array(
* '$arg_0 === $arg_1',
* '$arg_1 === "asdf"'
* ));
*
* $postconditions Contains the methods's postconditions similar to the precondition syntax
*
* As you might have already figured out, we rely heavily on the __call() method.
* For this to work all your methods have to be declared private and you might have to implement a workaround
* if you want to use __call() yourself.
*
* When specifiying conditions you have to make sure to use argument names based on their argument number
* and not their initial name (e.g. $arg_0 instead of the first aguments name). This is some behaviour of __call()'s
* $args I could not work around.
*/
trait PbcTrait
{
/**
* @param $name
* @param $args
* @return mixed
* @throws InvalidArgumentException
* @throws Exception
*/
function __call($name, $args)
{
if (isset($this->visibility[$name])) {
$calledClass = get_called_class();
// Now check what kind of visibility we would have
switch ($this->visibility[$name]) {
case "protected" :
if (!is_subclass_of($calledClass, __CLASS__)) {
throw new Exception($name . ' is of protected visibility. You are not allowed to access it in this context');
}
break;
case "public" :
break;
default :
throw new Exception($name . ' is of private visibility. You are not allowed to access it in this context');
break;
}
}
elseif (method_exists($this, $name) && in_array($name, get_class_methods($this))) {
return $this->$name();
} else {
throw new \InvalidArgumentException;
}
extract($args, EXTR_PREFIX_ALL, 'arg');
// Check the invariant up front
foreach ($this->invariant as $invariant) {
$string = 'return (' . $invariant . ')? true : false;';
if (eval($string) === false) {
throw new Exception('Invariant broken on entry of ' . $name);
}
}
// Check all the preconditions
foreach(@$this->preconditions[$name] as $precondition) {
$string = 'return (' . $precondition . ')? true : false;';
if (eval($string) === false) {
throw new Exception('Precondition ' . $precondition . ' broken within ' . $name);
}
}
// Save for use of old
$this->old = clone $this;
// Call the actual function
$result = call_user_func_array(array($this, $name), $args);
// Check all the postconditions
foreach(@$this->postconditions[$name] as $postcondition) {
$string = 'return (' . $postcondition . ')? true : false;';
if (eval($string) === false) {
throw new Exception('Postcondition ' . $postcondition . ' broken within ' . $name);
}
}
// Check the invariant before leaving
foreach ($this->invariant as $invariant) {
$string = 'return (' . $invariant . ')? true : false;';
if (eval($string) === false) {
throw new Exception('Invariant broken on exit of ' . $name);
}
}
}
}