77
88namespace cebe \openapi ;
99
10- use cebe \openapi \exceptions \ReadonlyPropertyException ;
1110use cebe \openapi \exceptions \TypeErrorException ;
1211use cebe \openapi \exceptions \UnknownPropertyException ;
12+ use cebe \openapi \json \JsonPointer ;
13+ use cebe \openapi \json \JsonReference ;
1314use cebe \openapi \spec \Reference ;
1415use cebe \openapi \spec \Type ;
1516
1920 * Implements property management and validation basics.
2021 *
2122 */
22- abstract class SpecBaseObject implements SpecObjectInterface
23+ abstract class SpecBaseObject implements SpecObjectInterface, DocumentContextInterface
2324{
2425 private $ _properties = [];
2526 private $ _errors = [];
27+ private $ _recursing = false ;
28+
29+ private $ _baseDocument ;
30+ private $ _jsonPointer ;
31+
2632
2733 /**
2834 * @return array array of attributes available in this object.
@@ -146,6 +152,12 @@ private function instantiate($type, $data)
146152 */
147153 public function getSerializableData ()
148154 {
155+ if ($ this ->_recursing ) {
156+ // return a reference
157+ return (object ) ['$ref ' => JsonReference::createFromUri ('' , $ this ->getDocumentPosition ())->getReference ()];
158+ }
159+ $ this ->_recursing = true ;
160+
149161 $ data = $ this ->_properties ;
150162 foreach ($ data as $ k => $ v ) {
151163 if ($ v instanceof SpecObjectInterface) {
@@ -166,6 +178,9 @@ public function getSerializableData()
166178 }
167179 }
168180 }
181+
182+ $ this ->_recursing = false ;
183+
169184 return (object ) $ data ;
170185 }
171186
@@ -176,19 +191,36 @@ public function getSerializableData()
176191 */
177192 public function validate (): bool
178193 {
194+ // avoid recursion to get stuck in a loop
195+ if ($ this ->_recursing ) {
196+ return true ;
197+ }
198+ $ this ->_recursing = true ;
199+ $ valid = true ;
179200 foreach ($ this ->_properties as $ v ) {
180201 if ($ v instanceof SpecObjectInterface) {
181- $ v ->validate ();
202+ if (!$ v ->validate ()) {
203+ $ valid = false ;
204+ }
182205 } elseif (is_array ($ v )) {
183206 foreach ($ v as $ item ) {
184207 if ($ item instanceof SpecObjectInterface) {
185- $ item ->validate ();
208+ if (!$ item ->validate ()) {
209+ $ valid = false ;
210+ }
186211 }
187212 }
188213 }
189214 }
215+ $ this ->_recursing = false ;
216+
190217 $ this ->performValidation ();
191- return \count ($ this ->getErrors ()) === 0 ;
218+
219+ if (!empty ($ this ->_errors )) {
220+ $ valid = false ;
221+ }
222+
223+ return $ valid ;
192224 }
193225
194226 /**
@@ -197,7 +229,21 @@ public function validate(): bool
197229 */
198230 public function getErrors (): array
199231 {
200- $ errors = [$ this ->_errors ];
232+ // avoid recursion to get stuck in a loop
233+ if ($ this ->_recursing ) {
234+ return [];
235+ }
236+ $ this ->_recursing = true ;
237+
238+ if (($ pos = $ this ->getDocumentPosition ()) !== null ) {
239+ $ errors = [
240+ array_map (function ($ e ) use ($ pos ) {
241+ return "[ {$ pos ->getPointer ()}] $ e " ;
242+ }, $ this ->_errors )
243+ ];
244+ } else {
245+ $ errors = [$ this ->_errors ];
246+ }
201247 foreach ($ this ->_properties as $ v ) {
202248 if ($ v instanceof SpecObjectInterface) {
203249 $ errors [] = $ v ->getErrors ();
@@ -209,6 +255,9 @@ public function getErrors(): array
209255 }
210256 }
211257 }
258+
259+ $ this ->_recursing = false ;
260+
212261 return array_merge (...$ errors );
213262 }
214263
@@ -331,4 +380,48 @@ public function setReferenceContext(ReferenceContext $context)
331380 }
332381 }
333382 }
383+
384+ /**
385+ * Provide context information to the object.
386+ *
387+ * Context information contains a reference to the base object where it is contained in
388+ * as well as a JSON pointer to its position.
389+ * @param SpecObjectInterface $baseDocument
390+ * @param JsonPointer $jsonPointer
391+ */
392+ public function setDocumentContext (SpecObjectInterface $ baseDocument , JsonPointer $ jsonPointer )
393+ {
394+ $ this ->_baseDocument = $ baseDocument ;
395+ $ this ->_jsonPointer = $ jsonPointer ;
396+
397+ foreach ($ this ->_properties as $ property => $ value ) {
398+ if ($ value instanceof DocumentContextInterface) {
399+ $ value ->setDocumentContext ($ baseDocument , $ jsonPointer ->append ($ property ));
400+ } elseif (is_array ($ value )) {
401+ foreach ($ value as $ k => $ item ) {
402+ if ($ item instanceof DocumentContextInterface) {
403+ $ item ->setDocumentContext ($ baseDocument , $ jsonPointer ->append ($ property )->append ($ k ));
404+ }
405+ }
406+ }
407+ }
408+ }
409+
410+ /**
411+ * @return SpecObjectInterface|null returns the base document where this object is located in.
412+ * Returns `null` if no context information was provided by [[setDocumentContext]].
413+ */
414+ public function getBaseDocument (): ?SpecObjectInterface
415+ {
416+ return $ this ->_baseDocument ;
417+ }
418+
419+ /**
420+ * @return JsonPointer|null returns a JSON pointer describing the position of this object in the base document.
421+ * Returns `null` if no context information was provided by [[setDocumentContext]].
422+ */
423+ public function getDocumentPosition (): ?JsonPointer
424+ {
425+ return $ this ->_jsonPointer ;
426+ }
334427}
0 commit comments