Skip to content

Commit db2681d

Browse files
authored
Merge pull request #10 from legionth/new-parameter-strategies
New parameter strategies
2 parents 297ba46 + 131bbeb commit db2681d

12 files changed

+374
-11
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Creating [ReactPHP HTTP Server](https://github.com/reactphp/http) but with REST
99
* [Server](#server)
1010
* [Dynamic Values](#dynamic-values)
1111
* [Default Callback](#default-callback)
12+
* [Parameter Placeholder](#parameter-placholder)
1213
* [Install](#install)
1314
* [License](#license)
1415

@@ -81,6 +82,19 @@ This method will be used if no definition can be found.
8182

8283
By default this library will respond with an `404` HTTP response.
8384

85+
### Parameter Placeholder
86+
87+
As seen in the previous chapter you can use the `:` to mark
88+
dynamic values.
89+
Instead of using this strategy, to mark dynamic parameters,
90+
this library supports additional strategies via different classes:
91+
92+
* `/to/path/:paramter` - `Legionth\React\Http\Rest\Paramaters\Label\Colon`
93+
* `/to/path/[paramter]` - `Legionth\React\Http\Rest\Paramaters\Label\CurlyBracket`
94+
* `/to/path/{paramter}` - `Legionth\React\Http\Rest\Paramaters\Label\SquareBrackets`
95+
96+
Checkout the examples for more information.
97+
8498
## Install
8599

86100
The recommended way to install this library is [through Composer](https://getcomposer.org).
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
require __DIR__ . '/../vendor/autoload.php';
4+
5+
$loop = \React\EventLoop\Factory::create();
6+
7+
$server = new \Legionth\React\Http\Rest\Server();
8+
9+
$server->get('/say/hello', function (\Psr\Http\Message\ServerRequestInterface $request, callable $next) {
10+
return new \React\Http\Response(200, array(), 'hello');
11+
});
12+
13+
$server->post('/say/{word}', function (\Psr\Http\Message\ServerRequestInterface $request, callable $next, array $arguments) {
14+
$word = $arguments['word'];
15+
16+
return new \React\Http\Response(200, array(), 'You said: ' . $word);
17+
});
18+
19+
$socket = new \React\Socket\Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);
20+
21+
$server->listen($socket, null, new \Legionth\React\Http\Rest\Paramaters\Label\CurlyBrackets());
22+
23+
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
24+
25+
$loop->run();

src/Paramaters/Label/Colon.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
namespace Legionth\React\Http\Rest\Paramaters\Label;
3+
4+
class Colon implements Strategy
5+
{
6+
const IDENTIFIER = ':';
7+
8+
public function extractParametersFromPath(array $pathAsArray, array $requestPathArray) : array
9+
{
10+
$result = array();
11+
foreach ($pathAsArray as $id => $valueName) {
12+
$position = strpos($valueName, self::IDENTIFIER);
13+
if (0 === $position) {
14+
$valueName = substr($valueName, 1);
15+
$result[$valueName] = $requestPathArray[$id];
16+
}
17+
}
18+
19+
return $result;
20+
}
21+
22+
public function getFirstIdentifier() : string
23+
{
24+
return self::IDENTIFIER;
25+
}
26+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Legionth\React\Http\Rest\Paramaters\Label;
4+
5+
class CurlyBrackets implements Strategy
6+
{
7+
8+
const PREFIX = '{';
9+
10+
const SUFFIX = '}';
11+
12+
/**
13+
* @var ExtractBetweenTwoCharacters
14+
*/
15+
private $extractor;
16+
17+
/**
18+
* @param ExtractBetweenTwoCharacters|null $extractor
19+
*/
20+
public function __construct(ExtractBetweenTwoCharacters $extractor = null)
21+
{
22+
if (null === $extractor) {
23+
$extractor = new ExtractBetweenTwoCharacters();
24+
}
25+
$this->extractor = $extractor;
26+
}
27+
28+
/**
29+
* @param array $pathAsArray
30+
* @param array $requestPathArray
31+
* @return array
32+
*/
33+
public function extractParametersFromPath(array $pathAsArray, array $requestPathArray) : array
34+
{
35+
$result = array();
36+
foreach ($pathAsArray as $id => $valueName) {
37+
$extractedName = $this->extractor->extract($valueName, self::PREFIX, self::SUFFIX);
38+
39+
if ('' !== $extractedName) {
40+
$result[$extractedName] = $requestPathArray[$id];
41+
}
42+
}
43+
44+
return $result;
45+
}
46+
47+
/**
48+
* @return string
49+
*/
50+
public function getFirstIdentifier() : string
51+
{
52+
return self::PREFIX;
53+
}
54+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Legionth\React\Http\Rest\Paramaters\Label;
4+
5+
class ExtractBetweenTwoCharacters
6+
{
7+
public function extract(string $value, string $firstCharacter, string $secondCharacter) : string
8+
{
9+
$start = strpos($value, $firstCharacter);
10+
if (false === $start || 0 !== $start) {
11+
return '';
12+
}
13+
14+
$end = strpos($value, $secondCharacter, $start + 1);
15+
if (false === $end || $end < $start) {
16+
return '';
17+
}
18+
19+
$length = $end - $start;
20+
$extractedName = substr($value, $start + 1, $length - 1);
21+
22+
return $extractedName;
23+
}
24+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace Legionth\React\Http\Rest\Paramaters\Label;
4+
5+
class SquareBrackets implements Strategy
6+
{
7+
const PREFIX = '[';
8+
9+
const SUFFIX = ']';
10+
11+
/**
12+
* @var ExtractBetweenTwoCharacters
13+
*/
14+
private $extractor;
15+
16+
/**
17+
* @param ExtractBetweenTwoCharacters|null $extractor
18+
*/
19+
public function __construct(ExtractBetweenTwoCharacters $extractor = null)
20+
{
21+
if (null === $extractor) {
22+
$extractor = new ExtractBetweenTwoCharacters();
23+
}
24+
$this->extractor = $extractor;
25+
}
26+
27+
/**
28+
* @param array $pathAsArray
29+
* @param array $requestPathArray
30+
* @return array
31+
*/
32+
public function extractParametersFromPath(array $pathAsArray, array $requestPathArray) : array
33+
{
34+
$result = array();
35+
foreach ($pathAsArray as $id => $valueName) {
36+
$extractedName = $this->extractor->extract($valueName, self::PREFIX, self::SUFFIX);
37+
38+
if ('' !== $extractedName) {
39+
$result[$extractedName] = $requestPathArray[$id];
40+
}
41+
}
42+
43+
return $result;
44+
}
45+
46+
/**
47+
* @return string
48+
*/
49+
public function getFirstIdentifier() : string
50+
{
51+
return self::PREFIX;
52+
}
53+
}

src/Paramaters/Label/Strategy.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Legionth\React\Http\Rest\Paramaters\Label;
4+
5+
interface Strategy
6+
{
7+
public function extractParametersFromPath(array $pathAsArray, array $requestPathArray) : array;
8+
9+
public function getFirstIdentifier() : string;
10+
}

src/Server.php

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,45 @@
22

33
namespace Legionth\React\Http\Rest;
44

5+
use Legionth\React\Http\Rest\Paramaters\Label\Colon;
6+
use Legionth\React\Http\Rest\Paramaters\Label\Strategy;
57
use Psr\Http\Message\RequestInterface;
68
use Psr\Http\Message\ServerRequestInterface;
79
use React\Socket\ServerInterface;
810
use RingCentral\Psr7\Response;
911

1012
class Server
1113
{
14+
15+
const DOUBLE_POINT = ':';
16+
const CURLY_BRACKETS = '{}';
17+
const SQUARE_BRACKETS = '[]';
18+
1219
/**
1320
* @var array
1421
*/
1522
private $functions;
1623

24+
/**
25+
* @var Strategy
26+
*/
27+
private $strategy;
28+
1729
/**
1830
* @param ServerInterface $socket
1931
* @param callable|null $callback
32+
* @param string $strategy
2033
*/
21-
public function listen(ServerInterface $socket, callable $callback = null)
34+
public function listen(ServerInterface $socket, callable $callback = null, Strategy $strategy = null)
2235
{
2336
$middleWareFunctions = array();
2437

38+
if ($strategy === null) {
39+
$strategy = new Colon();
40+
}
41+
42+
$this->strategy = $strategy;
43+
2544
foreach ($this->functions as $httpMethod => $pathFunctionArray) {
2645
foreach ($pathFunctionArray as $path => $function) {
2746
$middleWareFunctions[] = $this->createRestfulFunction($httpMethod, $path, $function);
@@ -99,19 +118,11 @@ private function createRestfulFunction($httpMethod, $path, $function)
99118
return $function($request, $next);
100119
}
101120

102-
if (false === strpos($path, ':')) {
121+
if (false === strpos($path, $this->strategy->getFirstIdentifier())) {
103122
return $next($request);
104123
}
105124

106-
$argument = array();
107-
108-
foreach ($pathArray as $id => $valueName) {
109-
$position = strpos($valueName, ':');
110-
if (0 === $position) {
111-
$valueName = substr($valueName, 1);
112-
$argument[$valueName] = $requestPathArray[$id];
113-
}
114-
}
125+
$argument = $this->strategy->extractParametersFromPath($pathArray, $requestPathArray);
115126

116127
return $function($request, $next, $argument);
117128
}

tests/ColonStrategyTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Tests\Legionth\React\Http\Rest;
4+
5+
6+
use Legionth\React\Http\Rest\Paramaters\Label\Colon;
7+
8+
class ColonStrategyTest extends TestCase
9+
{
10+
public function testColonWillBeExtracted()
11+
{
12+
$colonStrategy = new Colon();
13+
$pathArray = array('say', ':word');
14+
$requestArray = array('say', 'hello');
15+
16+
$result = $colonStrategy->extractParametersFromPath($pathArray, $requestArray);
17+
18+
$this->assertEquals(array('word' => 'hello'), $result);
19+
}
20+
21+
public function testNothingWillBeExtracted()
22+
{
23+
$colonStrategy = new Colon();
24+
$pathArray = array('say', 'word');
25+
$requestArray = array('say', 'hello');
26+
27+
$result = $colonStrategy->extractParametersFromPath($pathArray, $requestArray);
28+
29+
$this->assertEquals(array(), $result);
30+
}
31+
}

tests/CurlyBracketsStrategyTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace Tests\Legionth\React\Http\Rest;
4+
5+
6+
use Legionth\React\Http\Rest\Paramaters\Label\CurlyBrackets;
7+
8+
class CurlyBracketsStrategyTest extends TestCase
9+
{
10+
public function testValueWillBeExtracted()
11+
{
12+
$colonStrategy = new CurlyBrackets();
13+
$pathArray = array('say', '{word}');
14+
$requestArray = array('say', 'hello');
15+
16+
$result = $colonStrategy->extractParametersFromPath($pathArray, $requestArray);
17+
18+
$this->assertEquals(array('word' => 'hello'), $result);
19+
}
20+
21+
public function testNothingWillBeExtracted()
22+
{
23+
$colonStrategy = new CurlyBrackets();
24+
$pathArray = array('say', ':word');
25+
$requestArray = array('say', 'hello');
26+
27+
$result = $colonStrategy->extractParametersFromPath($pathArray, $requestArray);
28+
29+
$this->assertEquals(array(), $result);
30+
}
31+
32+
public function testNothingWillBeExtractedBecauseBracketIsNotComplete()
33+
{
34+
$colonStrategy = new CurlyBrackets();
35+
$pathArray = array('say', '{word');
36+
$requestArray = array('say', 'hello');
37+
38+
$result = $colonStrategy->extractParametersFromPath($pathArray, $requestArray);
39+
40+
$this->assertEquals(array(), $result);
41+
}
42+
}

0 commit comments

Comments
 (0)