Skip to content

Commit a189cd0

Browse files
committed
fix RefResolver to better support extend and $ref
1 parent 975f1b0 commit a189cd0

2 files changed

Lines changed: 136 additions & 3 deletions

File tree

src/JsonSchema/RefResolver.php

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,15 @@ public function resolve($schema, $sourceUri = null)
8383
return;
8484
}
8585

86-
if (null === $sourceUri && ! empty($schema->id)) {
87-
$sourceUri = $schema->id;
86+
if (!empty($schema->id)) {
87+
$sourceUri = $this->getUriRetriever()->resolve($schema->id, $sourceUri);
8888
}
8989

9090
// Resolve $ref first
9191
$this->resolveRef($schema, $sourceUri);
92+
// Resolve extends first
93+
$this->resolveExtends($schema, $sourceUri);
94+
9295

9396
// These properties are just schemas
9497
// eg. items can be a schema or an array of schemas
@@ -189,6 +192,69 @@ public function resolveRef($schema, $sourceUri)
189192
}
190193
}
191194

195+
/**
196+
* Look for the $ref property in the object. If found, remove the
197+
* reference and augment this object with the contents of another
198+
* schema.
199+
*
200+
* @param object $schema JSON Schema to flesh out
201+
* @param string $sourceUri URI where this schema was located
202+
*/
203+
public function resolveExtends($schema, $sourceUri)
204+
{
205+
if (empty($schema->extends)) {
206+
return;
207+
}
208+
if(is_object($schema->extends)) {
209+
self::merge($schema, $schema->extends);
210+
} else {
211+
if(is_array($schema->extends)) {
212+
foreach($schema->extends as $extends) {
213+
// yeah, some copy paste here
214+
if(is_object($schema)) {
215+
self::merge($schema, $schema);
216+
} else {
217+
$refSchema = $this->fetchRef($extends, $sourceUri);
218+
$refSchema = $this->getUriRetriever()->resolvePointer($refSchema, $extends);
219+
self::merge($schema, $refSchema);
220+
}
221+
}
222+
} else {
223+
$refSchema = $this->fetchRef($schema->extends, $sourceUri);
224+
$refSchema = $this->getUriRetriever()->resolvePointer($refSchema, $schema->extends);
225+
self::merge($schema, $refSchema);
226+
}
227+
}
228+
unset($schema->extends);
229+
}
230+
231+
/**
232+
* recursively merges all fields of $b into $a
233+
* fields in a win
234+
* @param stdObject $a
235+
* @param stdObject $b
236+
* @return void
237+
*/
238+
static function merge($a, $b) {
239+
if(!$a) return;
240+
if(!$b) return;
241+
foreach($b as $k=>$v) {
242+
if(is_object($v)) {
243+
if(!isset($a->$k)) $a->$k = new \stdClass;
244+
self::merge($a->$k, $b->$k);
245+
} elseif(is_array($v)) {
246+
if(!isset($a->$k)) {
247+
$a->$k = $b->$k;
248+
} elseif(is_array($a->$k)) {
249+
$a->$k = array_merge_recursive($b->$k, $a->$k); // a should win
250+
}
251+
} else {
252+
if(isset($a->$k)) continue; // a should win
253+
$a->$k = $b->$k;
254+
}
255+
}
256+
}
257+
192258
/**
193259
* Set URI Retriever for use with the Ref Resolver
194260
*

tests/JsonSchema/Tests/RefResolverTest.php

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* file that was distributed with this source code.
88
*/
99

10-
namespace JsonSchema\Tests;
10+
namespace JsonSchema;
1111

1212
class RefResolverTest extends \PHPUnit_Framework_TestCase
1313
{
@@ -184,4 +184,71 @@ public function refProvider() {
184184
),
185185
);
186186
}
187+
188+
public function testMerge() {
189+
$a = (object) array('a' => '1');
190+
$b = new \stdClass;
191+
RefResolver::merge($a, $b);
192+
$this->assertEquals((object)array('a'=>'1'), $a);
193+
194+
$a = (object) array('a' => '1');
195+
$b = (object) array('a' => '2');
196+
RefResolver::merge($a, $b);
197+
$this->assertEquals((object)array('a'=>'1'), $a);
198+
199+
$a = (object) array('a' => array(1,2,3));
200+
$b = (object) array('a' => array(4));
201+
RefResolver::merge($a, $b);
202+
$this->assertEquals((object)array('a'=>array(4,1,2,3)), $a); // $b values are prependet :( not nice but no issue
203+
204+
$a = new \stdClass;
205+
$b = (object) array('a' => array(4));
206+
RefResolver::merge($a, $b);
207+
$this->assertEquals((object)array('a'=>array(4)), $a);
208+
209+
$a = (object) array('a' => array(1,2,3));
210+
$b = new \stdClass;
211+
RefResolver::merge($a, $b);
212+
$this->assertEquals((object)array('a'=>array(1,2,3)), $a);
213+
214+
$a = (object) array('a' => 'in a');
215+
$b = (object) array('b' => 'from b');
216+
RefResolver::merge($a, $b);
217+
$this->assertEquals((object)array('a' => 'in a', 'b' => 'from b'), $a);
218+
219+
$a = null;
220+
$b = (object) array('b' => 'from b');
221+
RefResolver::merge($a, $b);
222+
$this->assertEquals(null, $a);
223+
224+
$a = (object) array('a' => 'in a');
225+
$b = null;
226+
RefResolver::merge($a, $b);
227+
$this->assertEquals((object) array('a' => 'in a'), $a);
228+
229+
$a = (object) array('a' => '1', 'c'=>(object)array('d'=>'from a'));
230+
$b = (object) array('a' => '1', 'c'=>(object)array('d'=>'from b'));
231+
RefResolver::merge($a, $b);
232+
$this->assertEquals((object) array('a' => '1', 'c'=>(object)array('d'=>'from a')), $a);
233+
234+
$a = (object) array('a' => '1');
235+
$b = (object) array('a' => '1', 'c'=>(object)array('d'=>'from b'));
236+
RefResolver::merge($a, $b);
237+
$this->assertEquals((object) array('a' => '1', 'c'=>(object)array('d'=>'from b')), $a);
238+
239+
$a = (object) array('a' => '1');
240+
$b = (object) array('a' => '1', 'c'=>'from b');
241+
RefResolver::merge($a, $b);
242+
$this->assertEquals((object) array('a' => '1', 'c'=>'from b'), $a);
243+
244+
$a = (object) array('a' => '1', 'c'=>'from a');
245+
$b = (object) array('a' => '1');
246+
RefResolver::merge($a, $b);
247+
$this->assertEquals((object) array('a' => '1', 'c'=>'from a'), $a);
248+
249+
$a = (object) array('fields' => array('1 from a', '2 from a'));
250+
$b = (object) array('fields' => array('1 from b', '2 from b'));
251+
RefResolver::merge($a, $b);
252+
$this->assertEquals((object) array('fields' => array('1 from b', '2 from b', '1 from a', '2 from a')), $a);
253+
}
187254
}

0 commit comments

Comments
 (0)