99
1010use cebe \openapi \exceptions \TypeErrorException ;
1111use cebe \openapi \exceptions \UnresolvableReferenceException ;
12+ use cebe \openapi \json \InvalidJsonPointerSyntaxException ;
13+ use cebe \openapi \json \JsonReference ;
14+ use cebe \openapi \json \NonexistentJsonPointerReferenceException ;
1215use cebe \openapi \ReferenceContext ;
1316use cebe \openapi \SpecObjectInterface ;
1417use Symfony \Component \Yaml \Yaml ;
@@ -25,6 +28,7 @@ class Reference implements SpecObjectInterface
2528{
2629 private $ _to ;
2730 private $ _ref ;
31+ private $ _jsonReference ;
2832 private $ _context ;
2933
3034 private $ _errors = [];
@@ -39,25 +43,29 @@ public function __construct(array $data, string $to = null)
3943 {
4044 if (!isset ($ data ['$ref ' ])) {
4145 throw new TypeErrorException (
42- "Unable to instantiate Reference Object with data ' " . print_r ($ data , true ) . "' "
46+ "Unable to instantiate Reference Object with data ' " . print_r ($ data , true ) . "'. "
4347 );
4448 }
4549 if ($ to !== null && !is_subclass_of ($ to , SpecObjectInterface::class, true )) {
4650 throw new TypeErrorException (
47- "Unable to instantiate Reference Object, Referenced Class type must implement SpecObjectInterface "
51+ "Unable to instantiate Reference Object, Referenced Class type must implement SpecObjectInterface. "
52+ );
53+ }
54+ if (!is_string ($ data ['$ref ' ])) {
55+ throw new TypeErrorException (
56+ 'Unable to instantiate Reference Object, value of $ref must be a string. '
4857 );
4958 }
5059 $ this ->_to = $ to ;
60+ $ this ->_ref = $ data ['$ref ' ];
61+ try {
62+ $ this ->_jsonReference = JsonReference::createFromReference ($ data ['$ref ' ]);
63+ } catch (InvalidJsonPointerSyntaxException $ e ) {
64+ $ this ->_errors [] = 'Reference: value of $ref is not a valid JSON pointer: ' . $ e ->getMessage ();
65+ }
5166 if (count ($ data ) !== 1 ) {
5267 $ this ->_errors [] = 'Reference: additional properties are given. Only $ref should be set in a Reference Object. ' ;
5368 }
54- if (!is_string ($ data ['$ref ' ])) {
55- $ this ->_errors [] = 'Reference: value of $ref must be a string. ' ;
56- }
57- if (!empty ($ this ->_errors )) {
58- return ;
59- }
60- $ this ->_ref = $ data ['$ref ' ];
6169 }
6270
6371 /**
@@ -96,6 +104,14 @@ public function getReference()
96104 return $ this ->_ref ;
97105 }
98106
107+ /**
108+ * @return JsonReference the JSON Reference.
109+ */
110+ public function getJsonReference (): JsonReference
111+ {
112+ return $ this ->_jsonReference ;
113+ }
114+
99115 /**
100116 * @param ReferenceContext $context
101117 */
@@ -128,68 +144,30 @@ public function resolve(ReferenceContext $context = null)
128144 throw new UnresolvableReferenceException ('No context given for resolving reference. ' );
129145 }
130146 }
131- if (($ pos = strpos ($ this ->_ref , '# ' )) === 0 ) {
132- // resolve in current document
133- $ jsonPointer = substr ($ this ->_ref , 1 );
134- // TODO type error if resolved object does not match $this->_to ?
135- return $ this ->resolveJsonPointer ($ jsonPointer , $ context ->getBaseSpec ());
136- }
137-
138- $ file = ($ pos === false ) ? $ this ->_ref : substr ($ this ->_ref , 0 , $ pos );
139- $ file = $ context ->resolveRelativeUri ($ file );
140- $ jsonPointer = ($ pos === false ) ? '' : substr ($ this ->_ref , $ pos + 1 );
141-
142- // TODO could be a good idea to cache loaded files in current context to avoid loading the same files over and over again
143- $ fileContent = $ this ->fetchReferencedFile ($ file );
144- $ referencedData = $ this ->resolveJsonPointer ($ jsonPointer , $ fileContent );
145-
146- /** @var $referencedObject SpecObjectInterface */
147- $ referencedObject = new $ this ->_to ($ referencedData );
148- if ($ jsonPointer === '' ) {
149- $ referencedObject ->setReferenceContext (new ReferenceContext ($ referencedObject , $ file ));
150- } else {
151- // TODO resolving references recursively does not work as we do not know the base type of the file at this point
152- // $referencedObject->resolveReferences(new ReferenceContext($referencedObject, $file));
153- }
154-
155- return $ referencedObject ;
156- }
157-
158- private function resolveJsonPointer ($ jsonPointer , $ currentReference )
159- {
160- if ($ jsonPointer === '' ) {
161- // empty pointer references the whole document
162- return $ currentReference ;
163- }
164- $ pointerParts = explode ('/ ' , ltrim ($ jsonPointer , '/ ' ));
165- foreach ($ pointerParts as $ part ) {
166- $ part = strtr ($ part , [
167- '~1 ' => '/ ' ,
168- '~0 ' => '~ ' ,
169- ]);
170-
171- if (is_array ($ currentReference ) || $ currentReference instanceof \ArrayAccess) {
172- if (!isset ($ currentReference [$ part ])) {
173- throw new UnresolvableReferenceException (
174- "Failed to resolve Reference ' $ this ->_ref ' to $ this ->_to Object: path $ jsonPointer does not exist in referenced object. "
175- );
176- }
177- $ currentReference = $ currentReference [$ part ];
178- } elseif (is_object ($ currentReference )) {
179- if (!isset ($ currentReference ->$ part )) {
180- throw new UnresolvableReferenceException (
181- "Failed to resolve Reference ' $ this ->_ref ' to $ this ->_to Object: path $ jsonPointer does not exist in referenced object. "
182- );
183- }
184- $ currentReference = $ currentReference ->$ part ;
147+ $ jsonReference = $ this ->_jsonReference ;
148+ try {
149+ if ($ jsonReference ->getDocumentUri () === '' ) {
150+ // TODO type error if resolved object does not match $this->_to ?
151+ return $ jsonReference ->getJsonPointer ()->evaluate ($ context ->getBaseSpec ());
152+ }
153+ $ file = $ context ->resolveRelativeUri ($ jsonReference ->getDocumentUri ());
154+ // TODO could be a good idea to cache loaded files in current context to avoid loading the same files over and over again
155+ $ referencedDocument = $ this ->fetchReferencedFile ($ file );
156+ $ referencedData = $ jsonReference ->getJsonPointer ()->evaluate ($ referencedDocument );
157+
158+ /** @var $referencedObject SpecObjectInterface */
159+ $ referencedObject = new $ this ->_to ($ referencedData );
160+ if ($ jsonReference ->getJsonPointer ()->getPointer () === '' ) {
161+ $ referencedObject ->setReferenceContext (new ReferenceContext ($ referencedObject , $ file ));
185162 } else {
186- throw new UnresolvableReferenceException (
187- "Failed to resolve Reference ' $ this ->_ref ' to $ this ->_to Object: path $ jsonPointer does not exist in referenced object. "
188- );
163+ // TODO resolving references recursively does not work as we do not know the base type of the file at this point
164+ // $referencedObject->resolveReferences(new ReferenceContext($referencedObject, $file));
189165 }
190- }
191166
192- return $ currentReference ;
167+ return $ referencedObject ;
168+ } catch (NonexistentJsonPointerReferenceException $ e ) {
169+ throw new UnresolvableReferenceException ("Failed to resolve Reference ' $ this ->_ref ' to $ this ->_to Object: " . $ e ->getMessage (), 0 , $ e );
170+ }
193171 }
194172
195173 /**
0 commit comments