Skip to content

Commit

Permalink
Merge pull request #9 from teresko/master
Browse files Browse the repository at this point in the history
Added new functionality for content parsers .. also a bit of docs
  • Loading branch information
teresko committed Mar 7, 2016
2 parents 4adb8e2 + 3106352 commit 15c2d3b
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 85 deletions.
39 changes: 36 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A simple abstraction for handling the HTTP request and responses. Library is mad
You can add the library to your project using composer with following command:

```sh
composer require fracture/http
composer require fracture/http
```


Expand Down Expand Up @@ -67,8 +67,41 @@ $request = $builder->create([
]);
```

The parser itself is defined as an anonymous function, which will be called with `Fracture\Http\Headers\ContentType` as the parameter and is expected to return an array of `name => value` pairs for parameters.
Also the `RequestBuilder` instance can have multiple content parsers added.

####Content parsers

A parser is defined as an anonymous function, which will be called with `Fracture\Http\Headers\ContentType` and `Fracture\Http\Request` instances as parameters and is expected to return an array of `name => value` pairs for parameters.

```
array function([ Fracture\Http\Headers\ContentType $header [, Fracture\Http\Request $request]])
```

You can also use content parsers to override `Request` attributes. For example, if you want to alter the request method, when submitting for with "magic" parameter like `<input type="hidden" name="_my_method" value="PUT" />` (which is a common approach for making more RESTful and bypass the limitations of standard webpage):

```php
array function([Fracture\Http\Headers\ContentType $header])
<?php
// -- unimportant code above --

