Skip to content

Commit

Permalink
Refactor to simplify the public API and make adding features easier
Browse files Browse the repository at this point in the history
This commit makes the following changes:
 * Split the Constraint class to make Validator independent from it
 * Add Validator::validate() as the main entry point
 * Turn Validator::coerce() and Validator::check() into aliases
 * Add Factory::setConfig(), getConfig(), addConfig() & removeConfig()
 * Make type-coercion a checkMode option, don't pass $coerce everywhere
 * Add some extra tests
  • Loading branch information
erayd committed Feb 5, 2017
1 parent 325a0f8 commit 2159893
Show file tree
Hide file tree
Showing 25 changed files with 348 additions and 272 deletions.
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

0 comments on commit 2159893

Please sign in to comment.