Init Server Composer Components

This commit is contained in:
Eole 2016-01-21 10:29:26 +01:00
parent 35db27b0e6
commit a44cc1d2e3
177 changed files with 24745 additions and 0 deletions

View file

@ -0,0 +1,5 @@
/docs export-ignore
/tests export-ignore
.gitignore export-ignore
.travis.yml export-ignore
phpunit.dist.xml export-ignore

View file

@ -0,0 +1,29 @@
Copyright (c) 2008, Gradua Networks
Author: Bruno Prieto Reis
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Gradua Networks nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,55 @@
# JSON Schema for PHP
[![Build Status](https://travis-ci.org/justinrainbow/json-schema.svg?branch=master)](https://travis-ci.org/justinrainbow/json-schema)
[![Latest Stable Version](https://poser.pugx.org/justinrainbow/json-schema/v/stable.png)](https://packagist.org/packages/justinrainbow/json-schema)
[![Total Downloads](https://poser.pugx.org/justinrainbow/json-schema/downloads.png)](https://packagist.org/packages/justinrainbow/json-schema)
A PHP Implementation for validating `JSON` Structures against a given `Schema`.
See [json-schema](http://json-schema.org/) for more details.
## Installation
### Library
$ git clone https://github.com/justinrainbow/json-schema.git
### Dependencies
#### [`Composer`](https://github.com/composer/composer) (*will use the Composer ClassLoader*)
$ wget http://getcomposer.org/composer.phar
$ php composer.phar require justinrainbow/json-schema:~1.3
## Usage
```php
<?php
// Get the schema and data as objects
$retriever = new JsonSchema\Uri\UriRetriever;
$schema = $retriever->retrieve('file://' . realpath('schema.json'));
$data = json_decode(file_get_contents('data.json'));
// If you use $ref or if you are unsure, resolve those references here
// This modifies the $schema object
$refResolver = new JsonSchema\RefResolver($retriever);
$refResolver->resolve($schema, 'file://' . __DIR__);
// Validate
$validator = new JsonSchema\Validator();
$validator->check($data, $schema);
if ($validator->isValid()) {
echo "The supplied JSON validates against the schema.\n";
} else {
echo "JSON does not validate. Violations:\n";
foreach ($validator->getErrors() as $error) {
echo sprintf("[%s] %s\n", $error['property'], $error['message']);
}
}
```
## Running the tests
$ vendor/bin/phpunit

View file

@ -0,0 +1,245 @@
#!/usr/bin/env php
<?php
/**
* JSON schema validator
*
* @author Christian Weiske <christian.weiske@netresearch.de>
*/
/**
* Dead simple autoloader
*
* @param string $className Name of class to load
*
* @return void
*/
function __autoload($className)
{
$className = ltrim($className, '\\');
$fileName = '';
$namespace = '';
if ($lastNsPos = strrpos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
if (stream_resolve_include_path($fileName)) {
require_once $fileName;
}
}
/**
* Show the json parse error that happened last
*
* @return void
*/
function showJsonError()
{
$constants = get_defined_constants(true);
$json_errors = array();
foreach ($constants['json'] as $name => $value) {
if (!strncmp($name, 'JSON_ERROR_', 11)) {
$json_errors[$value] = $name;
}
}
echo 'JSON parse error: ' . $json_errors[json_last_error()] . "\n";
}
function getUrlFromPath($path)
{
if (parse_url($path, PHP_URL_SCHEME) !== null) {
//already an URL
return $path;
}
if ($path{0} == '/') {
//absolute path
return 'file://' . $path;
}
//relative path: make absolute
return 'file://' . getcwd() . '/' . $path;
}
/**
* Take a HTTP header value and split it up into parts.
*
* @return array Key "_value" contains the main value, all others
* as given in the header value
*/
function parseHeaderValue($headerValue)
{
if (strpos($headerValue, ';') === false) {
return array('_value' => $headerValue);
}
$parts = explode(';', $headerValue);
$arData = array('_value' => array_shift($parts));
foreach ($parts as $part) {
list($name, $value) = explode('=', $part);
$arData[$name] = trim($value, ' "\'');
}
return $arData;
}
// support running this tool from git checkout
if (is_dir(__DIR__ . '/../src/JsonSchema')) {
set_include_path(__DIR__ . '/../src' . PATH_SEPARATOR . get_include_path());
}
$arOptions = array();
$arArgs = array();
array_shift($argv);//script itself
foreach ($argv as $arg) {
if ($arg{0} == '-') {
$arOptions[$arg] = true;
} else {
$arArgs[] = $arg;
}
}
if (count($arArgs) == 0
|| isset($arOptions['--help']) || isset($arOptions['-h'])
) {
echo <<<HLP
Validate schema
Usage: validate-json data.json
or: validate-json data.json schema.json
Options:
--dump-schema Output full schema and exit
--dump-schema-url Output URL of schema
-h --help Show this help
HLP;
exit(1);
}
if (count($arArgs) == 1) {
$pathData = $arArgs[0];
$pathSchema = null;
} else {
$pathData = $arArgs[0];
$pathSchema = getUrlFromPath($arArgs[1]);
}
$urlData = getUrlFromPath($pathData);
$context = stream_context_create(
array(
'http' => array(
'header' => array(
'Accept: */*',
'Connection: Close'
),
'max_redirects' => 5
)
)
);
$dataString = file_get_contents($pathData, false, $context);
if ($dataString == '') {
echo "Data file is not readable or empty.\n";
exit(3);
}
$data = json_decode($dataString);
unset($dataString);
if ($data === null) {
echo "Error loading JSON data file\n";
showJsonError();
exit(5);
}
if ($pathSchema === null) {
if (isset($http_response_header)) {
array_shift($http_response_header);//HTTP/1.0 line
foreach ($http_response_header as $headerLine) {
list($hName, $hValue) = explode(':', $headerLine, 2);
$hName = strtolower($hName);
if ($hName == 'link') {
//Link: <http://example.org/schema#>; rel="describedBy"
$hParts = parseHeaderValue($hValue);
if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') {
$pathSchema = trim($hParts['_value'], ' <>');
}
} else if ($hName == 'content-type') {
//Content-Type: application/my-media-type+json;
// profile=http://example.org/schema#
$hParts = parseHeaderValue($hValue);
if (isset($hParts['profile'])) {
$pathSchema = $hParts['profile'];
}
}
}
}
if (is_object($data) && property_exists($data, '$schema')) {
$pathSchema = $data->{'$schema'};
}
//autodetect schema
if ($pathSchema === null) {
echo "JSON data must be an object and have a \$schema property.\n";
echo "You can pass the schema file on the command line as well.\n";
echo "Schema autodetection failed.\n";
exit(6);
}
}
if ($pathSchema{0} == '/') {
$pathSchema = 'file://' . $pathSchema;
}
$resolver = new JsonSchema\Uri\UriResolver();
$retriever = new JsonSchema\Uri\UriRetriever();
try {
$urlSchema = $resolver->resolve($pathSchema, $urlData);
if (isset($arOptions['--dump-schema-url'])) {
echo $urlSchema . "\n";
exit();
}
$schema = $retriever->retrieve($urlSchema);
if ($schema === null) {
echo "Error loading JSON schema file\n";
echo $urlSchema . "\n";
showJsonError();
exit(2);
}
} catch (Exception $e) {
echo "Error loading JSON schema file\n";
echo $urlSchema . "\n";
echo $e->getMessage() . "\n";
exit(2);
}
$refResolver = new JsonSchema\RefResolver($retriever);
$refResolver->resolve($schema, $urlSchema);
if (isset($arOptions['--dump-schema'])) {
$options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
echo json_encode($schema, $options) . "\n";
exit();
}
try {
$validator = new JsonSchema\Validator();
$validator->check($data, $schema);
if ($validator->isValid()) {
echo "OK. The supplied JSON validates against the schema.\n";
} else {
echo "JSON does not validate. Violations:\n";
foreach ($validator->getErrors() as $error) {
echo sprintf("[%s] %s\n", $error['property'], $error['message']);
}
exit(23);
}
} catch (Exception $e) {
echo "JSON does not validate. Error:\n";
echo $e->getMessage() . "\n";
echo "Error code: " . $e->getCode() . "\n";
exit(24);
}
?>

View file

@ -0,0 +1,58 @@
{
"name": "justinrainbow/json-schema",
"description": "A library to validate a json schema.",
"keywords": ["json", "schema"],
"homepage": "https://github.com/justinrainbow/json-schema",
"type": "library",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Bruno Prieto Reis",
"email": "bruno.p.reis@gmail.com"
},
{
"name": "Justin Rainbow",
"email": "justin.rainbow@gmail.com"
},
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
},
{
"name": "Robert Schönthal",
"email": "seroscho@googlemail.com"
}
],
"repositories": [{
"type": "package",
"package": {
"name": "json-schema/JSON-Schema-Test-Suite",
"version": "1.1.0",
"source": {
"url": "https://github.com/json-schema/JSON-Schema-Test-Suite",
"type": "git",
"reference": "1.1.0"
}
}
}],
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"json-schema/JSON-Schema-Test-Suite": "1.1.0",
"phpunit/phpunit": "~3.7",
"phpdocumentor/phpdocumentor": "~2"
},
"autoload": {
"psr-4": { "JsonSchema\\": "src/JsonSchema/" }
},
"autoload-dev": {
"psr-4": { "JsonSchema\\Tests\\": "tests/JsonSchema/Tests/" }
},
"bin": ["bin/validate-json"],
"extra": {
"branch-alias": {
"dev-master": "1.4.x-dev"
}
}
}

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
verbose="true"
>
<testsuites>
<testsuite name="JSON Schema Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/JsonSchema/</directory>
</whitelist>
</filter>
</phpunit>

View file

@ -0,0 +1,112 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
/**
* The CollectionConstraint Constraints, validates an array against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class CollectionConstraint extends Constraint
{
/**
* {@inheritDoc}
*/
public function check($value, $schema = null, $path = null, $i = null)
{
// Verify minItems
if (isset($schema->minItems) && count($value) < $schema->minItems) {
$this->addError($path, "There must be a minimum of " . $schema->minItems . " items in the array", 'minItems', array('minItems' => $schema->minItems,));
}
// Verify maxItems
if (isset($schema->maxItems) && count($value) > $schema->maxItems) {
$this->addError($path, "There must be a maximum of " . $schema->maxItems . " items in the array", 'maxItems', array('maxItems' => $schema->maxItems,));
}
// Verify uniqueItems
if (isset($schema->uniqueItems) && $schema->uniqueItems) {
$unique = $value;
if (is_array($value) && count($value)) {
$unique = array_map(function($e) { return var_export($e, true); }, $value);
}
if (count(array_unique($unique)) != count($value)) {
$this->addError($path, "There are no duplicates allowed in the array", 'uniqueItems');
}
}
// Verify items
if (isset($schema->items)) {
$this->validateItems($value, $schema, $path, $i);
}
}
/**
* Validates the items
*
* @param array $value
* @param \stdClass $schema
* @param string $path
* @param string $i
*/
protected function validateItems($value, $schema = null, $path = null, $i = null)
{
if (is_object($schema->items)) {
// just one type definition for the whole array
foreach ($value as $k => $v) {
$initErrors = $this->getErrors();
// First check if its defined in "items"
$this->checkUndefined($v, $schema->items, $path, $k);
// Recheck with "additionalItems" if the first test fails
if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) {
$secondErrors = $this->getErrors();
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
}
// Reset errors if needed
if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) {
$this->errors = $secondErrors;
} else if (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) {
$this->errors = $initErrors;
}
}
} else {
// Defined item type definitions
foreach ($value as $k => $v) {
if (array_key_exists($k, $schema->items)) {
$this->checkUndefined($v, $schema->items[$k], $path, $k);
} else {
// Additional items
if (property_exists($schema, 'additionalItems')) {
if ($schema->additionalItems !== false) {
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
} else {
$this->addError(
$path, 'The item ' . $i . '[' . $k . '] is not defined and the definition does not allow additional items', 'additionalItems', array('additionalItems' => $schema->additionalItems,));
}
} else {
// Should be valid against an empty schema
$this->checkUndefined($v, new \stdClass(), $path, $k);
}
}
}
// Treat when we have more schema definitions than values, not for empty arrays
if(count($value) > 0) {
for ($k = count($value); $k < count($schema->items); $k++) {
$this->checkUndefined(new UndefinedConstraint(), $schema->items[$k], $path, $k);
}
}
}
}
}

