Skip to content

Add new config option, refactor coerce feature to use this, and simplify the public API #351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ install:
- travis_retry composer install --no-interaction --prefer-dist

script:
- if [[ "$WITH_COVERAGE" == "true" ]]; then composer coverage; else composer test; fi
- if [[ "$WITH_COVERAGE" == "true" ]]; then ./vendor/bin/phpunit --coverage-text; else composer test; fi
92 changes: 92 additions & 0 deletions src/JsonSchema/Constraints/BaseConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?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\Entity\JsonPointer;

/**
* A more basic constraint definition - used for the public
* interface to avoid exposing library internals.
*/
class BaseConstraint
{
/**
* @var array Errors
*/
protected $errors = array();

/**
* @var Factory
*/
protected $factory;

/**
* @param Factory $factory
*/
public function __construct(Factory $factory = null)
{
$this->factory = $factory ? : new Factory();
}

/**
* {@inheritDoc}
*/
public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null)
{
$error = array(
'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')),
'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'),
'message' => $message,
'constraint' => $constraint,
);

if (is_array($more) && count($more) > 0)
{
$error += $more;
}

$this->errors[] = $error;
}

/**
* {@inheritDoc}
*/
public function addErrors(array $errors)
{
if ($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();
}
}
55 changes: 22 additions & 33 deletions src/JsonSchema/Constraints/CollectionConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,7 @@ class CollectionConstraint extends Constraint
/**
* {@inheritDoc}
*/
public function check($value, $schema = null, JsonPointer $path = null, $i = null)
{
$this->_check($value, $schema, $path, $i);
}

/**
* {@inheritDoc}
*/
public function coerce(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
$this->_check($value, $schema, $path, $i, true);
}

protected function _check(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false)
public function check(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
// Verify minItems
if (isset($schema->minItems) && count($value) < $schema->minItems) {
Expand All @@ -61,7 +48,7 @@ protected function _check(&$value, $schema = null, JsonPointer $path = null, $i

// Verify items
if (isset($schema->items)) {
$this->validateItems($value, $schema, $path, $i, $coerce);
$this->validateItems($value, $schema, $path, $i);
}
}

Expand All @@ -72,9 +59,8 @@ protected function _check(&$value, $schema = null, JsonPointer $path = null, $i
* @param \stdClass $schema
* @param JsonPointer|null $path
* @param string $i
* @param boolean $coerce
*/
protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false)
protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
if (is_object($schema->items)) {
// just one type definition for the whole array
Expand All @@ -90,26 +76,24 @@ protected function validateItems(&$value, $schema = null, JsonPointer $path = nu
// performance optimization
$type = $schema->items->type;
$typeValidator = $this->factory->createInstanceFor('type');
$validator = $this->factory->createInstanceFor($type === 'integer' ? 'number' : $type);
$validator = $this->factory->createInstanceFor($type === 'integer' ? 'number' : $type);

foreach ($value as $k => $v) {
$k_path = $this->incrementPath($path, $k);
if($coerce) {
$typeValidator->coerce($v, $schema->items, $k_path, $i);
} else {
$typeValidator->check($v, $schema->items, $k_path, $i);
}
foreach ($value as $k => &$v) {
$k_path = $this->incrementPath($path, $k);
$typeValidator->check($v, $schema->items, $k_path, $i);

$validator->check($v, $schema->items, $k_path, $i);
$validator->check($v, $schema->items, $k_path, $i);
}
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 {
foreach ($value as $k => $v) {
foreach ($value as $k => &$v) {
$initErrors = $this->getErrors();

// First check if its defined in "items"
$this->checkUndefined($v, $schema->items, $path, $k, $coerce);
$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)) {
Expand All @@ -124,32 +108,37 @@ 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
}
} else {
// Defined item type definitions
foreach ($value as $k => $v) {
foreach ($value as $k => &$v) {
if (array_key_exists($k, $schema->items)) {
$this->checkUndefined($v, $schema->items[$k], $path, $k, $coerce);
$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, $coerce);
$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, $coerce);
$this->checkUndefined($v, new \stdClass(), $path, $k);
}
}
}
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) {
for ($k = count($value); $k < count($schema->items); $k++) {
$this->checkUndefined($this->factory->createInstanceFor('undefined'), $schema->items[$k], $path, $k, $coerce);
$undefinedInstance = $this->factory->createInstanceFor('undefined');
$this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k);
}
}
}
Expand Down
114 changes: 14 additions & 100 deletions src/JsonSchema/Constraints/Constraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,81 +20,15 @@
* @author Robert Schönthal <[email protected]>
* @author Bruno Prieto Reis <[email protected]>
*/
abstract class Constraint implements ConstraintInterface
abstract class Constraint extends BaseConstraint implements ConstraintInterface
{
protected $errors = array();
protected $inlineSchemaProperty = '$schema';

const CHECK_MODE_NORMAL = 0x00000001;
const CHECK_MODE_TYPE_CAST = 0x00000002;

/**
* @var Factory
*/
protected $factory;

/**
* @param Factory $factory
*/
public function __construct(Factory $factory = null)
{
$this->factory = $factory ? : new Factory();
}

/**
* {@inheritDoc}
*/
public function addError(JsonPointer $path = null, $message, $constraint='', array $more=null)
{
$error = array(
'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')),
'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'),
'message' => $message,
'constraint' => $constraint,
);

