Skip to content

Commit 39140a1

Browse files
authored
Merge pull request #133 from bhoehl/master
adding support for optional and referenced parameters in BUILT-IN functions
2 parents bddc25a + 99efd4d commit 39140a1

File tree

2 files changed

+105
-4
lines changed

2 files changed

+105
-4
lines changed

src/AspectMock/Intercept/FunctionInjector.php

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,95 @@ class FunctionInjector
77
<?php
88
namespace {{ns}};
99
if (!function_exists('{{ns}}\{{func}}')) {
10-
function {{func}}() {
11-
if ((\$__am_res = __amock_before_func('{{ns}}','{{func}}', func_get_args())) !== __AM_CONTINUE__) {
12-
return \$__am_res;
10+
function {{func}}() {
11+
if ((\$__am_res = __amock_before_func('{{ns}}','{{func}}', func_get_args())) !== __AM_CONTINUE__) {
12+
return \$__am_res;
13+
}
14+
return call_user_func_array('{{func}}', func_get_args());
1315
}
14-
return call_user_func_array('{{func}}', func_get_args());
1516
}
17+
EOF;
18+
19+
protected $templateByRefOptional = <<<EOF
20+
<?php
21+
namespace {{ns}};
22+
if (!function_exists('{{ns}}\{{func}}')) {
23+
function {{func}}({{arguments}}) {
24+
\$args = [];
25+
switch(count(func_get_args())) {
26+
{{code}} }
27+
if ((\$__am_res = __amock_before_func('{{ns}}','{{func}}', \$args)) !== __AM_CONTINUE__) {
28+
return \$__am_res;
29+
}
30+
return call_user_func_array('{{func}}', \$args);
31+
}
1632
}
1733
EOF;
34+
1835
protected $namespace;
36+
1937
protected $function;
2038
protected $fileName;
2139

2240
function __construct($namespace, $function)
2341
{
2442
$this->namespace = $namespace;
2543
$this->function = $function;
44+
$this->placeOptionalAndReferenceFunction($namespace, $function);
2645
$this->place('ns', $this->namespace);
2746
$this->place('func', $this->function);
47+
}
48+
49+
public function getParameterDeclaration(\ReflectionParameter $parameter, $internal)
50+
{
51+
$text = (string)$parameter;
52+
if (preg_match('@Parameter\s#[0-9]+\s\[\s<(required|optional)>(.*)(\sor NULL)(.*)\s\]@', $text, $match)) {
53+
$text = $match(2).$match[4];
54+
} elseif (preg_match('@Parameter\s#[0-9]+\s\[\s<(required|optional)>\s(.*)\s\]@', $text, $match)) {
55+
$text = $match[2];
56+
} else {
57+
throw new \Exception('reflection api changed. adjust code.');
58+
}
59+
if ($internal && $parameter->isOptional()) {
60+
$text .= "=NULL";
61+
}
62+
return $text;
63+
}
2864

65+
public function placeOptionalAndReferenceFunction($namespace, $function)
66+
{
67+
$reflect = new \ReflectionFunction($function);
68+
$parameters = [];
69+
$args = '';
70+
$byRef = false;
71+
$optionals = false;
72+
$names = [];
73+
$internal = $reflect->isInternal();
74+
foreach ($reflect->getParameters() as $parameter) {
75+
$name = '$'.$parameter->getName();
76+
$newname = '$p'.$parameter->getPosition();
77+
$declaration = str_replace($name, $newname, $this->getParameterDeclaration($parameter, $internal));
78+
$name = $newname;
79+
if (!$optionals && $parameter->isOptional()) {
80+
$optionals = true;
81+
}
82+
if ($parameter->isPassedByReference()) {
83+
$name = '&'.$name;
84+
$byRef = true;
85+
}
86+
$names[] = $name;
87+
$parameters[$newname] = $declaration;
88+
}
89+
if ($byRef) {
90+
$this->template = $this->templateByRefOptional;
91+
$this->place('arguments', join(', ', $parameters));
92+
$code = '';
93+
for ($i = count($parameters); $i > 0; $i--) {
94+
$code .= " case {$i}: \$args = [" . join(', ', $names) . "]; break;\n";
95+
array_pop($names);
96+
}
97+
$this->place('code', $code);
98+
}
2999
}
30100

31101
public function save()

tests/unit/FunctionInjectorTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,20 @@ class FunctionInjectorTest extends \Codeception\TestCase\Test
1111
* @var FunctionInjector
1212
*/
1313
protected $funcInjector;
14+
/**
15+
* @var FunctionInjector
16+
*/
17+
protected $funcOptionalParameterInjector;
18+
/**
19+
* @var FunctionInjector
20+
*/
21+
protected $funcReferencedParameterInjector;
1422

1523
public function _before()
1624
{
1725
$this->funcInjector = new FunctionInjector('demo', 'strlen');
26+
$this->funcOptionalParameterInjector = new FunctionInjector('demo', 'explode');
27+
$this->funcReferencedParameterInjector = new FunctionInjector('demo', 'preg_match');
1828
test::clean();
1929
}
2030

@@ -25,6 +35,18 @@ public function testTemplate()
2535
verify($php)->contains("return call_user_func_array('strlen', func_get_args());");
2636
}
2737

38+
public function testReferencedParameterTemplate()
39+
{
40+
$php = $this->funcReferencedParameterInjector->getPHP();
41+
verify($php)->contains("function preg_match(\$p0, \$p1, &\$p2=NULL, \$p3=NULL, \$p4=NULL)");
42+
verify($php)->contains("case 5: \$args = [\$p0, \$p1, &\$p2, \$p3, \$p4]; break;");
43+
verify($php)->contains("case 4: \$args = [\$p0, \$p1, &\$p2, \$p3]; break;");
44+
verify($php)->contains("case 3: \$args = [\$p0, \$p1, &\$p2]; break;");
45+
verify($php)->contains("case 2: \$args = [\$p0, \$p1]; break;");
46+
verify($php)->contains("case 1: \$args = [\$p0]; break;");
47+
verify($php)->contains("return call_user_func_array('preg_match', \$args);");
48+
}
49+
2850
public function testSave()
2951
{
3052
$this->funcInjector->save();
@@ -82,4 +104,13 @@ public function testFailedVerification()
82104
$func->verifyNeverInvoked('strlen');
83105
}
84106

107+
public function testReferencedParameter()
108+
{
109+
$func = test::func('\demo', 'preg_match', 10);
110+
expect(preg_match('@[0-9]+@', '1234', $match))->equals(10);
111+
test::clean();
112+
expect(preg_match('@[0-9]+@', '1234#', $match))->equals(1);
113+
expect($match[0])->equals('1234');
114+
}
115+
85116
}

0 commit comments

Comments
 (0)