View file

@ -0,0 +1,291 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Uri\UriRetriever;
/**
* The Base Constraints, all Validators should extend this class
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
abstract class Constraint implements ConstraintInterface
{
protected $checkMode = self::CHECK_MODE_NORMAL;
protected $uriRetriever;
protected $errors = array();
protected $inlineSchemaProperty = '$schema';
const CHECK_MODE_NORMAL = 1;
const CHECK_MODE_TYPE_CAST = 2;
/**
* @var null|Factory
*/
private $factory;
/**
* @param int $checkMode
* @param UriRetriever $uriRetriever
* @param Factory $factory
*/
public function __construct($checkMode = self::CHECK_MODE_NORMAL, UriRetriever $uriRetriever = null, Factory $factory = null)
{
$this->checkMode = $checkMode;
$this->uriRetriever = $uriRetriever;
$this->factory = $factory;
}
/**
* @return UriRetriever $uriRetriever
*/
public function getUriRetriever()
{
if (is_null($this->uriRetriever))
{
$this->setUriRetriever(new UriRetriever);
}
return $this->uriRetriever;
}
/**
* @return Factory
*/
public function getFactory()
{
if (!$this->factory) {
$this->factory = new Factory($this->getUriRetriever());
}
return $this->factory;
}
/**
* @param UriRetriever $uriRetriever
*/
public function setUriRetriever(UriRetriever $uriRetriever)
{
$this->uriRetriever = $uriRetriever;
}
/**
* {@inheritDoc}
*/
public function addError($path, $message, $constraint='', array $more=null)
{
$error = array(
'property' => $path,
'message' => $message,
'constraint' => $constraint,
);
if (is_array($more) && count($more) > 0)
{
$error += $more;
}
$this->errors[] = $error;
}
/**
* {@inheritDoc}
*/
public function addErrors(array $errors)
{
$this->errors = array_merge($this->errors, $errors);
}
/**
* {@inheritDoc}
*/
public function getErrors()
{
return $this->errors;
}
/**
* {@inheritDoc}
*/
public function isValid()
{
return !$this->getErrors();
}
/**
* Clears any reported errors. Should be used between
* multiple validation checks.
*/
public function reset()
{
$this->errors = array();
}
/**
* Bubble down the path
*
* @param string $path Current path
* @param mixed $i What to append to the path
*
* @return string
*/
protected function incrementPath($path, $i)
{
if ($path !== '') {
if (is_int($i)) {
$path .= '[' . $i . ']';
} elseif ($i == '') {
$path .= '';
} else {
$path .= '.' . $i;
}
} else {
$path = $i;
}
return $path;
}
/**
* Validates an array
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkArray($value, $schema = null, $path = null, $i = null)
{
$validator = $this->getFactory()->createInstanceFor('collection');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Validates an object
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
* @param mixed $patternProperties
*/
protected function checkObject($value, $schema = null, $path = null, $i = null, $patternProperties = null)
{
$validator = $this->getFactory()->createInstanceFor('object');
$validator->check($value, $schema, $path, $i, $patternProperties);
$this->addErrors($validator->getErrors());
}
/**
* Validates the type of a property
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkType($value, $schema = null, $path = null, $i = null)
{
$validator = $this->getFactory()->createInstanceFor('type');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Checks a undefined element
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkUndefined($value, $schema = null, $path = null, $i = null)
{
$validator = $this->getFactory()->createInstanceFor('undefined');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Checks a string element
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkString($value, $schema = null, $path = null, $i = null)
{
$validator = $this->getFactory()->createInstanceFor('string');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Checks a number element
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkNumber($value, $schema = null, $path = null, $i = null)
{
$validator = $this->getFactory()->createInstanceFor('number');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Checks a enum element
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkEnum($value, $schema = null, $path = null, $i = null)
{
$validator = $this->getFactory()->createInstanceFor('enum');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
protected function checkFormat($value, $schema = null, $path = null, $i = null)
{
$validator = $this->getFactory()->createInstanceFor('format');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* @param string $uri JSON Schema URI
* @return string JSON Schema contents
*/
protected function retrieveUri($uri)
{
if (null === $this->uriRetriever) {
$this->setUriRetriever(new UriRetriever);
}
$jsonSchema = $this->uriRetriever->retrieve($uri);
// TODO validate using schema
return $jsonSchema;
}
}

