4 * This file is part of the JsonSchema package.
6 * For the full copyright and license information, please view the LICENSE
7 * file that was distributed with this source code.
10 namespace JsonSchema\Uri;
12 use JsonSchema\Exception\InvalidSchemaMediaTypeException;
13 use JsonSchema\Exception\JsonDecodingException;
14 use JsonSchema\Exception\ResourceNotFoundException;
15 use JsonSchema\Uri\Retrievers\FileGetContents;
16 use JsonSchema\Uri\Retrievers\UriRetrieverInterface;
17 use JsonSchema\UriRetrieverInterface as BaseUriRetrieverInterface;
18 use JsonSchema\Validator;
21 * Retrieves JSON Schema URIs
23 * @author Tyler Akins <fidian@rumkin.com>
25 class UriRetriever implements BaseUriRetrieverInterface
28 * @var array Map of URL translations
30 protected $translationMap = array(
31 // use local copies of the spec schemas
32 '|^https?://json-schema.org/draft-(0[34])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json'
36 * @var null|UriRetrieverInterface
38 protected $uriRetriever = null;
45 private $schemaCache = array();
48 * Guarantee the correct media type was encountered
50 * @param UriRetrieverInterface $uriRetriever
55 public function confirmMediaType($uriRetriever, $uri)
57 $contentType = $uriRetriever->getContentType();
59 if (is_null($contentType)) {
60 // Well, we didn't get an invalid one
64 if (in_array($contentType, array(Validator::SCHEMA_MEDIA_TYPE, 'application/json'))) {
68 if (substr($uri, 0, 23) == 'http://json-schema.org/') {
69 //HACK; they deliver broken content types
73 throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE));
79 * If none is specified, sets a default FileGetContents retriever and
80 * returns that object.
82 * @return UriRetrieverInterface
84 public function getUriRetriever()
86 if (is_null($this->uriRetriever)) {
87 $this->setUriRetriever(new FileGetContents());
90 return $this->uriRetriever;
94 * Resolve a schema based on pointer
96 * URIs can have a fragment at the end in the format of
97 * #/path/to/object and we are to look up the 'path' property of
98 * the first object then the 'to' and 'object' properties.
100 * @param object $jsonSchema JSON Schema contents
101 * @param string $uri JSON Schema URI
103 * @throws ResourceNotFoundException
105 * @return object JSON Schema after walking down the fragment pieces
107 public function resolvePointer($jsonSchema, $uri)
109 $resolver = new UriResolver();
110 $parsed = $resolver->parse($uri);
111 if (empty($parsed['fragment'])) {
115 $path = explode('/', $parsed['fragment']);
117 $pathElement = array_shift($path);
118 if (!empty($pathElement)) {
119 $pathElement = str_replace('~1', '/', $pathElement);
120 $pathElement = str_replace('~0', '~', $pathElement);
121 if (!empty($jsonSchema->$pathElement)) {
122 $jsonSchema = $jsonSchema->$pathElement;
124 throw new ResourceNotFoundException(
125 'Fragment "' . $parsed['fragment'] . '" not found'
130 if (!is_object($jsonSchema)) {
131 throw new ResourceNotFoundException(
132 'Fragment part "' . $pathElement . '" is no object '
145 public function retrieve($uri, $baseUri = null, $translate = true)
147 $resolver = new UriResolver();
148 $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri);
150 //fetch URL without #fragment
151 $arParts = $resolver->parse($resolvedUri);
152 if (isset($arParts['fragment'])) {
153 unset($arParts['fragment']);
154 $fetchUri = $resolver->generate($arParts);
157 // apply URI translations
159 $fetchUri = $this->translate($fetchUri);
162 $jsonSchema = $this->loadSchema($fetchUri);
164 // Use the JSON pointer if specified
165 $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri);
167 if ($jsonSchema instanceof \stdClass) {
168 $jsonSchema->id = $resolvedUri;
175 * Fetch a schema from the given URI, json-decode it and return it.
176 * Caches schema objects.
178 * @param string $fetchUri Absolute URI
180 * @return object JSON schema object
182 protected function loadSchema($fetchUri)
184 if (isset($this->schemaCache[$fetchUri])) {
185 return $this->schemaCache[$fetchUri];
188 $uriRetriever = $this->getUriRetriever();
189 $contents = $this->uriRetriever->retrieve($fetchUri);
190 $this->confirmMediaType($uriRetriever, $fetchUri);
191 $jsonSchema = json_decode($contents);
193 if (JSON_ERROR_NONE < $error = json_last_error()) {
194 throw new JsonDecodingException($error);
197 $this->schemaCache[$fetchUri] = $jsonSchema;
203 * Set the URI Retriever
205 * @param UriRetrieverInterface $uriRetriever
207 * @return $this for chaining
209 public function setUriRetriever(UriRetrieverInterface $uriRetriever)
211 $this->uriRetriever = $uriRetriever;
217 * Parses a URI into five main components
223 public function parse($uri)
225 preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);
227 $components = array();
228 if (5 < count($match)) {
230 'scheme' => $match[2],
231 'authority' => $match[4],
236 if (7 < count($match)) {
237 $components['query'] = $match[7];
240 if (9 < count($match)) {
241 $components['fragment'] = $match[9];
248 * Builds a URI based on n array with the main components
250 * @param array $components
254 public function generate(array $components)
256 $uri = $components['scheme'] . '://'
257 . $components['authority']
258 . $components['path'];
260 if (array_key_exists('query', $components)) {
261 $uri .= $components['query'];
264 if (array_key_exists('fragment', $components)) {
265 $uri .= $components['fragment'];
274 * @param string $uri Absolute or relative
275 * @param string $baseUri Optional base URI
279 public function resolve($uri, $baseUri = null)
281 $components = $this->parse($uri);
282 $path = $components['path'];
284 if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) {
288 $baseComponents = $this->parse($baseUri);
289 $basePath = $baseComponents['path'];
291 $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath);
293 return $this->generate($baseComponents);
301 public function isValid($uri)
303 $components = $this->parse($uri);
305 return !empty($components);
309 * Set a URL translation rule
311 public function setTranslation($from, $to)
313 $this->translationMap[$from] = $to;
317 * Apply URI translation rules
319 public function translate($uri)
321 foreach ($this->translationMap as $from => $to) {
322 $uri = preg_replace($from, $to, $uri);
325 // translate references to local files within the json-schema package
326 $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri);