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
…351)

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 authored and bighappyface committed Feb 15, 2017
1 parent 325a0f8 commit 9b6ebfe
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 9b6ebfe

Please sign in to comment.