View file

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
/**
* The Constraints Interface
*
* @author Robert Schönthal <seroscho@googlemail.com>
*/
interface ConstraintInterface
{
/**
* returns all collected errors
*
* @return array
*/
public function getErrors();
/**
* adds errors to this validator
*
* @param array $errors
*/
public function addErrors(array $errors);
/**
* adds an error
*
* @param string $path
* @param string $message
* @param string $constraint the constraint/rule that is broken, e.g.: 'minLength'
* @param array $more more array elements to add to the error
*/
public function addError($path, $message, $constraint='', array $more=null);
/**
* checks if the validator has not raised errors
*
* @return boolean
*/
public function isValid();
/**
* invokes the validation of an element
*
* @abstract
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
public function check($value, $schema = null, $path = null, $i = null);
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
/**
* The EnumConstraint Constraints, validates an element against a given set of possibilities
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class EnumConstraint extends Constraint
{
/**
* {@inheritDoc}
*/
public function check($element, $schema = null, $path = null, $i = null)
{
// Only validate enum if the attribute exists
if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) {
return;
}
foreach ($schema->enum as $enum) {
$type = gettype($element);
if ($type === gettype($enum)) {
if ($type == "object") {
if ($element == $enum)
return;
} else {
if ($element === $enum)
return;
}
}
}
$this->addError($path, "Does not have a value in the enumeration " . print_r($schema->enum, true), 'enum', array('enum' => $schema->enum,));
}
}

View file

@ -0,0 +1,81 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\Uri\UriRetriever;
use JsonSchema\Validator;
/**
* Factory for centralize constraint initialization.
*/
class Factory
{
/**
* @var UriRetriever
*/
protected $uriRetriever;
/**
* @param UriRetriever $uriRetriever
*/
public function __construct(UriRetriever $uriRetriever = null)
{
if (!$uriRetriever) {
$uriRetriever = new UriRetriever();
}
$this->uriRetriever = $uriRetriever;
}
/**
* @return UriRetriever
*/
public function getUriRetriever()
{
return $this->uriRetriever;
}
/**
* Create a constraint instance for the given constraint name.
*
* @param string $constraintName
* @return ConstraintInterface|ObjectConstraint
* @throws InvalidArgumentException if is not possible create the constraint instance.
*/
public function createInstanceFor($constraintName)
{
switch ($constraintName) {
case 'array':
case 'collection':
return new CollectionConstraint(Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this);
case 'object':
return new ObjectConstraint(Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this);
case 'type':
return new TypeConstraint(Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this);
case 'undefined':
return new UndefinedConstraint(Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this);
case 'string':
return new StringConstraint(Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this);
case 'number':
return new NumberConstraint(Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this);
case 'enum':
return new EnumConstraint(Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this);
case 'format':
return new FormatConstraint(Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this);
case 'schema':
return new SchemaConstraint(Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this);
case 'validator':
return new Validator(Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this);
}
throw new InvalidArgumentException('Unknown constraint ' . $constraintName);
}
}

View file

@ -0,0 +1,181 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
/**
* Validates against the "format" property
*
* @author Justin Rainbow <justin.rainbow@gmail.com>
* @link http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
*/
class FormatConstraint extends Constraint
{
/**
* {@inheritDoc}
*/
public function check($element, $schema = null, $path = null, $i = null)
{
if (!isset($schema->format)) {
return;
}
switch ($schema->format) {
case 'date':
if (!$date = $this->validateDateTime($element, 'Y-m-d')) {
$this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format,));
}
break;
case 'time':
if (!$this->validateDateTime($element, 'H:i:s')) {
$this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format,));
}
break;
case 'date-time':
if (!$this->validateDateTime($element, 'Y-m-d\TH:i:s\Z') &&
!$this->validateDateTime($element, 'Y-m-d\TH:i:s.u\Z') &&
!$this->validateDateTime($element, 'Y-m-d\TH:i:sP') &&
!$this->validateDateTime($element, 'Y-m-d\TH:i:sO')
) {
$this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)), 'format', array('format' => $schema->format,));
}
break;
case 'utc-millisec':
if (!$this->validateDateTime($element, 'U')) {
$this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format,));
}
break;
case 'regex':
if (!$this->validateRegex($element)) {
$this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format,));
}
break;
case 'color':
if (!$this->validateColor($element)) {
$this->addError($path, "Invalid color", 'format', array('format' => $schema->format,));
}
break;
case 'style':
if (!$this->validateStyle($element)) {
$this->addError($path, "Invalid style", 'format', array('format' => $schema->format,));
}
break;
case 'phone':
if (!$this->validatePhone($element)) {
$this->addError($path, "Invalid phone number", 'format', array('format' => $schema->format,));
}
break;
case 'uri':
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
$this->addError($path, "Invalid URL format", 'format', array('format' => $schema->format,));
}
break;
case 'email':
if (null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE)) {
$this->addError($path, "Invalid email", 'format', array('format' => $schema->format,));
}
break;
case 'ip-address':
case 'ipv4':
if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
$this->addError($path, "Invalid IP address", 'format', array('format' => $schema->format,));
}
break;
case 'ipv6':
if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) {
$this->addError($path, "Invalid IP address", 'format', array('format' => $schema->format,));
}
break;
case 'host-name':
case 'hostname':
if (!$this->validateHostname($element)) {
$this->addError($path, "Invalid hostname", 'format', array('format' => $schema->format,));
}
break;
default:
// Empty as it should be:
// The value of this keyword is called a format attribute. It MUST be a string.
// A format attribute can generally only validate a given set of instance types.
// If the type of the instance to validate is not in this set, validation for
// this format attribute and instance SHOULD succeed.
// http://json-schema.org/latest/json-schema-validation.html#anchor105
break;
}
}
protected function validateDateTime($datetime, $format)
{
$dt = \DateTime::createFromFormat($format, $datetime);
if (!$dt) {
return false;
}
if ($datetime === $dt->format($format)) {
return true;
}
// handles the case where a non-6 digit microsecond datetime is passed
// which will fail the above string comparison because the passed
// $datetime may be '2000-05-01T12:12:12.123Z' but format() will return
// '2000-05-01T12:12:12.123000Z'
if ((strpos('u', $format) !== -1) && (intval($dt->format('u')) > 0)) {
return true;
}
return false;
}
protected function validateRegex($regex)
{
return false !== @preg_match('/' . $regex . '/', '');
}
protected function validateColor($color)
{
if (in_array(strtolower($color), array('aqua', 'black', 'blue', 'fuchsia',
'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple',
'red', 'silver', 'teal', 'white', 'yellow'))) {
return true;
}
return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color);
}
protected function validateStyle($style)
{
$properties = explode(';', rtrim($style, ';'));
$invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT);
return empty($invalidEntries);
}
protected function validatePhone($phone)
{
return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone);
}
protected function validateHostname($host)
{
return preg_match('/^[_a-z]+\.([_a-z]+\.?)+$/i', $host);
}
}

View file

