77
88namespace cebe \openapi ;
99
10+ use cebe \openapi \exceptions \IOException ;
1011use cebe \openapi \exceptions \UnresolvableReferenceException ;
12+ use cebe \openapi \json \JsonPointer ;
13+ use cebe \openapi \spec \Reference ;
14+ use Symfony \Component \Yaml \Yaml ;
1115
1216/**
1317 * ReferenceContext represents a context in which references are resolved.
@@ -27,17 +31,24 @@ class ReferenceContext
2731 * @var string
2832 */
2933 private $ _uri ;
34+ /**
35+ * @var ReferenceContextCache
36+ */
37+ private $ _cache ;
38+
3039
3140 /**
3241 * ReferenceContext constructor.
3342 * @param SpecObjectInterface $base the base object of the spec.
3443 * @param string $uri the URI to the base object.
44+ * @param ReferenceContextCache $cache cache instance for storing referenced file data.
3545 * @throws UnresolvableReferenceException in case an invalid or non-absolute URI is provided.
3646 */
37- public function __construct (?SpecObjectInterface $ base , string $ uri )
47+ public function __construct (?SpecObjectInterface $ base , string $ uri, $ cache = null )
3848 {
3949 $ this ->_baseSpec = $ base ;
4050 $ this ->_uri = $ this ->normalizeUri ($ uri );
51+ $ this ->_cache = $ cache ?? new ReferenceContextCache ();
4152 }
4253
4354 /**
@@ -138,4 +149,68 @@ private function dirname($path)
138149 }
139150 return '' ;
140151 }
152+
153+ private $ _fileCache ;
154+
155+ /**
156+ * Fetch referenced file by URI.
157+ *
158+ * The current context will cache files by URI, so they are only loaded once.
159+ *
160+ * @throws IOException in case the file is not readable or fetching the file
161+ * from a remote URL failed.
162+ */
163+ public function fetchReferencedFile ($ uri )
164+ {
165+ $ content = file_get_contents ($ uri );
166+ if ($ content === false ) {
167+ $ e = new IOException ("Failed to read file: ' $ uri' " );
168+ $ e ->fileName = $ uri ;
169+ throw $ e ;
170+ }
171+ // TODO lazy content detection, should be improved
172+ if (strpos (ltrim ($ content ), '{ ' ) === 0 ) {
173+ return json_decode ($ content , true );
174+ } else {
175+ return Yaml::parse ($ content );
176+ }
177+ }
178+
179+ /**
180+ * Retrieve the referenced data via JSON pointer.
181+ *
182+ * This function caches referenced data to make sure references to the same
183+ * data structures end up being the same object instance in PHP.
184+ *
185+ * @param string $uri
186+ * @param JsonPointer $pointer
187+ * @param array $data
188+ * @param string|null $toType
189+ * @return SpecObjectInterface|array
190+ */
191+ public function resolveReferenceData ($ uri , JsonPointer $ pointer , $ data , $ toType )
192+ {
193+ $ ref = $ uri . '# ' . $ pointer ->getPointer ();
194+ if ($ this ->_cache ->has ($ ref , $ toType )) {
195+ return $ this ->_cache ->get ($ ref , $ toType );
196+ }
197+
198+ $ referencedData = $ pointer ->evaluate ($ data );
199+
200+ if ($ referencedData === null ) {
201+ return null ;
202+ }
203+
204+ // transitive reference
205+ if (isset ($ referencedData ['$ref ' ])) {
206+ return (new Reference ($ referencedData , $ toType ))->resolve (new ReferenceContext (null , $ uri ));
207+ }
208+ /** @var SpecObjectInterface|array $referencedObject */
209+ $ referencedObject = $ toType !== null ? new $ toType ($ referencedData ) : $ referencedData ;
210+
211+ $ this ->_cache ->set ($ ref , $ toType , $ referencedObject );
212+
213+ return $ referencedObject ;
214+ }
215+
141216}
0 commit comments