90% of 680OPs |
95% of 99Lines |
70% of 91Branches |
54% of 50Paths |
# | |
---|---|
1 |
<?php |
2 |
|
3 |
namespace mageekguy\atoum\php; |
4 |
|
5 |
use |
6 |
mageekguy\atoum, |
7 |
mageekguy\atoum\exceptions |
8 |
; |
9 |
|
10 |
class mocker |
11 |
{ |
12 |
protected $defaultNamespace = ''; |
13 |
protected $reflectedFunctionFactory = null; |
14 |
|
15 |
protected static $adapter = null; |
16 |
|
17 |
public function __construct($defaultNamespace = '')100% |
18 |
{ |
19 |
$this |
20 |
->setDefaultNamespace($defaultNamespace) |
21 |
->setReflectedFunctionFactory() |
22 |
; |
23 |
} |
24 |
|
25 |
public function __get($functionName)100% |
26 |
{ |
27 |
return static::$adapter->{$this->generateIfNotExists($functionName)}; |
28 |
} |
29 |
|
30 |
public function __set($functionName, $mixed)100% |
31 |
{ |
32 |
static::$adapter->{$this->generateIfNotExists($functionName)} = $mixed; |
33 |
|
34 |
return $this; |
35 |
} |
36 |
|
37 |
public function __isset($functionName)100% |
38 |
{ |
39 |
return $this->functionExists($this->getFqdn($functionName)); |
40 |
} |
41 |
|
42 |
public function __unset($functionName)100% |
43 |
{ |
44 |
$this->setDefaultBehavior($this->getFqdn($functionName)); |
45 |
} |
46 |
|
47 |
public function setDefaultNamespace($namespace)100% |
48 |
{ |
49 |
$this->defaultNamespace = trim($namespace, '\\'); |
50 |
|
51 |
if ($this->defaultNamespace !== '') |
52 |
{ |
53 |
$this->defaultNamespace .= '\\'; |
54 |
} |
55 |
|
56 |
return $this; |
57 |
} |
58 |
|
59 |
public function getDefaultNamespace()100% |
60 |
{ |
61 |
return $this->defaultNamespace; |
62 |
} |
63 |
|
64 |
public function setReflectedFunctionFactory(\closure $factory = null)100% |
65 |
{ |
66 |
$this->reflectedFunctionFactory = $factory ?: function($functionName) { return new \reflectionFunction($functionName); }; |
67 |
|
68 |
return $this; |
69 |
} |
70 |
|
71 |
public function useClassNamespace($className)100% |
72 |
{ |
73 |
return $this->setDefaultNamespace(substr($className, 0, strrpos($className, '\\'))); |
74 |
} |
75 |
|
76 |
public function generate($functionName)100% |
77 |
{ |
78 |
$fqdn = $this->getFqdn($functionName); |
79 |
|
80 |
if ($this->functionExists($fqdn) === false) |
81 |
{ |
82 |
if (function_exists($fqdn) === true) |
83 |
{ |
84 |
throw new exceptions\logic\invalidArgument('Function \'' . $fqdn . '\' already exists'); |
85 |
} |
86 |
|
87 |
$lastAntislash = strrpos($fqdn, '\\'); |
88 |
$namespace = substr($fqdn, 0, $lastAntislash); |
89 |
$function = substr($fqdn, $lastAntislash + 1); |
90 |
$reflectedFunction = $this->buildReflectedFunction($function); |
91 |
|
92 |
static::defineMockedFunction($namespace, get_class($this), $function, $reflectedFunction); |
93 |
} |
94 |
|
95 |
return $this->setDefaultBehavior($fqdn); |
96 |
} |
97 |
|
98 |
public function resetCalls($functionName = null)100% |
99 |
{ |
100 |
static::$adapter->resetCalls($this->getFqdn($functionName)); |
101 |
|
102 |
return $this; |
103 |
} |
104 |
|
105 |
public static function setAdapter(atoum\test\adapter $adapter = null)100% |
106 |
{ |
107 |
static::$adapter = $adapter ?: new atoum\php\mocker\adapter(); |
108 |
} |
109 |
|
110 |
public static function getAdapter()100% |
111 |
{ |
112 |
return static::$adapter; |
113 |
} |
114 |
|
115 |
protected function getFqdn($functionName)100% |
116 |
{ |
117 |
return $this->defaultNamespace . $functionName; |
118 |
} |
119 |
|
120 |
protected function generateIfNotExists($functionName)100% |
121 |
{ |
122 |
if (isset($this->{$functionName}) === false) |
123 |
{ |
124 |
$this->generate($functionName); |
125 |
} |
126 |
|
127 |
return $this->getFqdn($functionName); |
128 |
} |
129 |
|
130 |
protected function setDefaultBehavior($fqdn, \reflectionFunction $reflectedFunction = null)100% |
131 |
{ |
132 |
$function = substr($fqdn, strrpos($fqdn, '\\') + 1); |
133 |
|
134 |
if ($reflectedFunction === null) |
135 |
{ |
136 |
$reflectedFunction = $this->buildReflectedFunction($function); |
137 |
} |
138 |
|
139 |
if ($reflectedFunction === null) |
140 |
{ |
141 |
$closure = function() { return null; }; |
142 |
} |
143 |
else |
144 |
{ |
145 |
$closure = eval('return function(' . static::getParametersSignature($reflectedFunction) . ') { return call_user_func_array(\'\\' . $function . '\', ' . static::getParameters($reflectedFunction) . '); };'); |
146 |
} |
147 |
|
148 |
static::$adapter->{$fqdn}->setClosure($closure); |
149 |
|
150 |
return $this; |
151 |
} |
152 |
|
153 |
protected function functionExists($fqdn)100% |
154 |
{ |
155 |
return (isset(static::$adapter->{$fqdn}) === true); |
156 |
} |
157 |
|
158 |
protected static function getParametersSignature(\reflectionFunction $function)85% |
159 |
{ |
160 |
$parameters = array(); |
161 |
|
162 |
foreach (self::filterParameters($function) as $parameter) |
163 |
{ |
164 |
$parameterCode = self::getParameterType($parameter) . ($parameter->isPassedByReference() == false ? '' : '& ') . '$' . $parameter->getName(); |
165 |
|
166 |
switch (true) |
167 |
{ |
168 |
case $parameter->isDefaultValueAvailable(): |
169 |
$parameterCode .= ' = ' . var_export($parameter->getDefaultValue(), true); |
170 |
break; |
171 |
|
172 |
case $parameter->isOptional(): |
173 |
$parameterCode .= ' = null'; |
174 |
} |
175 |
|
176 |
$parameters[] = $parameterCode; |
177 |
} |
178 |
|
179 |
return join(', ', $parameters); |
180 |
} |
181 |
|
182 |
protected static function getParameters(\reflectionFunction $function)100% |
183 |
{ |
184 |
$parameters = array(); |
185 |
|
186 |
foreach (self::filterParameters($function) as $parameter) |
187 |
{ |
188 |
$parameters[] = ($parameter->isPassedByReference() === false ? '' : '& ') . '$' . $parameter->getName(); |
189 |
} |
190 |
|
191 |
return 'array(' . join(',', $parameters) . ')'; |
192 |
} |
193 |
|
194 |
protected static function getParameterType(\reflectionParameter $parameter)70% |
195 |
{ |
196 |
switch (true) |
197 |
{ |
198 |
case $parameter->isArray(): |
199 |
return 'array '; |
200 |
|
201 |
case method_exists($parameter, 'isCallable') && $parameter->isCallable(): |
202 |
return 'callable '; |
203 |
|
204 |
case ($class = $parameter->getClass()): |
205 |
return '\\' . $class->getName() . ' '; |
206 |
|
207 |
default: |
208 |
return ''; |
209 |
} |
210 |
} |
211 |
|
212 |
protected static function defineMockedFunction($namespace, $class, $function, \reflectionFunction $reflectedFunction = null)100% |
213 |
{ |
214 |
eval(sprintf( |
215 |
'namespace %s { function %s(%s) { return \\%s::getAdapter()->invoke(__FUNCTION__, %s); } }', |
216 |
$namespace, |
217 |
$function, |
218 |
$reflectedFunction ? static::getParametersSignature($reflectedFunction) : '', |
219 |
$class, |
220 |
$reflectedFunction ? static::getParameters($reflectedFunction) : 'func_get_args()' |
221 |
)); |
222 |
} |
223 |
|
224 |
private function buildReflectedFunction($function)100% |
225 |
{ |
226 |
$reflectedFunction = null; |
227 |
|
228 |
try |
229 |
{ |
230 |
$reflectedFunction = call_user_func_array($this->reflectedFunctionFactory, array($function)); |
231 |
} |
232 |
catch (\exception $exception) {} |
233 |
|
234 |
return $reflectedFunction; |
235 |
} |
236 |
|
237 |
private static function filterParameters(\reflectionFunction $function)100% |
238 |
{ |
239 |
return array_filter($function->getParameters(), function($parameter) { return ($parameter->getName() != '...'); }); |
240 |
} |
241 |
} |
242 |
|
243 |
mocker::setAdapter(); |