Skip to content

Commit e02c0e6

Browse files
committed
Merge pull request #87 from Respect/issue-85
Make routines accept instances that are callable
2 parents b50f9dc + a0a8ec4 commit e02c0e6

14 files changed

Lines changed: 340 additions & 29 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,11 @@ Respect\Rest uses a different approach to validate route parameters:
291291
})->when(function($documentId) {
292292
return is_numeric($documentId) && $documentId > 0;
293293
});
294+
// Routines can also be called using class and method names.
295+
$r3->get('/documents/*', function($documentId) {
296+
/** do something */
297+
})->when('SomeClass_name', 'someMethod_name');
298+
// You can also pass any instance that implements the __invoke() magic method to any routine.
294299
```
295300

296301
1. This will match the route only if the callback on *when* is matched.

library/Respect/Rest/Routines/AbstractRoutine.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
namespace Respect\Rest\Routines;
44

55
use InvalidArgumentException;
6-
use ReflectionFunction;
7-
use ReflectionMethod;
8-
use Respect\Rest\Routes\AbstractRoute;
96

107
/** Base class for callback routines */
118
abstract class AbstractRoutine implements Routinable
@@ -15,9 +12,17 @@ abstract class AbstractRoutine implements Routinable
1512

1613
public function __construct($callback)
1714
{
15+
if (is_string($callback) && class_exists($callback) && method_exists($callback, '__invoke'))
16+
return $this->callback = $callback;
17+
1818
if (!is_callable($callback))
1919
throw new InvalidArgumentException('Routine callback must be... guess what... callable!');
20+
2021
$this->callback = $callback;
2122
}
2223

24+
protected function getCallback()
25+
{
26+
return $this->callback;
27+
}
2328
}

library/Respect/Rest/Routines/AbstractSyncedRoutine.php

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,68 @@
55
use InvalidArgumentException;
66
use ReflectionFunction;
77
use ReflectionMethod;
8-
use Respect\Rest\Routes\AbstractRoute;
8+
use ReflectionObject;
9+
use ReflectionClass;
10+
use Closure;
11+
use Respect\Rest\Routes\AbstractRoute,
12+
Respect\Rest\Request;
913

1014
/** Base class for routines that sync parameters */
1115
abstract class AbstractSyncedRoutine extends AbstractRoutine implements ParamSynced
1216
{
13-
17+
/**
18+
* @var Reflector
19+
*/
1420
protected $reflection;
1521

22+
/**
23+
* Return parameters that can be used with the routine.
24+
*
25+
* @return array
26+
*/
1627
public function getParameters()
1728
{
18-
return $this->getReflection()->getParameters();
29+
$reflection = $this->getReflection();
30+
if (!$reflection instanceof ReflectionObject && !$reflection instanceof ReflectionClass)
31+
return $this->getReflection()->getParameters();
32+
33+
return array();
34+
}
35+
36+
/**
37+
* Executes the routine and return its result.
38+
*
39+
* @param Respect\Rest\Request $request
40+
* @param array $params
41+
* @return mixed
42+
*/
43+
public function execute(Request $request, $params)
44+
{
45+
$callback = $this->getCallback();
46+
if (is_string($callback)) {
47+
$reflection = $this->getReflection();
48+
$routineInstance = $reflection->newInstanceArgs($params);
49+
return $routineInstance();
50+
}
51+
return call_user_func_array($callback, $params);
1952
}
2053

21-
/** Returns a concrete ReflectionFunctionAbstract for this routine callback */
54+
/**
55+
* Returns a concrete ReflectionFunctionAbstract for this routine callback.
56+
*
57+
* @return Reflector
58+
*/
2259
protected function getReflection()
2360
{
24-
if (is_array($this->callback))
25-
return new ReflectionMethod($this->callback[0], $this->callback[1]);
61+
$callback = $this->getCallback();
62+
if (is_array($callback))
63+
return new ReflectionMethod($callback[0], $callback[1]);
64+
else if ($callback instanceof Closure)
65+
return new ReflectionFunction($callback);
66+
else if (is_string($callback))
67+
return new ReflectionClass($callback);
2668
else
27-
return new ReflectionFunction($this->callback);
69+
return new ReflectionObject($callback);
2870
}
2971

3072
}

library/Respect/Rest/Routines/By.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class By extends AbstractSyncedRoutine implements ProxyableBy
1010

1111
public function by(Request $request, $params)
1212
{
13-
return call_user_func_array($this->callback, $params);
13+
return $this->execute($request, $params);
1414
}
1515

1616
}

library/Respect/Rest/Routines/Through.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Through extends AbstractSyncedRoutine implements ProxyableThrough
1010

1111
public function through(Request $request, $params)
1212
{
13-
return call_user_func_array($this->callback, $params);
13+
return $this->execute($request, $params);
1414
}
1515

1616
}

library/Respect/Rest/Routines/When.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class When extends AbstractSyncedRoutine implements ProxyableWhen
1010

1111
public function when(Request $request, $params)
1212
{
13-
$valid = call_user_func_array($this->callback, $params);
13+
$valid = $this->execute($request, $params);
1414

1515
if (!$valid)
1616
header('HTTP/1.1 400');

tests/bootstrap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
$paths[] = $path;
1313
}
1414

15+
$paths[] = realpath(__DIR__.DIRECTORY_SEPARATOR.'src');
1516
natsort($paths);
1617
array_unshift($paths, dirname(__DIR__) .'/library');
1718
set_include_path(implode(PATH_SEPARATOR, array_unique($paths)));

tests/legacy/Respect/Rest/Routines/AbstractSyncedRoutineTest.php

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<?php
22
namespace Respect\Rest\Routines;
33

4+
use Stubs\Routines\ByClassWithInvoke;
5+
46
/**
57
* @covers Respect\Rest\Routines\ParamSynced
68
* @author Nick Lombard <github@jigsoft.co.za>
79
*/
8-
use \ReflectionParameterr;
9-
1010
class AbstractSyncedRoutineTest extends \PHPUnit_Framework_TestCase
1111
{
1212
/**
@@ -37,7 +37,7 @@ protected function tearDown()
3737
}
3838

3939
/**
40-
* @covers Respect\Rest\Routines\AbstractSyncedRoutine::getParameters
40+
* @covers Respect\Rest\Routines\AbstractSyncedRoutine
4141
*/
4242
public function testGetParameters()
4343
{
@@ -48,4 +48,82 @@ public function testGetParameters()
4848
$this->assertEquals('blogId', $parameters[1]->name);
4949
$this->assertInstanceOf('ReflectionParameter', $parameters[0]);
5050
}
51+
52+
/**
53+
* @covers Respect\Rest\Routines\AbstractSyncedRoutine
54+
* @covers Respect\Rest\Routines\AbstractRoutine
55+
*/
56+
public function test_getParameters_with_an_array()
57+
{
58+
$class = 'Respect\Rest\Routines\AbstractSyncedRoutine';
59+
$callback = array('DateTime', 'createFromFormat');
60+
$stub = $this->getMockBuilder($class)
61+
->setMethods(array('getCallback'))
62+
->disableOriginalConstructor()
63+
->getMock();
64+
$stub->expects($this->any())
65+
->method('getCallback')
66+
->will($this->returnValue($callback));
67+
68+
$this->assertContainsOnlyInstancesOf(
69+
$expected = 'ReflectionParameter',
70+
$result = $stub->getParameters()
71+
);
72+
$this->assertCount(
73+
$expected = 3,
74+
$result
75+
);
76+
}
77+
78+
/**
79+
* @covers Respect\Rest\Routines\AbstractSyncedRoutine
80+
* @covers Respect\Rest\Routines\AbstractRoutine
81+
*/
82+
public function test_getParameters_with_function()
83+
{
84+
$class = 'Respect\Rest\Routines\AbstractSyncedRoutine';
85+
$callback = function($name) { return 'Hello '.$name; };
86+
$stub = $this->getMockBuilder($class)
87+
->setMethods(array('getCallback'))
88+
->disableOriginalConstructor()
89+
->getMock();
90+
$stub->expects($this->any())
91+
->method('getCallback')
92+
->will($this->returnValue($callback));
93+
94+
$this->assertContainsOnlyInstancesOf(
95+
$expected = 'ReflectionParameter',
96+
$result = $stub->getParameters()
97+
);
98+
$this->assertCount(
99+
$expected = 1,
100+
$result
101+
);
102+
}
103+
104+
/**
105+
* @covers Respect\Rest\Routines\AbstractSyncedRoutine
106+
* @covers Respect\Rest\Routines\AbstractRoutine
107+
*/
108+
public function test_getParameters_with_callable_instance()
109+
{
110+
$stub = new ByClassWithInvoke;
111+
$this->assertTrue(
112+
is_callable($stub),
113+
'Callable instance does not pass the is_callable test.'
114+
);
115+
$class = 'Respect\Rest\Routines\AbstractSyncedRoutine';
116+
$callback = function($name) { return 'Hello '.$name; };
117+
$routine = $this->getMockBuilder($class)
118+
->setMethods(array('getCallback'))
119+
->disableOriginalConstructor()
120+
->getMock();
121+
$routine->expects($this->any())
122+
->method('getCallback')
123+
->will($this->returnValue($stub));
124+
$this->assertCount(
125+
$expected = 0,
126+
$result = $routine->getParameters()
127+
);
128+
}
51129
}

tests/legacy/Respect/Rest/Routines/ByTest.php

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
<?php
22
namespace Respect\Rest\Routines;
33

4-
use Respect\Rest\Request;
4+
use Respect\Rest\Request,
5+
Respect\Rest\Router;
6+
use Stubs\Routines\ByClassWithInvoke;
57

68
/**
79
* @covers Respect\Rest\Routines\By
810
* @author Nick Lombard <github@jigsoft.co.za>
911
*/
1012
class ByTest extends \PHPUnit_Framework_TestCase
1113
{
12-
/**
13-
* @var By
14-
*/
15-
protected $object;
16-
1714
/**
1815
* Sets up the fixture, for example, opens a network connection.
1916
* This method is called before a test is executed.
@@ -37,12 +34,66 @@ protected function tearDown()
3734
/**
3835
* @covers Respect\Rest\Routines\By::by
3936
*/
40-
public function testBy()
37+
public function test_by_with_an_anonymous_function()
38+
{
39+
$request = new Request();
40+
$params = array();
41+
$routine = new By(function() { return 'from by callback'; });
42+
$this->assertEquals('from by callback', $routine->by($request, $params));
43+
}
44+
45+
/**
46+
* @covers Respect\Rest\Routines\By
47+
* @covers Respect\Rest\Routines\AbstractSyncedRoutine
48+
*/
49+
public function test_by_on_a_route()
50+
{
51+
$router = new Router();
52+
$router->get('/', function() { return 'route'; })
53+
->by(function() { return 'by'; });
54+
// By does not affect the output of the route.
55+
$this->assertEquals(
56+
$expected = 'route',
57+
(string) $router->dispatch('GET', '/')
58+
);
59+
}
60+
61+
/**
62+
* @covers Respect\Rest\Routines\By
63+
* @covers Respect\Rest\Routines\AbstractSyncedRoutine
64+
*/
65+
public function test_by_on_a_route_with_classname()
66+
{
67+
$router = new Router();
68+
$router->get('/', function() { return 'route'; })
69+
->by('Stubs\Routines\ByClassWithInvoke');
70+
// By does not affect the output of the route.
71+
$this->assertEquals(
72+
$expected = 'route',
73+
(string) $router->dispatch('GET', '/')
74+
);
75+
}
76+
77+
/**
78+
* @covers Respect\Rest\Routines\By
79+
* @covers Respect\Rest\Routines\AbstractSyncedRoutine
80+
*/
81+
public function test_by_with_a_callable_class_on_a_route()
4182
{
42-
$request = @new Request();
43-
$params = array();
44-
$alias = &$this->object;
45-
$this->assertEquals('from by callback',
46-
$alias->by($request, $params));
83+
$router = new Router;
84+
$routine = new ByClassWithInvoke;
85+
$router->get('/', function() { return 'route'; })
86+
->by($routine);
87+
// By does not affect the output of the route.
88+
$this->assertEquals(
89+
$expected = 'route',
90+
(string) $router->dispatch('GET', '/')
91+
);
92+
$this->assertAttributeEquals(
93+
$expected = true,
94+
'invoked',
95+
$routine,
96+
'Routine was not invoked!'
97+
);
4798
}
4899
}

tests/legacy/Respect/Rest/Routines/WhenTest.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
<?php
22
namespace Respect\Rest\Routines {
3-
use Respect\Rest\Request;
3+
4+
use Respect\Rest\Request,
5+
Respect\Rest\Router;
6+
use Stubs\Routines\WhenAlwaysTrue;
7+
48
/**
59
* @covers Respect\Rest\Routines\When
610
* @author Nick Lombard <github@jigsoft.co.za>
@@ -53,6 +57,25 @@ public function testWhen()
5357
$this->assertFalse($alias->when($request, $params));
5458
$this->assertArrayHasKey('HTTP/1.1 400', $header);
5559
}
60+
61+
public function test_when_with_a_callable_class_within_a_route()
62+
{
63+
$router = new Router;
64+
$routine = new WhenAlwaysTrue;
65+
$router->get('/', function() { return 'route'; })
66+
->by($routine);
67+
// By does not affect the output of the route.
68+
$this->assertEquals(
69+
$expected = 'route',
70+
(string) $router->dispatch('GET', '/')
71+
);
72+
$this->assertAttributeEquals(
73+
$expected = true,
74+
'invoked',
75+
$routine,
76+
'Routine was not invoked!'
77+
);
78+
}
5679
}
5780

5881
if (!function_exists(__NAMESPACE__.'\\header')) {

0 commit comments

Comments
 (0)