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(); |