@ -0,0 +1,83 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
/**
* The NumberConstraint Constraints, validates an number against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class NumberConstraint extends Constraint
{
/**
* {@inheritDoc}
*/
public function check($element, $schema = null, $path = null, $i = null)
{
// Verify minimum
if (isset($schema->exclusiveMinimum)) {
if (isset($schema->minimum)) {
if ($schema->exclusiveMinimum && $element === $schema->minimum) {
$this->addError($path, "Must have a minimum value greater than boundary value of " . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum,));
} else if ($element < $schema->minimum) {
$this->addError($path, "Must have a minimum value of " . $schema->minimum, 'minimum', array('minimum' => $schema->minimum,));
}
} else {
$this->addError($path, "Use of exclusiveMinimum requires presence of minimum", 'missingMinimum');
}
} else if (isset($schema->minimum) && $element < $schema->minimum) {
$this->addError($path, "Must have a minimum value of " . $schema->minimum, 'minimum', array('minimum' => $schema->minimum,));
}
// Verify maximum
if (isset($schema->exclusiveMaximum)) {
if (isset($schema->maximum)) {
if ($schema->exclusiveMaximum && $element === $schema->maximum) {
$this->addError($path, "Must have a maximum value less than boundary value of " . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum,));
} else if ($element > $schema->maximum) {
$this->addError($path, "Must have a maximum value of " . $schema->maximum, 'maximum', array('maximum' => $schema->maximum,));
}
} else {
$this->addError($path, "Use of exclusiveMaximum requires presence of maximum", 'missingMinimum');
}
} else if (isset($schema->maximum) && $element > $schema->maximum) {
$this->addError($path, "Must have a maximum value of " . $schema->maximum, 'maximum', array('maximum' => $schema->maximum,));
}
// Verify divisibleBy - Draft v3
if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) {
$this->addError($path, "Is not divisible by " . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy,));
}
// Verify multipleOf - Draft v4
if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) {
$this->addError($path, "Must be a multiple of " . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf,));
}
$this->checkFormat($element, $schema, $path, $i);
}
private function fmod($number1, $number2)
{
$modulus = fmod($number1, $number2);
$precision = abs(0.0000000001);
$diff = (float)($modulus - $number2);
if (-$precision < $diff && $diff < $precision) {
return 0.0;
}
$decimals1 = mb_strpos($number1, ".") ? mb_strlen($number1) - mb_strpos($number1, ".") - 1 : 0;
$decimals2 = mb_strpos($number2, ".") ? mb_strlen($number2) - mb_strpos($number2, ".") - 1 : 0;
return (float)round($modulus, max($decimals1, $decimals2));
}
}

View file

@ -0,0 +1,149 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
/**
* The ObjectConstraint Constraints, validates an object against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class ObjectConstraint extends Constraint
{
/**
* {@inheritDoc}
*/
function check($element, $definition = null, $path = null, $additionalProp = null, $patternProperties = null)
{
if ($element instanceof UndefinedConstraint) {
return;
}
$matches = array();
if ($patternProperties) {
$matches = $this->validatePatternProperties($element, $path, $patternProperties);
}
if ($definition) {
// validate the definition properties
$this->validateDefinition($element, $definition, $path);
}
// additional the element properties
$this->validateElement($element, $matches, $definition, $path, $additionalProp);
}
public function validatePatternProperties($element, $path, $patternProperties)
{
$try = array('/','#','+','~','%');
$matches = array();
foreach ($patternProperties as $pregex => $schema) {
$delimiter = '/';
// Choose delimiter. Necessary for patterns like ^/ , otherwise you get error
foreach ($try as $delimiter) {
if (strpos($pregex, $delimiter) === false) { // safe to use
break;
}
}
// Validate the pattern before using it to test for matches
if (@preg_match($delimiter. $pregex . $delimiter, '') === false) {
$this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex,));
continue;
}
foreach ($element as $i => $value) {
if (preg_match($delimiter . $pregex . $delimiter, $i)) {
$matches[] = $i;
$this->checkUndefined($value, $schema ? : new \stdClass(), $path, $i);
}
}
}
return $matches;
}
/**
* Validates the element properties
*
* @param \stdClass $element Element to validate
* @param array $matches Matches from patternProperties (if any)
* @param \stdClass $objectDefinition ObjectConstraint definition
* @param string $path Path to test?
* @param mixed $additionalProp Additional properties
*/
public function validateElement($element, $matches, $objectDefinition = null, $path = null, $additionalProp = null)
{
foreach ($element as $i => $value) {
$property = $this->getProperty($element, $i, new UndefinedConstraint());
$definition = $this->getProperty($objectDefinition, $i);
// no additional properties allowed
if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) {
$this->addError($path, "The property " . $i . " is not defined and the definition does not allow additional properties", 'additionalProp');
}
// additional properties defined
if (!in_array($i, $matches) && $additionalProp && !$definition) {
if ($additionalProp === true) {
$this->checkUndefined($value, null, $path, $i);
} else {
$this->checkUndefined($value, $additionalProp, $path, $i);
}
}
// property requires presence of another
$require = $this->getProperty($definition, 'requires');
if ($require && !$this->getProperty($element, $require)) {
$this->addError($path, "The presence of the property " . $i . " requires that " . $require . " also be present", 'requires');
}
if (!$definition) {
// normal property verification
$this->checkUndefined($value, new \stdClass(), $path, $i);
}
}
}
/**
* Validates the definition properties
*
* @param \stdClass $element Element to validate
* @param \stdClass $objectDefinition ObjectConstraint definition
* @param string $path Path?
*/
public function validateDefinition($element, $objectDefinition = null, $path = null)
{
foreach ($objectDefinition as $i => $value) {
$property = $this->getProperty($element, $i, new UndefinedConstraint());
$definition = $this->getProperty($objectDefinition, $i);
$this->checkUndefined($property, $definition, $path, $i);
}
}
/**
* retrieves a property from an object or array
*
* @param mixed $element Element to validate
* @param string $property Property to retrieve
* @param mixed $fallback Default value if property is not found
*
* @return mixed
*/
protected function getProperty($element, $property, $fallback = null)
{
if (is_array($element) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) {
return array_key_exists($property, $element) ? $element[$property] : $fallback;
} elseif (is_object($element)) {
return property_exists($element, $property) ? $element->$property : $fallback;
}
return $fallback;
}
}

View file

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Exception\InvalidArgumentException;
/**
* The SchemaConstraint Constraints, validates an element against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class SchemaConstraint extends Constraint
{
/**
* {@inheritDoc}
*/
public function check($element, $schema = null, $path = null, $i = null)
{
if ($schema !== null) {
// passed schema
$this->checkUndefined($element, $schema, '', '');
} elseif (property_exists($element, $this->inlineSchemaProperty)) {
// inline schema
$this->checkUndefined($element, $element->{$this->inlineSchemaProperty}, '', '');
} else {
throw new InvalidArgumentException('no schema found to verify against');
}
}
}

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
/**
* The StringConstraint Constraints, validates an string against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class StringConstraint extends Constraint
{
/**
* {@inheritDoc}
*/
public function check($element, $schema = null, $path = null, $i = null)
{
// Verify maxLength
if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) {
$this->addError($path, "Must be at most " . $schema->maxLength . " characters long", 'maxLength', array(
'maxLength' => $schema->maxLength,
));
}
//verify minLength
if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) {
$this->addError($path, "Must be at least " . $schema->minLength . " characters long", 'minLength', array(
'minLength' => $schema->minLength,
));
}
// Verify a regex pattern
if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#', $element)) {
$this->addError($path, "Does not match the regex pattern " . $schema->pattern, 'pattern', array(
'pattern' => $schema->pattern,
));
}
$this->checkFormat($element, $schema, $path, $i);
}
private function strlen($string)
{
if (extension_loaded('mbstring')) {
return mb_strlen($string, mb_detect_encoding($string));
} else {
return strlen($string);
}
}
}

View file