if (is_array($more) && count($more) > 0)
{
$error += $more;
}

$this->errors[] = $error;
}

/**
* {@inheritDoc}
*/
public function addErrors(array $errors)
{
if ($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();
}
const CHECK_MODE_NONE = 0x00000000;
const CHECK_MODE_NORMAL = 0x00000001;
const CHECK_MODE_TYPE_CAST = 0x00000002;
const CHECK_MODE_COERCE_TYPES = 0x00000004;
const CHECK_MODE_APPLY_DEFAULTS = 0x00000008;

/**
* Bubble down the path
Expand Down Expand Up @@ -123,16 +57,11 @@ protected function incrementPath(JsonPointer $path = null, $i)
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
* @param boolean $coerce
*/
protected function checkArray(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false)
protected function checkArray(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
$validator = $this->factory->createInstanceFor('collection');
if($coerce) {
$validator->coerce($value, $schema, $path, $i);
} else {
$validator->check($value, $schema, $path, $i);
}
$validator->check($value, $schema, $path, $i);

$this->addErrors($validator->getErrors());
}
Expand All @@ -145,16 +74,11 @@ protected function checkArray(&$value, $schema = null, JsonPointer $path = null,
* @param JsonPointer|null $path
* @param mixed $i
* @param mixed $patternProperties
* @param boolean $coerce
*/
protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $i = null, $patternProperties = null, $coerce = false)
protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $i = null, $patternProperties = null)
{
$validator = $this->factory->createInstanceFor('object');
if($coerce){
$validator->coerce($value, $schema, $path, $i, $patternProperties);
} else {
$validator->check($value, $schema, $path, $i, $patternProperties);
}
$validator->check($value, $schema, $path, $i, $patternProperties);

$this->addErrors($validator->getErrors());
}
Expand All @@ -166,16 +90,11 @@ protected function checkObject(&$value, $schema = null, JsonPointer $path = null
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
* @param boolean $coerce
*/
protected function checkType(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false)
protected function checkType(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
$validator = $this->factory->createInstanceFor('type');
if($coerce) {
$validator->coerce($value, $schema, $path, $i);
} else {
$validator->check($value, $schema, $path, $i);
}
$validator->check($value, $schema, $path, $i);

$this->addErrors($validator->getErrors());
}
Expand All @@ -187,17 +106,12 @@ protected function checkType(&$value, $schema = null, JsonPointer $path = null,
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
* @param boolean $coerce
*/
protected function checkUndefined(&$value, $schema = null, JsonPointer $path = null, $i = null, $coerce = false)
protected function checkUndefined(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
$validator = $this->factory->createInstanceFor('undefined');

if($coerce){
$validator->coerce($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i);
} else {
$validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i);
}
$validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i);

$this->addErrors($validator->getErrors());
}
Expand Down
Loading