$builder->addContentParser('*/*', function ($header, $request) {
$override = $request->getParameter('_my_method');
if ($override) {
$request->setMethod($override);
}
return [];
});
```


###Accessing data in the request

When the instance of `Request` has been fully initialized (using `RequestBuilder`), you gain ability to extract several types of information from this abstraction:

####Parameters



####Cookies
####File uploads
####Request method
####Headers
38 changes: 38 additions & 0 deletions src/Fracture/Http/Headers/ContentType.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,42 @@ public function contains($type)
{
return array_key_exists('value', $this->data) && $this->data['value'] === $type;
}


public function match($type)
{
if ($this->contains($type)) {
return true;
}

return $this->isCompatible($this->data['value'], $type);
}


private function isCompatible($target, $pattern)
{
return $this->replaceStars($target, $pattern) === $this->replaceStars($pattern, $target);
}


/**
* @param string $target
* @param string $pattern
* @return string
*/
private function replaceStars($target, $pattern)
{
$target = explode('/', $target . '/*');
$pattern = explode('/', $pattern . '/*');

if ($pattern[0] === '*') {
$target[0] = '*';
}

if ($pattern[1] === '*') {
$target[1] = '*';
}

return $target[0] . '/' . $target[1];
}
}
25 changes: 3 additions & 22 deletions src/Fracture/Http/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,6 @@ public function __construct($fileBagBuilder = null)
}


private function getResolvedMethod()
{
$method = $this->method;

// to mimic RESTlike API this lets you define override
// for request method in form element with name '_method'
if (array_key_exists('_method', $this->parameters)) {
$replacement = strtolower($this->parameters['_method']);

if (in_array($replacement, ['post', 'put', 'delete'])) {
$method = $replacement;
}

unset($this->parameters['_method']);
}

return $method;
}


private function getResolvedAcceptHeader()
{
$header = $this->acceptHeader;
Expand All @@ -69,7 +49,6 @@ private function getResolvedAcceptHeader()

public function prepare()
{
$this->method = $this->getResolvedMethod();
$this->acceptHeader = $this->getResolvedAcceptHeader();
}

Expand Down Expand Up @@ -101,7 +80,9 @@ public function getParameter($name)
public function setMethod($value)
{
$method = strtolower($value);
$this->method = $method;
if (in_array($method, ['get', 'post', 'put', 'delete', 'head', 'options', 'trace'])) {
$this->method = $method;
}
}


Expand Down
9 changes: 5 additions & 4 deletions src/Fracture/Http/RequestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ protected function applyContentParsers($instance)
}

foreach ($this->parsers as $type => $parser) {
if ($header->contains($type)) {
$parameters += $this->alterParameters($parser, $type, $header);
if ($header->match($type)) {
$parameters += $this->alterParameters($parser, $type, $header, $instance);
}
}

Expand All @@ -81,10 +81,11 @@ protected function applyContentParsers($instance)
* @param callable $parser
* @param string $type
* @param Headers\ContentType $header
* @param Request $instance
*/
private function alterParameters($parser, $type, $header)
private function alterParameters($parser, $type, $header, $instance)
{
$result = call_user_func($parser, $header);
$result = call_user_func($parser, $header, $instance);

if (false === is_array($result)) {
$message = "Parser for '$type' did not return a 'name => value' array of parameters";
Expand Down
51 changes: 51 additions & 0 deletions tests/unit/Fracture/Http/Headers/ContentTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,55 @@ public function testGivenName()
$instance = new ContentType;
$this->assertSame('Content-Type', $instance->getName());
}


/**
* @covers Fracture\Http\Headers\ContentType::__construct
* @covers Fracture\Http\Headers\ContentType::prepare
* @covers Fracture\Http\Headers\ContentType::match
*
* @covers Fracture\Http\Headers\ContentType::isCompatible
* @covers Fracture\Http\Headers\ContentType::replaceStars
*
* @dataProvider provideMimeMatches
*/
public function testMimeMatches($expected, $value, $match)
{
$instance = new ContentType;
$instance->setValue($value);
$instance->prepare();
$this->assertEquals($expected, $instance->match($match));
}


public function provideMimeMatches()
{
return [
[
'expected' => true,
'value' => 'text/html',
'match' => 'text/html',
],
[
'expected' => false,
'value' => 'text/html',
'match' => 'text/plain',
],
[
'expected' => true,
'value' => 'text/html',
'match' => 'text/*',
],
[
'expected' => false,
'value' => 'text/html',
'match' => 'application/*',
],
[
'expected' => true,
'value' => 'application/json',
'match' => '*/*',
],
];
}
}
96 changes: 84 additions & 12 deletions tests/unit/Fracture/Http/RequestBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public function testWhenContentParsersApplied()
],
];

$builder = $this->getMock('Fracture\Http\RequestBuilder', ['applyContentParsers', 'isCLI']);
$builder = $this->getMock('Fracture\Http\RequestBuilder', ['applyContentParsers']);

$builder->expects($this->once())
->method('applyContentParsers')
Expand All @@ -199,7 +199,7 @@ public function testWhenContentParsersIgnored()
],
];

$builder = $this->getMock('Fracture\Http\RequestBuilder', ['applyContentParsers', 'isCLI']);
$builder = $this->getMock('Fracture\Http\RequestBuilder', ['applyContentParsers']);

$builder->expects($this->never())
->method('applyContentParsers');
Expand Down Expand Up @@ -228,8 +228,7 @@ public function testAppliedContentParsers()
],
];

$builder = $this->getMock('Fracture\Http\RequestBuilder', ['isCLI']);

$builder = new RequestBuilder;
$builder->addContentParser('application/json', function () {
return ['foo' => 'bar'];
});
Expand Down Expand Up @@ -261,8 +260,7 @@ public function testAppliedContentParsersOverridesPameters()
],
];

$builder = $this->getMock('Fracture\Http\RequestBuilder', ['isCLI']);

$builder = new RequestBuilder;
$builder->addContentParser('application/json', function () {
return ['foo' => 'different'];
});
Expand Down Expand Up @@ -296,8 +294,7 @@ public function testAppliedContentParsersWithBadReturn()
],
];

$builder = $this->getMock('Fracture\Http\RequestBuilder', ['isCLI']);

$builder = new RequestBuilder;
$builder->addContentParser('application/json', function () {
return null;
});
Expand All @@ -324,8 +321,7 @@ public function testAppliedContentParsersWithMissingHeader()
],
];

$builder = $this->getMock('Fracture\Http\RequestBuilder', ['isCLI']);

$builder = new RequestBuilder;
$builder->addContentParser('application/json', function () {
return ['foo' => 'bar'];
});
Expand All @@ -349,8 +345,7 @@ public function testAppliedContentParsersWithHeader()
],
];

$builder = $this->getMock('Fracture\Http\RequestBuilder', ['isCLI']);

$builder = new RequestBuilder;
$builder->addContentParser('text/html', function ($header) {
return ['foo' => $header->getParameter('version')];
});
Expand All @@ -360,6 +355,83 @@ public function testAppliedContentParsersWithHeader()
}


/**
* @covers Fracture\Http\RequestBuilder::create
* @covers Fracture\Http\RequestBuilder::applyContentParsers
* @covers Fracture\Http\RequestBuilder::addContentParser
*/
public function testAppliedContentParsersWithRequest()
{
$input = [
'get' => [
'test' => 'value',
],
'server' => [
'CONTENT_TYPE' => 'text/html',
],
];

$builder = new RequestBuilder;
$builder->addContentParser('text/html', function ($header, $request) {
return ['duplicate' => $request->getParameter('test')];
});

$instance = $builder->create($input);
$this->assertEquals('value', $instance->getParameter('duplicate'));
}


/**
* @covers Fracture\Http\RequestBuilder::create
* @covers Fracture\Http\RequestBuilder::applyContentParsers
* @covers Fracture\Http\RequestBuilder::addContentParser
*/
public function testOverrideRequestMethodWithParser()
{
$input = [
'get' => [
'_mark' => 'put',
],
'server' => [
'CONTENT_TYPE' => 'text/html',
],
];

$builder = new RequestBuilder;
$builder->addContentParser('text/html', function ($header, $request) {
$method = $request->getParameter('_mark');
$request->setMethod($method);
return [];
});

$instance = $builder->create($input);
$this->assertEquals('put', $instance->getMethod());
}


/**
* @covers Fracture\Http\RequestBuilder::create
* @covers Fracture\Http\RequestBuilder::applyContentParsers
* @covers Fracture\Http\RequestBuilder::addContentParser
*/
public function testAppliedParserForWildecard()
{
$input = [
'server' => [
'CONTENT_TYPE' => 'text/html',
],
];

$builder = new RequestBuilder;
$builder->addContentParser('*/*', function ($header, $request) {
return ['called' => true];
});

$instance = $builder->create($input);
$this->assertTrue($instance->getParameter('called'));
}


/**
* @covers Fracture\Http\RequestBuilder::create
* @covers Fracture\Http\RequestBuilder::applyContentParsers
Expand Down
Loading

0 comments on commit 15c2d3b

Please sign in to comment.