@ -0,0 +1,145 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Exception\InvalidArgumentException;
use UnexpectedValueException as StandardUnexpectedValueException;
/**
* The TypeConstraint Constraints, validates an element against a given type
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class TypeConstraint extends Constraint
{
/**
* @var array|string[] type wordings for validation error messages
*/
static $wording = array(
'integer' => 'an integer',
'number' => 'a number',
'boolean' => 'a boolean',
'object' => 'an object',
'array' => 'an array',
'string' => 'a string',
'null' => 'a null',
'any' => NULL, // validation of 'any' is always true so is not needed in message wording
0 => NULL, // validation of a false-y value is always true, so not needed as well
);
/**
* {@inheritDoc}
*/
public function check($value = null, $schema = null, $path = null, $i = null)
{
$type = isset($schema->type) ? $schema->type : null;
$isValid = true;
if (is_array($type)) {
// @TODO refactor
$validatedOneType = false;
$errors = array();
foreach ($type as $tp) {
$validator = new TypeConstraint($this->checkMode);
$subSchema = new \stdClass();
$subSchema->type = $tp;
$validator->check($value, $subSchema, $path, null);
$error = $validator->getErrors();
if (!count($error)) {
$validatedOneType = true;
break;
}
$errors = $error;
}
if (!$validatedOneType) {
$this->addErrors($errors);
return;
}
} elseif (is_object($type)) {
$this->checkUndefined($value, $type, $path);
} else {
$isValid = $this->validateType($value, $type);
}
if ($isValid === false) {
if (!isset(self::$wording[$type])) {
throw new StandardUnexpectedValueException(
sprintf(
"No wording for %s available, expected wordings are: [%s]",
var_export($type, true),
implode(', ', array_filter(self::$wording)))
);
}
$this->addError($path, ucwords(gettype($value)) . " value found, but " . self::$wording[$type] . " is required", 'type');
}
}
/**
* Verifies that a given value is of a certain type
*
* @param mixed $value Value to validate
* @param string $type TypeConstraint to check against
*
* @return boolean
*
* @throws InvalidArgumentException
*/
protected function validateType($value, $type)
{
//mostly the case for inline schema
if (!$type) {
return true;
}
if ('integer' === $type) {
return is_int($value);
}
if ('number' === $type) {
return is_numeric($value) && !is_string($value);
}
if ('boolean' === $type) {
return is_bool($value);
}
if ('object' === $type) {
return is_object($value);
//return ($this::CHECK_MODE_TYPE_CAST == $this->checkMode) ? is_array($value) : is_object($value);
}
if ('array' === $type) {
return is_array($value);
}
if ('string' === $type) {
return is_string($value);
}
if ('email' === $type) {
return is_string($value);
}
if ('null' === $type) {
return is_null($value);
}
if ('any' === $type) {
return true;
}
throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type);
}
}

View file

@ -0,0 +1,307 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\Uri\UriResolver;
/**
* The UndefinedConstraint Constraints
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class UndefinedConstraint extends Constraint
{
/**
* {@inheritDoc}
*/
public function check($value, $schema = null, $path = null, $i = null)
{
if (is_null($schema)) {
return;
}
if (!is_object($schema)) {
throw new InvalidArgumentException(
'Given schema must be an object in ' . $path
. ' but is a ' . gettype($schema)
);
}
$i = is_null($i) ? "" : $i;
$path = $this->incrementPath($path, $i);
// check special properties
$this->validateCommonProperties($value, $schema, $path);
// check allOf, anyOf, and oneOf properties
$this->validateOfProperties($value, $schema, $path);
// check known types
$this->validateTypes($value, $schema, $path, $i);
}
/**
* Validates the value against the types
*
* @param mixed $value
* @param mixed $schema
* @param string $path
* @param string $i
*/
public function validateTypes($value, $schema = null, $path = null, $i = null)
{
// check array
if (is_array($value)) {
$this->checkArray($value, $schema, $path, $i);
}
// check object
if (is_object($value) && (isset($schema->properties) || isset($schema->patternProperties) || isset($schema->additionalProperties))) {
$this->checkObject(
$value,
isset($schema->properties) ? $schema->properties : null,
$path,
isset($schema->additionalProperties) ? $schema->additionalProperties : null,
isset($schema->patternProperties) ? $schema->patternProperties : null
);
}
// check string
if (is_string($value)) {
$this->checkString($value, $schema, $path, $i);
}
// check numeric
if (is_numeric($value)) {
$this->checkNumber($value, $schema, $path, $i);
}
// check enum
if (isset($schema->enum)) {
$this->checkEnum($value, $schema, $path, $i);
}
}
/**
* Validates common properties
*
* @param mixed $value
* @param mixed $schema
* @param string $path
* @param string $i
*/
protected function validateCommonProperties($value, $schema = null, $path = null, $i = "")
{
// if it extends another schema, it must pass that schema as well
if (isset($schema->extends)) {
if (is_string($schema->extends)) {
$schema->extends = $this->validateUri($schema, $schema->extends);
}
if (is_array($schema->extends)) {
foreach ($schema->extends as $extends) {
$this->checkUndefined($value, $extends, $path, $i);
}
} else {
$this->checkUndefined($value, $schema->extends, $path, $i);
}
}
// Verify required values
if (is_object($value)) {
if (!($value instanceof UndefinedConstraint) && isset($schema->required) && is_array($schema->required) ) {
// Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...]
foreach ($schema->required as $required) {
if (!property_exists($value, $required)) {
$this->addError((!$path) ? $required : "$path.$required", "The property " . $required . " is required", 'required');
}
}
} else if (isset($schema->required) && !is_array($schema->required)) {
// Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true}
if ( $schema->required && $value instanceof UndefinedConstraint) {
$this->addError($path, "Is missing and it is required", 'required');
}
}
}
// Verify type
if (!($value instanceof UndefinedConstraint)) {
$this->checkType($value, $schema, $path);
}
// Verify disallowed items
if (isset($schema->disallow)) {
$initErrors = $this->getErrors();
$typeSchema = new \stdClass();
$typeSchema->type = $schema->disallow;
$this->checkType($value, $typeSchema, $path);
// if no new errors were raised it must be a disallowed value
if (count($this->getErrors()) == count($initErrors)) {
$this->addError($path, "Disallowed value was matched", 'disallow');
} else {
$this->errors = $initErrors;
}
}
if (isset($schema->not)) {
$initErrors = $this->getErrors();
$this->checkUndefined($value, $schema->not, $path, $i);
// if no new errors were raised then the instance validated against the "not" schema
if (count($this->getErrors()) == count($initErrors)) {
$this->addError($path, "Matched a schema which it should not", 'not');
} else {
$this->errors = $initErrors;
}
}
// Verify minimum and maximum number of properties
if (is_object($value)) {
if (isset($schema->minProperties)) {
if (count(get_object_vars($value)) < $schema->minProperties) {
$this->addError($path, "Must contain a minimum of " . $schema->minProperties . " properties", 'minProperties', array('minProperties' => $schema->minProperties,));
}
}
if (isset($schema->maxProperties)) {
if (count(get_object_vars($value)) > $schema->maxProperties) {
$this->addError($path, "Must contain no more than " . $schema->maxProperties . " properties", 'maxProperties', array('maxProperties' => $schema->maxProperties,));
}
}
}
// Verify that dependencies are met
if (is_object($value) && isset($schema->dependencies)) {
$this->validateDependencies($value, $schema->dependencies, $path);
}
}
/**
* Validate allOf, anyOf, and oneOf properties
*
* @param mixed $value
* @param mixed $schema
* @param string $path
* @param string $i
*/
protected function validateOfProperties($value, $schema, $path, $i = "")
{
// Verify type
if ($value instanceof UndefinedConstraint) {
return;
}
if (isset($schema->allOf)) {
$isValid = true;
foreach ($schema->allOf as $allOf) {
$initErrors = $this->getErrors();
$this->checkUndefined($value, $allOf, $path, $i);
$isValid = $isValid && (count($this->getErrors()) == count($initErrors));
}
if (!$isValid) {
$this->addError($path, "Failed to match all schemas", 'allOf');
}
}
if (isset($schema->anyOf)) {
$isValid = false;
$startErrors = $this->getErrors();
foreach ($schema->anyOf as $anyOf) {
$initErrors = $this->getErrors();
$this->checkUndefined($value, $anyOf, $path, $i);
if ($isValid = (count($this->getErrors()) == count($initErrors))) {
break;
}
}
if (!$isValid) {
$this->addError($path, "Failed to match at least one schema", 'anyOf');
} else {
$this->errors = $startErrors;
}
}
if (isset($schema->oneOf)) {
$allErrors = array();
$matchedSchemas = 0;
$startErrors = $this->getErrors();
foreach ($schema->oneOf as $oneOf) {
$this->errors = array();
$this->checkUndefined($value, $oneOf, $path, $i);
if (count($this->getErrors()) == 0) {
$matchedSchemas++;
}
$allErrors = array_merge($allErrors, array_values($this->getErrors()));
}
if ($matchedSchemas !== 1) {
$this->addErrors(
array_merge(
$allErrors,
array(array(
'property' => $path,
'message' => "Failed to match exactly one schema",
'constraint' => 'oneOf',
),),
$startErrors
)
);
} else {
$this->errors = $startErrors;
}
}
}
/**
* Validate dependencies
*
* @param mixed $value
* @param mixed $dependencies
* @param string $path
* @param string $i
*/
protected function validateDependencies($value, $dependencies, $path, $i = "")
{
foreach ($dependencies as $key => $dependency) {
if (property_exists($value, $key)) {
if (is_string($dependency)) {
// Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"}
if (!property_exists($value, $dependency)) {
$this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies');
}
} else if (is_array($dependency)) {
// Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]}
foreach ($dependency as $d) {
if (!property_exists($value, $d)) {
$this->addError($path, "$key depends on $d and $d is missing", 'dependencies');
}
}
} else if (is_object($dependency)) {
// Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}}
$this->checkUndefined($value, $dependency, $path, $i);
}
}
}
}
protected function validateUri($schema, $schemaUri = null)
{
$resolver = new UriResolver();
$retriever = $this->getUriRetriever();
$jsonSchema = null;
if ($resolver->isValid($schemaUri)) {
$schemaId = property_exists($schema, 'id') ? $schema->id : null;
$jsonSchema = $retriever->retrieve($schemaId, $schemaUri);
}
return $jsonSchema;
}
}

