Skip to content

Commit

Permalink
Merge pull request #2 from lcobucci/add-base-functionality
Browse files Browse the repository at this point in the history
Add base functionality
  • Loading branch information
lcobucci authored Mar 25, 2018
2 parents a1560d2 + c17c3a9 commit 5f81517
Show file tree
Hide file tree
Showing 9 changed files with 737 additions and 2 deletions.
18 changes: 17 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,26 @@
"require-dev": {
"doctrine/coding-standard": "^4.0",
"infection/infection": "^0.8",
"middlewares/negotiation": "^1.0",
"phpstan/phpstan": "^0.10@dev",
"phpstan/phpstan-phpunit": "^0.10@dev",
"phpstan/phpstan-strict-rules": "^0.10@dev",
"phpunit/phpunit": "^7.0",
"squizlabs/php_codesniffer": "^3.2"
"squizlabs/php_codesniffer": "^3.2",
"zendframework/zend-diactoros": "^1.7"
},
"suggest": {
"middlewares/negotiation": "For acceptable format identification",
"zendframework/zend-diactoros": "For concrete implementation of PSR-7"
},
"autoload": {
"psr-4": {
"Lcobucci\\ContentNegotiation\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Lcobucci\\ContentNegotiation\\Tests\\": "tests"
}
}
}
6 changes: 5 additions & 1 deletion phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
<file>src</file>
<file>tests</file>

<rule ref="Doctrine" />
<rule ref="Doctrine">
<exclude name="SlevomatCodingStandard.Commenting.RequireOneLinePropertyDocComment" />
</rule>

<rule ref="SlevomatCodingStandard.TypeHints.DeclareStrictTypes">
<properties>
Expand All @@ -25,4 +27,6 @@
<property name="spacesCountBeforeColon" value="0"/>
</properties>
</rule>

<rule ref="SlevomatCodingStandard.Commenting.DisallowOneLinePropertyDocComment" />
</ruleset>
10 changes: 10 additions & 0 deletions src/ContentCouldNotBeFormatted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);

namespace Lcobucci\ContentNegotiation;

use RuntimeException;

final class ContentCouldNotBeFormatted extends RuntimeException
{
}
113 changes: 113 additions & 0 deletions src/ContentTypeMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);

namespace Lcobucci\ContentNegotiation;

use Fig\Http\Message\StatusCodeInterface;
use Middlewares\ContentType;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Stream;
use function strpos;
use function substr;

final class ContentTypeMiddleware implements MiddlewareInterface
{
/**
* @var MiddlewareInterface
*/
private $negotiator;

/**
* @var callable
*/
private $streamFactory;

/**
* @var Formatter[]
*/
private $formatters;

/**
* @param Formatter[] $formatters
*/
public function __construct(
MiddlewareInterface $negotiator,
array $formatters,
callable $streamFactory
) {
$this->negotiator = $negotiator;
$this->formatters = $formatters;
$this->streamFactory = $streamFactory;
}

/**
* @param mixed[] $formats
* @param Formatter[] $formatters
*/
public static function fromRecommendedSettings(
array $formats,
array $formatters,
?callable $streamFactory = null
): self {
return new self(
new ContentType($formats),
$formatters,
function () use ($streamFactory): StreamInterface {
return $streamFactory !== null ? $streamFactory() : new Stream('php://temp', 'wb+');
}
);
}

/**
* {@inheritdoc}
*
* @throws ContentCouldNotBeFormatted
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $this->negotiator->process($request, $handler);

if (! $response instanceof UnformattedResponse) {
return $response;
}

$contentType = $this->extractContentType($response->getHeaderLine('Content-Type'));
$formatter = $this->formatters[$contentType] ?? null;

return $this->formatResponse($response, $formatter);
}

private function extractContentType(string $contentType): string
{
$charsetSeparatorPosition = strpos($contentType, ';');

if ($charsetSeparatorPosition === false) {
return $contentType;
}

return substr($contentType, 0, $charsetSeparatorPosition);
}

/**
* @throws ContentCouldNotBeFormatted
*/
private function formatResponse(UnformattedResponse $response, ?Formatter $formatter): ResponseInterface
{
/** @var StreamInterface $body */
$body = ($this->streamFactory)();
$response = $response->withBody($body);

if ($formatter === null) {
return $response->withStatus(StatusCodeInterface::STATUS_NOT_ACCEPTABLE);
}

$body->write($formatter->format($response->getUnformattedContent()));
$body->rewind();

return $response;
}
}
14 changes: 14 additions & 0 deletions src/Formatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);

namespace Lcobucci\ContentNegotiation;

interface Formatter
{
/**
* @param mixed $content
*
* @throw ContentCouldNotBeFormatted
*/
public function format($content): string;
}
167 changes: 167 additions & 0 deletions src/UnformattedResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php
declare(strict_types=1);

namespace Lcobucci\ContentNegotiation;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

final class UnformattedResponse implements ResponseInterface
{
/**
* @var ResponseInterface
*/
private $decoratedResponse;

/**
* @var mixed
*/
private $unformattedContent;

/**
* @param mixed $unformattedContent
*/
public function __construct(ResponseInterface $decoratedResponse, $unformattedContent)
{
$this->decoratedResponse = $decoratedResponse;
$this->unformattedContent = $unformattedContent;
}

/**
* @return mixed
*/
public function getUnformattedContent()
{
return $this->unformattedContent;
}

/**
* {@inheritdoc}
*/
public function getProtocolVersion()
{
return $this->decoratedResponse->getProtocolVersion();
}

/**
* {@inheritdoc}
*/
public function withProtocolVersion($version)
{
return new self(
$this->decoratedResponse->withProtocolVersion($version),
$this->unformattedContent
);
}

/**
* {@inheritdoc}
*/
public function getHeaders()
{
return $this->decoratedResponse->getHeaders();
}

/**
* {@inheritdoc}
*/
public function hasHeader($name)
{
return $this->decoratedResponse->hasHeader($name);
}

/**
* {@inheritdoc}
*/
public function getHeader($name)
{
return $this->decoratedResponse->getHeader($name);
}

/**
* {@inheritdoc}
*/
public function getHeaderLine($name)
{
return $this->decoratedResponse->getHeaderLine($name);
}

/**
* {@inheritdoc}
*/
public function withHeader($name, $value)
{
return new self(
$this->decoratedResponse->withHeader($name, $value),
$this->unformattedContent
);
}

/**
* {@inheritdoc}
*/
public function withAddedHeader($name, $value)
{
return new self(
$this->decoratedResponse->withAddedHeader($name, $value),
$this->unformattedContent
);
}

/**
* {@inheritdoc}
*/
public function withoutHeader($name)
{
return new self(
$this->decoratedResponse->withoutHeader($name),
$this->unformattedContent
);
}

/**
* {@inheritdoc}
*/
public function getBody()
{
return $this->decoratedResponse->getBody();
}

/**
* {@inheritdoc}
*/
public function withBody(StreamInterface $body)
{
return new self(
$this->decoratedResponse->withBody($body),
$this->unformattedContent
);
}

/**
* {@inheritdoc}
*/
public function getStatusCode()
{
return $this->decoratedResponse->getStatusCode();
}

/**
* {@inheritdoc}
*/
public function withStatus($code, $reasonPhrase = '')
{
return new self(
$this->decoratedResponse->withStatus($code, $reasonPhrase),
$this->unformattedContent
);
}

/**
* {@inheritdoc}
*/
public function getReasonPhrase()
{
return $this->decoratedResponse->getReasonPhrase();
}
}
Loading

0 comments on commit 5f81517

Please sign in to comment.