Skip to content
This repository has been archived by the owner on May 12, 2018. It is now read-only.

Commit

Permalink
Merge a80be79 into ea7bca7
Browse files Browse the repository at this point in the history
  • Loading branch information
peterpostmann committed Nov 29, 2017
2 parents ea7bca7 + a80be79 commit 377f0cc
Show file tree
Hide file tree
Showing 19 changed files with 724 additions and 55 deletions.
24 changes: 20 additions & 4 deletions docs/decoding.md
Expand Up @@ -5,7 +5,7 @@ title: Decoding

# Decoders

Anytime a schema is decoded it uses a decoder. Decoders are registered for a specific file extension. You need to register the proper decoder for the file type you would like to decode.
Anytime a schema is decoded it uses a decoder. Decoders are registered for a specific media type. You need to register a decoder for every type you would like to decode.

Decoders can also be decorated to add behavior like caching.

Expand All @@ -20,7 +20,7 @@ By default the 'json' decoder is used.

## Custom Decoders

You can make your own decoders by implementing the [Decoder Interface](https://github.com/thephpleague/json-reference/blob/master/src/DecoderInterface.php).
You can create your own decoders by implementing the [Decoder Interface](https://github.com/thephpleague/json-reference/blob/master/src/DecoderInterface.php).

Imagine you may want to decode schemas from a xml document, and your references look like this:

Expand Down Expand Up @@ -48,7 +48,7 @@ Once you have written your custom decoder, you can register it.

## Registering Decoders

Decoders are registered in the Loaders's constructor. You register a decoder by passing an instance.
Decoders are registered with the Loaders's DecoderManager. You register a decoder by passing the extension you would like to decode schemas for and the decoder instance to the `registerDecoder` method.

```php
<?php
Expand All @@ -59,5 +59,21 @@ $decoder = new CustomDecoder();
$loader = new FileLoader($decoder);
$deref = new League\JsonReference\Dereferencer();

$deref->getLoaderManager()->registerLoader('file', $loader);
$deref->getLoaderManager()->getDecoderManager()->registerDecoder('xyz', $customDecoder);
```

### Media Types

The decoder manager determines the decoder based on the Content-Type header (first priority) or file extension (second priority).


| Content-Type Header | File Extension | Evaluated Type |
| ------------------- | -------------- | -------------- |
| xxx/yyy | | xxx/yyy |
| xxx/yyy+zzz | | +zzz |
| xxx/yyy+zzz | foo.bar | +zzz |
| xxx/yyy | foo.bar | xxx/yyy |
| | foo.bar | bar |
| | | null |

If the suffix of a sub-type is used, a '+'-sign is used to distinguishe between suffixes and file extensions.
141 changes: 141 additions & 0 deletions src/DecoderManager.php
@@ -0,0 +1,141 @@
<?php

namespace League\JsonReference;

use League\JsonReference\Decoder\JsonDecoder;
use League\JsonReference\Decoder\YamlDecoder;

final class DecoderManager
{
/**
* @var ParserInterface[]
*/
private $decoders = [];

/**
* @param DecoderInterface[] $decoders
* @param string $defaultType
*/
public function __construct(array $decoders = [], $defaultType = null)
{
if (empty($decoders)) {
$this->registerDefaultDecoders();

// Backwards compatibilty
if ($defaultType === null) {
$defaultType = 'json';
}
} else {
foreach ($decoders as $type => $decoder) {
$this->registerDecoder($type, $decoder);
}
}

if ($defaultType) {
$this->setDefaultType($defaultType);
}
}

/**
* Register a DecoderInterface for the given MIME-types.
*
* @param string $type
* @param DecoderInterface $decoder
*/
public function registerDecoder($type, DecoderInterface $decoder = null)
{
if ($decoder) {
$this->decoders[$type] = $decoder;
} else {
unset($this->decoders[$type]);
}
}

/**
* Get all registered decoders, keyed by the extensions they are registered to decode schemas for.
*
* @return DecoderInterface[]
*/
public function getDecoders()
{
return $this->decoders;
}

/**
* Set the default type for unknown files
*
* @param string defaultType
*/
public function setDefaultType($defaultType = null)
{
$this->registerDecoder(null, $defaultType ? $this->getDecoder($defaultType) : null);
}

/**
* Get the decoder for the given extension.
*
* @param string $type
*
* @return DecoderInterface
* @throws \InvalidArgumentException
*/
public function getDecoder($type)
{
if (!$this->hasDecoder($type)) {
if ($this->hasDecoder(null)) {
$type = null;
} else {
throw new \InvalidArgumentException(
sprintf('A decoder is not registered for the extension "%s"', $type)
);
}
}

return $this->decoders[$type];
}

/**
* @param string $type
*
* @return bool
*/
public function hasDecoder($type)
{
return isset($this->decoders[$type]);
}

/**
* Register the default decoders
*/
private function registerDefaultDecoders()
{
$this->registerJsonDecoder();
}

/**
* @param DecoderInterface $decoder
*/
public function registerJsonDecoder(DecoderInterface $decoder = null)
{
$decoder = $decoder ?: new JsonDecoder();

$this->registerDecoder('json', $decoder);
$this->registerDecoder('text/json', $decoder);
$this->registerDecoder('application/json', $decoder);
$this->registerDecoder('+json', $decoder);
}

/**
* @param DecoderInterface $decoder
*/
public function registerYamlDecoder(DecoderInterface $decoder = null)
{
$decoder = $decoder ?: new YamlDecoder();

$this->registerDecoder('yml', $decoder);
$this->registerDecoder('yaml', $decoder);
$this->registerDecoder('text/yaml', $decoder);
$this->registerDecoder('application/x-yaml', $decoder);
$this->registerDecoder('+yaml', $decoder);
}
}
35 changes: 24 additions & 11 deletions src/Loader/ArrayLoader.php
Expand Up @@ -2,10 +2,11 @@

namespace League\JsonReference\Loader;

use League\JsonReference\Decoder\JsonDecoder;
use League\JsonReference\DecoderManager;
use League\JsonReference\DecoderInterface;
use League\JsonReference\LoaderInterface;
use League\JsonReference\SchemaLoadingException;
use function League\JsonReference\determineMediaType;

final class ArrayLoader implements LoaderInterface
{
Expand All @@ -15,23 +16,34 @@ final class ArrayLoader implements LoaderInterface
private $schemas;

/**
* @var DecoderInterface
* @var DecoderManager
*/
private $jsonDecoder;
private $decoderManager;

/**
* @param array $schemas A map of schemas where path => schema.The schema should be a string or the
* object resulting from a json_decode call.
* @param DecoderInterface $jsonDecoder
* @param array $schemas A map of schemas where path => schema.The schema should be a string or the
* object resulting from a json_decode call.
*
* @param DecoderInterface|DecoderManager $decoderManager
*/
public function __construct(array $schemas, DecoderInterface $jsonDecoder = null)
public function __construct(array $schemas, $decoderManager = null)
{
$this->schemas = $schemas;
$this->jsonDecoder = $jsonDecoder ?: new JsonDecoder();
$this->schemas = $schemas;

if ($decoderManager instanceof DecoderInterface) {
$this->decoderManager = new DecoderManager([null => $decoderManager]);
} else {
$this->decoderManager = $decoderManager ?: new DecoderManager();
}
}

/**
* {@inheritdoc}
* @param string $schema
* @param string $type
*
* @return object
*
* @throws DecodingException
*/
public function load($path)
{
Expand All @@ -42,7 +54,8 @@ public function load($path)
$schema = $this->schemas[$path];

if (is_string($schema)) {
return $this->jsonDecoder->decode($schema);
$type = determineMediaType(['uri' => $path]);
return $this->decoderManager->getDecoder($type)->decode($schema);
} elseif (is_object($schema)) {
return $schema;
} else {
Expand Down
44 changes: 28 additions & 16 deletions src/Loader/CurlWebLoader.php
Expand Up @@ -3,9 +3,10 @@
namespace League\JsonReference\Loader;

use League\JsonReference;
use League\JsonReference\Decoder\JsonDecoder;
use League\JsonReference\DecoderManager;
use League\JsonReference\DecoderInterface;
use League\JsonReference\LoaderInterface;
use function League\JsonReference\determineMediaType;

final class CurlWebLoader implements LoaderInterface
{
Expand All @@ -20,20 +21,25 @@ final class CurlWebLoader implements LoaderInterface
private $curlOptions;

/**
* @var DecoderInterface
* @var DecoderManager
*/
private $jsonDecoder;
private $decoderManager;

/**
* @param string $prefix
* @param array $curlOptions
* @param DecoderInterface $jsonDecoder
* @param string $prefix
* @param array $curlOptions
* @param DecoderInterface|DecoderManager $decoderManager
*/
public function __construct($prefix, array $curlOptions = null, DecoderInterface $jsonDecoder = null)
public function __construct($prefix, array $curlOptions = null, $decoderManager = null)
{
$this->prefix = $prefix;
$this->jsonDecoder = $jsonDecoder ?: new JsonDecoder();
$this->prefix = $prefix;
$this->setCurlOptions($curlOptions);

if ($decoderManager instanceof DecoderInterface) {
$this->decoderManager = new DecoderManager([null => $decoderManager]);
} else {
$this->decoderManager = $decoderManager ?: new DecoderManager();
}
}

/**
Expand All @@ -44,27 +50,33 @@ public function load($path)
$uri = $this->prefix . $path;
$ch = curl_init($uri);
curl_setopt_array($ch, $this->curlOptions);
list($response, $statusCode) = $this->getResponseBodyAndStatusCode($ch);
list($response, $statusCode, $type) = $this->getResponseBodyStatusCodeAndContentType($ch);
curl_close($ch);

if ($statusCode >= 400 || !$response) {
throw JsonReference\SchemaLoadingException::create($uri);
}

return $this->jsonDecoder->decode($response);
$type = determineMediaType(['Content-Type' => $type, 'uri' => $uri]);
return $this->decoderManager->getDecoder($type)->decode($response);
}

/**
* @param resource $ch
*
* @return array
*/
private function getResponseBodyAndStatusCode($ch)
private function getResponseBodyStatusCodeAndContentType($ch)
{
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$body = substr($response, $header_size);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

return [$response, $statusCode];
return [$body, $statusCode, $contentType];
}

/**
Expand Down

0 comments on commit 377f0cc

Please sign in to comment.