View file

@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the InvalidArgumentException
*/
class InvalidArgumentException extends \InvalidArgumentException
{
}

View file

@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the InvalidSchemaMediaType
*/
class InvalidSchemaMediaTypeException extends \RuntimeException
{
}

View file

@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the InvalidSourceUriException
*/
class InvalidSourceUriException extends InvalidArgumentException
{
}

View file

@ -0,0 +1,40 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the JsonDecodingException
*/
class JsonDecodingException extends \RuntimeException
{
public function __construct($code = JSON_ERROR_NONE, \Exception $previous = null)
{
switch ($code) {
case JSON_ERROR_DEPTH:
$message = 'The maximum stack depth has been exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$message = 'Invalid or malformed JSON';
break;
case JSON_ERROR_CTRL_CHAR:
$message = 'Control character error, possibly incorrectly encoded';
break;
case JSON_ERROR_UTF8:
$message = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
case JSON_ERROR_SYNTAX:
$message = 'JSON syntax is malformed';
break;
default:
$message = 'Syntax error';
}
parent::__construct($message, $code, $previous);
}
}

View file

@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the ResourceNotFoundException
*/
class ResourceNotFoundException extends \RuntimeException
{
}

View file

@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the UriResolverException
*/
class UriResolverException extends \RuntimeException
{
}

View file

@ -0,0 +1,277 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema;
use JsonSchema\Exception\JsonDecodingException;
use JsonSchema\Uri\Retrievers\UriRetrieverInterface;
use JsonSchema\Uri\UriRetriever;
/**
* Take in an object that's a JSON schema and take care of all $ref references
*
* @author Tyler Akins <fidian@rumkin.com>
* @see README.md
*/
class RefResolver
{
/**
* HACK to prevent too many recursive expansions.
* Happens e.g. when you want to validate a schema against the schema
* definition.
*
* @var integer
*/
protected static $depth = 0;
/**
* maximum references depth
* @var integer
*/
public static $maxDepth = 7;
/**
* @var UriRetrieverInterface
*/
protected $uriRetriever = null;
/**
* @var object
*/
protected $rootSchema = null;
/**
* @param UriRetriever $retriever
*/
public function __construct($retriever = null)
{
$this->uriRetriever = $retriever;
}
/**
* Retrieves a given schema given a ref and a source URI
*
* @param string $ref Reference from schema
* @param string $sourceUri URI where original schema was located
* @return object Schema
*/
public function fetchRef($ref, $sourceUri)
{
$retriever = $this->getUriRetriever();
$jsonSchema = $retriever->retrieve($ref, $sourceUri);
$this->resolve($jsonSchema);
return $jsonSchema;
}
/**
* Return the URI Retriever, defaulting to making a new one if one
* was not yet set.
*
* @return UriRetriever
*/
public function getUriRetriever()
{
if (is_null($this->uriRetriever)) {
$this->setUriRetriever(new UriRetriever);
}
return $this->uriRetriever;
}
/**
* Resolves all $ref references for a given schema. Recurses through
* the object to resolve references of any child schemas.
*
* The 'format' property is omitted because it isn't required for
* validation. Theoretically, this class could be extended to look
* for URIs in formats: "These custom formats MAY be expressed as
* an URI, and this URI MAY reference a schema of that format."
*
* The 'id' property is not filled in, but that could be made to happen.
*
* @param object $schema JSON Schema to flesh out
* @param string $sourceUri URI where this schema was located
*/
public function resolve($schema, $sourceUri = null)
{
if (self::$depth > self::$maxDepth) {
self::$depth = 0;
throw new JsonDecodingException(JSON_ERROR_DEPTH);
}
++self::$depth;
if (! is_object($schema)) {
--self::$depth;
return;
}
if (null === $sourceUri && ! empty($schema->id)) {
$sourceUri = $schema->id;
}
if (null === $this->rootSchema) {
$this->rootSchema = $schema;
}
// Resolve $ref first
$this->resolveRef($schema, $sourceUri);
// These properties are just schemas
// eg. items can be a schema or an array of schemas
foreach (array('additionalItems', 'additionalProperties', 'extends', 'items') as $propertyName) {
$this->resolveProperty($schema, $propertyName, $sourceUri);
}
// These are all potentially arrays that contain schema objects
// eg. type can be a value or an array of values/schemas
// eg. items can be a schema or an array of schemas
foreach (array('disallow', 'extends', 'items', 'type', 'allOf', 'anyOf', 'oneOf') as $propertyName) {
$this->resolveArrayOfSchemas($schema, $propertyName, $sourceUri);
}
// These are all objects containing properties whose values are schemas
foreach (array('dependencies', 'patternProperties', 'properties') as $propertyName) {
$this->resolveObjectOfSchemas($schema, $propertyName, $sourceUri);
}
--self::$depth;
}
/**
* Given an object and a property name, that property should be an
* array whose values can be schemas.
*
* @param object $schema JSON Schema to flesh out
* @param string $propertyName Property to work on
* @param string $sourceUri URI where this schema was located
*/
public function resolveArrayOfSchemas($schema, $propertyName, $sourceUri)
{
if (! isset($schema->$propertyName) || ! is_array($schema->$propertyName)) {
return;
}
foreach ($schema->$propertyName as $possiblySchema) {
$this->resolve($possiblySchema, $sourceUri);
}
}
/**
* Given an object and a property name, that property should be an
* object whose properties are schema objects.
*
* @param object $schema JSON Schema to flesh out
* @param string $propertyName Property to work on
* @param string $sourceUri URI where this schema was located
*/
public function resolveObjectOfSchemas($schema, $propertyName, $sourceUri)
{
if (! isset($schema->$propertyName) || ! is_object($schema->$propertyName)) {
return;
}
foreach (get_object_vars($schema->$propertyName) as $possiblySchema) {
$this->resolve($possiblySchema, $sourceUri);
}
}
/**
* Given an object and a property name, that property should be a
* schema object.
*
* @param object $schema JSON Schema to flesh out
* @param string $propertyName Property to work on
* @param string $sourceUri URI where this schema was located
*/
public function resolveProperty($schema, $propertyName, $sourceUri)
{
if (! isset($schema->$propertyName)) {
return;
}
$this->resolve($schema->$propertyName, $sourceUri);
}
/**
* Look for the $ref property in the object. If found, remove the
* reference and augment this object with the contents of another
* schema.
*
* @param object $schema JSON Schema to flesh out
* @param string $sourceUri URI where this schema was located
*/
public function resolveRef($schema, $sourceUri)
{
$ref = '$ref';
if (empty($schema->$ref)) {
return;
}
$splitRef = explode('#', $schema->$ref, 2);
$refDoc = $splitRef[0];
$refPath = null;
if (count($splitRef) === 2) {
$refPath = explode('/', $splitRef[1]);
array_shift($refPath);
}
if (empty($refDoc) && empty($refPath)) {
// TODO: Not yet implemented - root pointer ref, causes recursion issues
return;
}
if (!empty($refDoc)) {
$refSchema = $this->fetchRef($refDoc, $sourceUri);
} else {
$refSchema = $this->rootSchema;
}
if (null !== $refPath) {
$refSchema = $this->resolveRefSegment($refSchema, $refPath);
}
unset($schema->$ref);
// Augment the current $schema object with properties fetched
foreach (get_object_vars($refSchema) as $prop => $value) {
$schema->$prop = $value;
}
}
/**
* Set URI Retriever for use with the Ref Resolver
*
* @param UriRetriever $retriever
* @return $this for chaining
*/
public function setUriRetriever(UriRetriever $retriever)
{
$this->uriRetriever = $retriever;
return $this;
}
protected function resolveRefSegment($data, $pathParts)
{
foreach ($pathParts as $path) {
$path = strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%'));
if (is_array($data)) {
$data = $data[$path];
} else {
$data = $data->{$path};
}
}
return $data;
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* JsonSchema
* @filesource
*/
namespace JsonSchema\Uri\Retrievers;
/**
* AbstractRetriever implements the default shared behavior
* that all decendant Retrievers should inherit
* @author Steven Garcia <webwhammy@gmail.com>
*/
abstract class AbstractRetriever implements UriRetrieverInterface
{
/**
* Media content type
* @var string
*/
protected $contentType;
/**
* {@inheritDoc}
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType()
*/
public function getContentType()
{
return $this->contentType;
}
}

View file

@ -0,0 +1,79 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Uri\Retrievers;
use JsonSchema\Validator;
/**
* Tries to retrieve JSON schemas from a URI using cURL library
*
* @author Sander Coolen <sander@jibber.nl>
*/
class Curl extends AbstractRetriever
{
protected $messageBody;
public function __construct()
{
if (!function_exists('curl_init')) {
throw new \RuntimeException("cURL not installed");
}
}
/**
* {@inheritDoc}
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
*/
public function retrieve($uri)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uri);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: ' . Validator::SCHEMA_MEDIA_TYPE));
$response = curl_exec($ch);
if (false === $response) {
throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found');
}
$this->fetchMessageBody($response);
$this->fetchContentType($response);
curl_close($ch);
return $this->messageBody;
}
/**
* @param string $response cURL HTTP response
*/
private function fetchMessageBody($response)
{
preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match);
$this->messageBody = $match[1];
}
/**
* @param string $response cURL HTTP response
* @return boolean Whether the Content-Type header was found or not
*/
protected function fetchContentType($response)
{
if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) {
$this->contentType = trim($match[1]);
return true;
}
return false;
}
}

