1<?php 2 3namespace JsonSchema; 4 5use JsonSchema\Constraints\BaseConstraint; 6use JsonSchema\Entity\JsonPointer; 7use JsonSchema\Exception\UnresolvableJsonPointerException; 8use JsonSchema\Uri\UriResolver; 9use JsonSchema\Uri\UriRetriever; 10 11class SchemaStorage implements SchemaStorageInterface 12{ 13 const INTERNAL_PROVIDED_SCHEMA_URI = 'internal://provided-schema/'; 14 15 protected $uriRetriever; 16 protected $uriResolver; 17 protected $schemas = array(); 18 19 public function __construct( 20 UriRetrieverInterface $uriRetriever = null, 21 UriResolverInterface $uriResolver = null 22 ) { 23 $this->uriRetriever = $uriRetriever ?: new UriRetriever(); 24 $this->uriResolver = $uriResolver ?: new UriResolver(); 25 } 26 27 /** 28 * @return UriRetrieverInterface 29 */ 30 public function getUriRetriever() 31 { 32 return $this->uriRetriever; 33 } 34 35 /** 36 * @return UriResolverInterface 37 */ 38 public function getUriResolver() 39 { 40 return $this->uriResolver; 41 } 42 43 /** 44 * {@inheritdoc} 45 */ 46 public function addSchema($id, $schema = null) 47 { 48 if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) { 49 // if the schema was user-provided to Validator and is still null, then assume this is 50 // what the user intended, as there's no way for us to retrieve anything else. User-supplied 51 // schemas do not have an associated URI when passed via Validator::validate(). 52 $schema = $this->uriRetriever->retrieve($id); 53 } 54 55 // cast array schemas to object 56 if (is_array($schema)) { 57 $schema = BaseConstraint::arrayToObjectRecursive($schema); 58 } 59 60 // workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format) 61 // see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367 62 if (is_object($schema) && property_exists($schema, 'id')) { 63 if ($schema->id == 'http://json-schema.org/draft-04/schema#') { 64 $schema->properties->id->format = 'uri-reference'; 65 } elseif ($schema->id == 'http://json-schema.org/draft-03/schema#') { 66 $schema->properties->id->format = 'uri-reference'; 67 $schema->properties->{'$ref'}->format = 'uri-reference'; 68 } 69 } 70 71 // resolve references 72 $this->expandRefs($schema, $id); 73 74 $this->schemas[$id] = $schema; 75 } 76 77 /** 78 * Recursively resolve all references against the provided base 79 * 80 * @param mixed $schema 81 * @param string $base 82 */ 83 private function expandRefs(&$schema, $base = null) 84 { 85 if (!is_object($schema)) { 86 if (is_array($schema)) { 87 foreach ($schema as &$member) { 88 $this->expandRefs($member, $base); 89 } 90 } 91 92 return; 93 } 94 95 if (property_exists($schema, 'id') && is_string($schema->id) && $base != $schema->id) { 96 $base = $this->uriResolver->resolve($schema->id, $base); 97 } 98 99 if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) { 100 $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base)); 101 $schema->{'$ref'} = (string) $refPointer; 102 } 103 104 foreach ($schema as &$member) { 105 $this->expandRefs($member, $base); 106 } 107 } 108 109 /** 110 * {@inheritdoc} 111 */ 112 public function getSchema($id) 113 { 114 if (!array_key_exists($id, $this->schemas)) { 115 $this->addSchema($id); 116 } 117 118 return $this->schemas[$id]; 119 } 120 121 /** 122 * {@inheritdoc} 123 */ 124 public function resolveRef($ref) 125 { 126 $jsonPointer = new JsonPointer($ref); 127 128 // resolve filename for pointer 129 $fileName = $jsonPointer->getFilename(); 130 if (!strlen($fileName)) { 131 throw new UnresolvableJsonPointerException(sprintf( 132 "Could not resolve fragment '%s': no file is defined", 133 $jsonPointer->getPropertyPathAsString() 134 )); 135 } 136 137 // get & process the schema 138 $refSchema = $this->getSchema($fileName); 139 foreach ($jsonPointer->getPropertyPaths() as $path) { 140 if (is_object($refSchema) && property_exists($refSchema, $path)) { 141 $refSchema = $this->resolveRefSchema($refSchema->{$path}); 142 } elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) { 143 $refSchema = $this->resolveRefSchema($refSchema[$path]); 144 } else { 145 throw new UnresolvableJsonPointerException(sprintf( 146 'File: %s is found, but could not resolve fragment: %s', 147 $jsonPointer->getFilename(), 148 $jsonPointer->getPropertyPathAsString() 149 )); 150 } 151 } 152 153 return $refSchema; 154 } 155 156 /** 157 * {@inheritdoc} 158 */ 159 public function resolveRefSchema($refSchema) 160 { 161 if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) { 162 $newSchema = $this->resolveRef($refSchema->{'$ref'}); 163 $refSchema = (object) (get_object_vars($refSchema) + get_object_vars($newSchema)); 164 unset($refSchema->{'$ref'}); 165 } 166 167 return $refSchema; 168 } 169} 170