From b80053b620826810b38211b3c5f935ba9cddf6b3 Mon Sep 17 00:00:00 2001 From: Erayd Date: Tue, 3 Oct 2017 13:49:49 +1300 Subject: [PATCH] Backports for 5.2.2 (Part 3) (#450) * Update php-csfixer rules to address problem in 2.7 & new multiline rule (#449) * Update php-csfixer rules to address problem in 2.7 & new multiline rule * yoda_style in 2.7 is dangerous and may result in logic errors. In some cases, it also results in invalid syntax. * multiline comments prefixed with // now seem to be misaligned, and this cannot be disabled, so have changed the relevant comment. * PHP-5.3 is not available on trusty, so explicitly specify precise for 5.3 * Add proper recursive handling for $ref resolution base (#448) Fixes #447 Note that this patch does not check whether a given container is actually a schema when recursing into it. In most cases this will not matter, however it does mean that in some edge cases it will attempt to resolve a `$ref` in a context where ref is actually not part of the spec. Limiting resolution to schema-context containers is outside the scope of this patch, but can be added later. --- .php_cs.dist | 2 + .travis.yml | 1 + .../Constraints/CollectionConstraint.php | 12 +++--- src/JsonSchema/SchemaStorage.php | 41 +++++++++++++++---- src/JsonSchema/Uri/UriResolver.php | 11 +++++ src/JsonSchema/Validator.php | 9 +++- tests/Uri/UriResolverTest.php | 33 +++++++++++++++ 7 files changed, 94 insertions(+), 15 deletions(-) diff --git a/.php_cs.dist b/.php_cs.dist index 9b7f040e..6b7d9b92 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -11,6 +11,7 @@ $config '@PSR2' => true, '@Symfony' => true, // additionally + 'align_multiline_comment' => array('comment_type' => 'phpdocs_like'), 'array_syntax' => array('syntax' => 'long'), 'binary_operator_spaces' => false, 'concat_space' => array('spacing' => 'one'), @@ -24,6 +25,7 @@ $config 'pre_increment' => false, 'trailing_comma_in_multiline_array' => false, 'simplified_null_return' => false, + 'yoda_style' => null, )) ->setFinder($finder) ; diff --git a/.travis.yml b/.travis.yml index 069d8bec..3462c9f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ matrix: fast_finish: true include: - php: 5.3 + dist: precise - php: 5.4 - php: 5.5 - php: 5.6 diff --git a/src/JsonSchema/Constraints/CollectionConstraint.php b/src/JsonSchema/Constraints/CollectionConstraint.php index 3c594b3c..49841a49 100644 --- a/src/JsonSchema/Constraints/CollectionConstraint.php +++ b/src/JsonSchema/Constraints/CollectionConstraint.php @@ -85,8 +85,8 @@ protected function validateItems(&$value, $schema = null, JsonPointer $path = nu $validator->check($v, $schema->items, $k_path, $i); } - unset($v); // remove dangling reference to prevent any future bugs - // caused by accidentally using $v elsewhere + unset($v); /* remove dangling reference to prevent any future bugs + * caused by accidentally using $v elsewhere */ $this->addErrors($typeValidator->getErrors()); $this->addErrors($validator->getErrors()); } else { @@ -109,8 +109,8 @@ protected function validateItems(&$value, $schema = null, JsonPointer $path = nu $this->errors = $initErrors; } } - unset($v); // remove dangling reference to prevent any future bugs - // caused by accidentally using $v elsewhere + unset($v); /* remove dangling reference to prevent any future bugs + * caused by accidentally using $v elsewhere */ } } else { // Defined item type definitions @@ -132,8 +132,8 @@ protected function validateItems(&$value, $schema = null, JsonPointer $path = nu } } } - unset($v); // remove dangling reference to prevent any future bugs - // caused by accidentally using $v elsewhere + unset($v); /* remove dangling reference to prevent any future bugs + * caused by accidentally using $v elsewhere */ // Treat when we have more schema definitions than values, not for empty arrays if (count($value) > 0) { diff --git a/src/JsonSchema/SchemaStorage.php b/src/JsonSchema/SchemaStorage.php index 9a1caeb4..1088fbc2 100644 --- a/src/JsonSchema/SchemaStorage.php +++ b/src/JsonSchema/SchemaStorage.php @@ -5,7 +5,6 @@ use JsonSchema\Constraints\BaseConstraint; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\UnresolvableJsonPointerException; -use JsonSchema\Iterator\ObjectIterator; use JsonSchema\Uri\UriResolver; use JsonSchema\Uri\UriRetriever; @@ -69,14 +68,42 @@ public function addSchema($id, $schema = null) } } - $objectIterator = new ObjectIterator($schema); - foreach ($objectIterator as $toResolveSchema) { - if (property_exists($toResolveSchema, '$ref') && is_string($toResolveSchema->{'$ref'})) { - $jsonPointer = new JsonPointer($this->uriResolver->resolve($toResolveSchema->{'$ref'}, $id)); - $toResolveSchema->{'$ref'} = (string) $jsonPointer; + // resolve references + $this->expandRefs($schema, $id); + + $this->schemas[$id] = $schema; + } + + /** + * Recursively resolve all references against the provided base + * + * @param mixed $schema + * @param string $base + */ + private function expandRefs(&$schema, $base = null) + { + if (!is_object($schema)) { + if (is_array($schema)) { + foreach ($schema as &$member) { + $this->expandRefs($member, $base); + } } + + return; + } + + if (property_exists($schema, 'id') && is_string($schema->id)) { + $base = $this->uriResolver->resolve($schema->id, $base); + } + + if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) { + $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base)); + $schema->{'$ref'} = (string) $refPointer; + } + + foreach ($schema as &$member) { + $this->expandRefs($member, $base); } - $this->schemas[$id] = $schema; } /** diff --git a/src/JsonSchema/Uri/UriResolver.php b/src/JsonSchema/Uri/UriResolver.php index f26046d0..ced7a8da 100644 --- a/src/JsonSchema/Uri/UriResolver.php +++ b/src/JsonSchema/Uri/UriResolver.php @@ -76,6 +76,17 @@ public function generate(array $components) */ public function resolve($uri, $baseUri = null) { + // treat non-uri base as local file path + if (!is_null($baseUri) && !filter_var($baseUri, \FILTER_VALIDATE_URL)) { + if (is_file($baseUri)) { + $baseUri = 'file://' . realpath($baseUri); + } elseif (is_dir($baseUri)) { + $baseUri = 'file://' . realpath($baseUri) . '/'; + } else { + $baseUri = 'file://' . getcwd() . '/' . $baseUri; + } + } + if ($uri == '') { return $baseUri; } diff --git a/src/JsonSchema/Validator.php b/src/JsonSchema/Validator.php index 7b6a8077..0e3b4709 100644 --- a/src/JsonSchema/Validator.php +++ b/src/JsonSchema/Validator.php @@ -54,12 +54,17 @@ public function validate(&$value, $schema = null, $checkMode = null) } // add provided schema to SchemaStorage with internal URI to allow internal $ref resolution - $this->factory->getSchemaStorage()->addSchema(SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI, $schema); + if (is_object($schema) && property_exists($schema, 'id')) { + $schemaURI = $schema->id; + } else { + $schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI; + } + $this->factory->getSchemaStorage()->addSchema($schemaURI, $schema); $validator = $this->factory->createInstanceFor('schema'); $validator->check( $value, - $this->factory->getSchemaStorage()->getSchema(SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI) + $this->factory->getSchemaStorage()->getSchema($schemaURI) ); $this->factory->setConfig($initialCheckMode); diff --git a/tests/Uri/UriResolverTest.php b/tests/Uri/UriResolverTest.php index 456b13d3..051c2a58 100644 --- a/tests/Uri/UriResolverTest.php +++ b/tests/Uri/UriResolverTest.php @@ -190,4 +190,37 @@ public function testReversable() // check that the recombined URI matches the original input $this->assertEquals($uri, $this->resolver->generate($split)); } + + public function testRelativeFileAsRoot() + { + $this->assertEquals( + 'file://' . getcwd() . '/src/JsonSchema/Validator.php', + $this->resolver->resolve( + 'Validator.php', + 'src/JsonSchema/SchemaStorage.php' + ) + ); + } + + public function testRelativeDirectoryAsRoot() + { + $this->assertEquals( + 'file://' . getcwd() . '/src/JsonSchema/Validator.php', + $this->resolver->resolve( + 'Validator.php', + 'src/JsonSchema' + ) + ); + } + + public function testRelativeNonExistentFileAsRoot() + { + $this->assertEquals( + 'file://' . getcwd() . '/resolved.file', + $this->resolver->resolve( + 'resolved.file', + 'test.file' + ) + ); + } }