View file

@ -0,0 +1,87 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Uri\Retrievers;
use JsonSchema\Exception\ResourceNotFoundException;
use JsonSchema\Validator;
/**
* Tries to retrieve JSON schemas from a URI using file_get_contents()
*
* @author Sander Coolen <sander@jibber.nl>
*/
class FileGetContents extends AbstractRetriever
{
protected $messageBody;
/**
* {@inheritDoc}
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
*/
public function retrieve($uri)
{
$context = stream_context_create(array(
'http' => array(
'method' => 'GET',
'header' => "Accept: " . Validator::SCHEMA_MEDIA_TYPE
)));
set_error_handler(function() use ($uri) {
throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
});
$response = file_get_contents($uri);
restore_error_handler();
if (false === $response) {
throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
}
if ($response == ''
&& substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/'
) {
throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
}
$this->messageBody = $response;
if (! empty($http_response_header)) {
$this->fetchContentType($http_response_header);
} else {
// Could be a "file://" url or something else - fake up the response
$this->contentType = null;
}
return $this->messageBody;
}
/**
* @param array $headers HTTP Response Headers
* @return boolean Whether the Content-Type header was found or not
*/
private function fetchContentType(array $headers)
{
foreach ($headers as $header) {
if ($this->contentType = self::getContentTypeMatchInHeader($header)) {
return true;
}
}
return false;
}
/**
* @param string $header
* @return string|null
*/
protected static function getContentTypeMatchInHeader($header)
{
if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) {
return trim($match[1]);
}
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace JsonSchema\Uri\Retrievers;
use JsonSchema\Validator;
/**
* URI retrieved based on a predefined array of schemas
*
* @example
*
* $retriever = new PredefinedArray(array(
* 'http://acme.com/schemas/person#' => '{ ... }',
* 'http://acme.com/schemas/address#' => '{ ... }',
* ))
*
* $schema = $retriever->retrieve('http://acme.com/schemas/person#');
*/
class PredefinedArray extends AbstractRetriever
{
/**
* Contains schemas as URI => JSON
* @var array
*/
private $schemas;
/**
* Constructor
*
* @param array $schemas
* @param string $contentType
*/
public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE)
{
$this->schemas = $schemas;
$this->contentType = $contentType;
}
/**
* {@inheritDoc}
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
*/
public function retrieve($uri)
{
if (!array_key_exists($uri, $this->schemas)) {
throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf(
'The JSON schema "%s" was not found.',
$uri
));
}
return $this->schemas[$uri];
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Uri\Retrievers;
/**
* Interface for URI retrievers
*
* @author Sander Coolen <sander@jibber.nl>
*/
interface UriRetrieverInterface
{
/**
* Retrieve a schema from the specified URI
* @param string $uri URI that resolves to a JSON schema
* @throws \JsonSchema\Exception\ResourceNotFoundException
* @return mixed string|null
*/
public function retrieve($uri);
/**
* Get media content type
* @return string
*/
public function getContentType();
}

View file

@ -0,0 +1,157 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Uri;
use JsonSchema\Exception\UriResolverException;
/**
* Resolves JSON Schema URIs
*
* @author Sander Coolen <sander@jibber.nl>
*/
class UriResolver
{
/**
* Parses a URI into five main components
*
* @param string $uri
* @return array
*/
public function parse($uri)
{
preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);
$components = array();
if (5 < count($match)) {
$components = array(
'scheme' => $match[2],
'authority' => $match[4],
'path' => $match[5]
);
}
if (7 < count($match)) {
$components['query'] = $match[7];
}
if (9 < count($match)) {
$components['fragment'] = $match[9];
}
return $components;
}
/**
* Builds a URI based on n array with the main components
*
* @param array $components
* @return string
*/
public function generate(array $components)
{
$uri = $components['scheme'] . '://'
. $components['authority']
. $components['path'];
if (array_key_exists('query', $components)) {
$uri .= $components['query'];
}
if (array_key_exists('fragment', $components)) {
$uri .= '#' . $components['fragment'];
}
return $uri;
}
/**
* Resolves a URI
*
* @param string $uri Absolute or relative
* @param string $baseUri Optional base URI
* @return string Absolute URI
*/
public function resolve($uri, $baseUri = null)
{
if ($uri == '') {
return $baseUri;
}
$components = $this->parse($uri);
$path = $components['path'];
if (! empty($components['scheme'])) {
return $uri;
}
$baseComponents = $this->parse($baseUri);
$basePath = $baseComponents['path'];
$baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath);
if (isset($components['fragment'])) {
$baseComponents['fragment'] = $components['fragment'];
}
return $this->generate($baseComponents);
}
/**
* Tries to glue a relative path onto an absolute one
*
* @param string $relativePath
* @param string $basePath
* @return string Merged path
* @throws UriResolverException
*/
public static function combineRelativePathWithBasePath($relativePath, $basePath)
{
$relativePath = self::normalizePath($relativePath);
if ($relativePath == '') {
return $basePath;
}
if ($relativePath{0} == '/') {
return $relativePath;
}
$basePathSegments = explode('/', $basePath);
preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match);
$numLevelUp = strlen($match[0]) /3 + 1;
if ($numLevelUp >= count($basePathSegments)) {
throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath));
}
$basePathSegments = array_slice($basePathSegments, 0, -$numLevelUp);
$path = preg_replace('|^/?(\.\./(\./)*)*|', '', $relativePath);
return implode('/', $basePathSegments) . '/' . $path;
}
/**
* Normalizes a URI path component by removing dot-slash and double slashes
*
* @param string $path
* @return string
*/
private static function normalizePath($path)
{
$path = preg_replace('|((?<!\.)\./)*|', '', $path);
$path = preg_replace('|//|', '/', $path);
return $path;
}
/**
* @param string $uri
* @return boolean
*/
public function isValid($uri)
{
$components = $this->parse($uri);
return !empty($components);
}
}

View file

@ -0,0 +1,289 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Uri;
use JsonSchema\Uri\Retrievers\FileGetContents;
use JsonSchema\Uri\Retrievers\UriRetrieverInterface;
use JsonSchema\Validator;
use JsonSchema\Exception\InvalidSchemaMediaTypeException;
use JsonSchema\Exception\JsonDecodingException;
use JsonSchema\Exception\ResourceNotFoundException;
/**
* Retrieves JSON Schema URIs
*
* @author Tyler Akins <fidian@rumkin.com>
*/
class UriRetriever
{
/**
* @var null|UriRetrieverInterface
*/
protected $uriRetriever = null;
/**
* @var array|object[]
* @see loadSchema
*/
private $schemaCache = array();
/**
* Guarantee the correct media type was encountered
*
* @param UriRetrieverInterface $uriRetriever
* @param string $uri
* @return bool|void
*/
public function confirmMediaType($uriRetriever, $uri)
{
$contentType = $uriRetriever->getContentType();
if (is_null($contentType)) {
// Well, we didn't get an invalid one
return;
}
if (Validator::SCHEMA_MEDIA_TYPE === $contentType) {
return;
}
if (substr($uri, 0, 23) == 'http://json-schema.org/') {
//HACK; they deliver broken content types
return true;
}
throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE));
}
/**
* Get a URI Retriever
*
* If none is specified, sets a default FileGetContents retriever and
* returns that object.
*
* @return UriRetrieverInterface
*/
public function getUriRetriever()
{
if (is_null($this->uriRetriever)) {
$this->setUriRetriever(new FileGetContents);
}
return $this->uriRetriever;
}
/**
* Resolve a schema based on pointer
*
* URIs can have a fragment at the end in the format of
* #/path/to/object and we are to look up the 'path' property of
* the first object then the 'to' and 'object' properties.
*
* @param object $jsonSchema JSON Schema contents
* @param string $uri JSON Schema URI
* @return object JSON Schema after walking down the fragment pieces
*
* @throws ResourceNotFoundException
*/
public function resolvePointer($jsonSchema, $uri)
{
$resolver = new UriResolver();
$parsed = $resolver->parse($uri);
if (empty($parsed['fragment'])) {
return $jsonSchema;
}
$path = explode('/', $parsed['fragment']);
while ($path) {
$pathElement = array_shift($path);
if (! empty($pathElement)) {
$pathElement = str_replace('~1', '/', $pathElement);
$pathElement = str_replace('~0', '~', $pathElement);
if (! empty($jsonSchema->$pathElement)) {
$jsonSchema = $jsonSchema->$pathElement;
} else {
throw new ResourceNotFoundException(
'Fragment "' . $parsed['fragment'] . '" not found'
. ' in ' . $uri
);
}
if (! is_object($jsonSchema)) {
throw new ResourceNotFoundException(
'Fragment part "' . $pathElement . '" is no object '
. ' in ' . $uri
);
}
}
}
return $jsonSchema;
}
/**
* Retrieve a URI
*
* @param string $uri JSON Schema URI
* @param string|null $baseUri
* @return object JSON Schema contents
*/
public function retrieve($uri, $baseUri = null)
{
$resolver = new UriResolver();
$resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri);
//fetch URL without #fragment
$arParts = $resolver->parse($resolvedUri);
if (isset($arParts['fragment'])) {
unset($arParts['fragment']);
$fetchUri = $resolver->generate($arParts);
}
$jsonSchema = $this->loadSchema($fetchUri);
// Use the JSON pointer if specified
$jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri);
if ($jsonSchema instanceof \stdClass) {
$jsonSchema->id = $resolvedUri;
}
return $jsonSchema;
}
/**
* Fetch a schema from the given URI, json-decode it and return it.
* Caches schema objects.
*
* @param string $fetchUri Absolute URI
*
* @return object JSON schema object
*/
protected function loadSchema($fetchUri)
{
if (isset($this->schemaCache[$fetchUri])) {
return $this->schemaCache[$fetchUri];
}
$uriRetriever = $this->getUriRetriever();
$contents = $this->uriRetriever->retrieve($fetchUri);
$this->confirmMediaType($uriRetriever, $fetchUri);
$jsonSchema = json_decode($contents);
if (JSON_ERROR_NONE < $error = json_last_error()) {
throw new JsonDecodingException($error);
}
$this->schemaCache[$fetchUri] = $jsonSchema;
return $jsonSchema;
}
/**
* Set the URI Retriever
*
* @param UriRetrieverInterface $uriRetriever
* @return $this for chaining
*/
public function setUriRetriever(UriRetrieverInterface $uriRetriever)
{
$this->uriRetriever = $uriRetriever;
return $this;
}
/**
* Parses a URI into five main components
*
* @param string $uri
* @return array
*/
public function parse($uri)
{
preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);
$components = array();
if (5 < count($match)) {
$components = array(
'scheme' => $match[2],
'authority' => $match[4],
'path' => $match[5]
);
}
if (7 < count($match)) {
$components['query'] = $match[7];
}
if (9 < count($match)) {
$components['fragment'] = $match[9];
}
return $components;
}
/**
* Builds a URI based on n array with the main components
*
* @param array $components
* @return string
*/
public function generate(array $components)
{
$uri = $components['scheme'] . '://'
. $components['authority']
. $components['path'];
if (array_key_exists('query', $components)) {
$uri .= $components['query'];
}
if (array_key_exists('fragment', $components)) {
$uri .= $components['fragment'];
}
return $uri;
}
/**
* Resolves a URI
*
* @param string $uri Absolute or relative
* @param string $baseUri Optional base URI
* @return string
*/
public function resolve($uri, $baseUri = null)
{
$components = $this->parse($uri);
$path = $components['path'];
if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) {
return $uri;
}
$baseComponents = $this->parse($baseUri);
$basePath = $baseComponents['path'];
$baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath);
return $this->generate($baseComponents);
}
/**
* @param string $uri
* @return boolean
*/
public function isValid($uri)
{
$components = $this->parse($uri);
return !empty($components);
}
}

View file

@ -0,0 +1,40 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema;
use JsonSchema\Constraints\SchemaConstraint;
use JsonSchema\Constraints\Constraint;
/**
* A JsonSchema Constraint
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
* @see README.md
*/
class Validator extends Constraint
{
const SCHEMA_MEDIA_TYPE = 'application/schema+json';
/**
* Validates the given data against the schema and returns an object containing the results
* Both the php object and the schema are supposed to be a result of a json_decode call.
* The validation works as defined by the schema proposal in http://json-schema.org
*
* {@inheritDoc}
*/
public function check($value, $schema = null, $path = null, $i = null)
{
$validator = $this->getFactory()->createInstanceFor('schema');
$validator->check($value, $schema);
$this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR));
}
}