Skip to content

Commit 9da41b0

Browse files
SereneAntbighappyface
authored andcommitted
improve validation performance of long string, number and integer arrays (#331)
* improve validation performance of long string and numeric arrays * update 'Running the thests' section in README.md
1 parent 24aa1a9 commit 9da41b0

File tree

6 files changed

+236
-84
lines changed

6 files changed

+236
-84
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,7 @@ $jsonValidator->check($jsonToValidateObject, $jsonSchemaObject);
141141
## Running the tests
142142

143143
```bash
144-
vendor/bin/phpunit
145-
```
144+
composer test
145+
composer testOnly TestClass
146+
composer testOnly TestClass::testMethod
147+
```

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
},
5858
"scripts": {
5959
"test" : "vendor/bin/phpunit",
60+
"testOnly" : "vendor/bin/phpunit --colors --filter",
6061
"coverage" : "vendor/bin/phpunit --coverage-text"
6162
}
6263
}

src/JsonSchema/Constraints/CollectionConstraint.php

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,51 @@ protected function validateItems($value, $schema = null, JsonPointer $path = nul
6363
{
6464
if (is_object($schema->items)) {
6565
// just one type definition for the whole array
66-
foreach ($value as $k => $v) {
67-
$initErrors = $this->getErrors();
6866

69-
// First check if its defined in "items"
70-
$this->checkUndefined($v, $schema->items, $path, $k);
67+
if (isset($schema->items->type)
68+
&& (
69+
$schema->items->type == 'string'
70+
|| $schema->items->type == 'number'
71+
|| $schema->items->type == 'integer'
72+
)
73+
&& !isset($schema->additionalItems)
74+
) {
75+
// performance optimization
76+
$type = $schema->items->type;
77+
$validator = $this->factory->createInstanceFor($type === 'integer' ? 'number' : $type);
78+
79+
foreach ($value as $k => $v) {
80+
$k_path = $this->incrementPath($path, $k);
7181

72-
// Recheck with "additionalItems" if the first test fails
73-
if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) {
74-
$secondErrors = $this->getErrors();
75-
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
82+
if (($type === 'string' && !is_string($v))
83+
|| ($type === 'number' && !(is_numeric($v) && !is_string($v)))
84+
|| ($type === 'integer' && !is_int($v))
85+
){
86+
$this->addError($k_path, ucwords(gettype($v)) . " value found, but $type is required", 'type');
87+
} else {
88+
$validator->check($v, $schema, $k_path, $i);
89+
}
7690
}
91+
$this->addErrors($validator->getErrors());
92+
} else {
93+
foreach ($value as $k => $v) {
94+
$initErrors = $this->getErrors();
7795

78-
// Reset errors if needed
79-
if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) {
80-
$this->errors = $secondErrors;
81-
} elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) {
82-
$this->errors = $initErrors;
96+
// First check if its defined in "items"
97+
$this->checkUndefined($v, $schema->items, $path, $k);
98+
99+
// Recheck with "additionalItems" if the first test fails
100+
if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) {
101+
$secondErrors = $this->getErrors();
102+
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
103+
}
104+
105+
// Reset errors if needed
106+
if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) {
107+
$this->errors = $secondErrors;
108+
} elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) {
109+
$this->errors = $initErrors;
110+
}
83111
}
84112
}
85113
} else {

tests/Constraints/BaseTestCase.php

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,12 @@
1414
use JsonSchema\SchemaStorage;
1515
use JsonSchema\Uri\UriResolver;
1616
use JsonSchema\Validator;
17-
use Prophecy\Argument;
1817

1918
/**
2019
* @package JsonSchema\Tests\Constraints
2120
*/
22-
abstract class BaseTestCase extends \PHPUnit_Framework_TestCase
21+
abstract class BaseTestCase extends VeryBaseTestCase
2322
{
24-
/** @var object */
25-
private $jsonSchemaDraft03;
26-
27-
/** @var object */
28-
private $jsonSchemaDraft04;
29-
3023
/**
3124
* @dataProvider getInvalidTests
3225
*/
@@ -127,65 +120,4 @@ public function getInvalidForAssocTests()
127120
{
128121
return $this->getInvalidTests();
129122
}
130-
131-
/**
132-
* @param object $schema
133-
* @return object
134-
*/
135-
protected function getUriRetrieverMock($schema)
136-
{
137-
$relativeTestsRoot = realpath(__DIR__ . '/../../vendor/json-schema/JSON-Schema-Test-Suite/remotes');
138-
139-
$jsonSchemaDraft03 = $this->getJsonSchemaDraft03();
140-
$jsonSchemaDraft04 = $this->getJsonSchemaDraft04();
141-
142-
$uriRetriever = $this->prophesize('JsonSchema\UriRetrieverInterface');
143-
$uriRetriever->retrieve('http://www.my-domain.com/schema.json')
144-
->willReturn($schema)
145-
->shouldBeCalled();
146-
147-
$uriRetriever->retrieve(Argument::any())
148-
->will(function ($args) use ($jsonSchemaDraft03, $jsonSchemaDraft04, $relativeTestsRoot) {
149-
if ('http://json-schema.org/draft-03/schema' === $args[0]) {
150-
return $jsonSchemaDraft03;
151-
} elseif ('http://json-schema.org/draft-04/schema' === $args[0]) {
152-
return $jsonSchemaDraft04;
153-
} elseif (0 === strpos($args[0], 'http://localhost:1234')) {
154-
$urlParts = parse_url($args[0]);
155-
return json_decode(file_get_contents($relativeTestsRoot . $urlParts['path']));
156-
} elseif (0 === strpos($args[0], 'http://www.my-domain.com')) {
157-
$urlParts = parse_url($args[0]);
158-
return json_decode(file_get_contents($relativeTestsRoot . '/folder' . $urlParts['path']));
159-
}
160-
});
161-
return $uriRetriever->reveal();
162-
}
163-
164-
/**
165-
* @return object
166-
*/
167-
private function getJsonSchemaDraft03()
168-
{
169-
if (!$this->jsonSchemaDraft03) {
170-
$this->jsonSchemaDraft03 = json_decode(
171-
file_get_contents(__DIR__ . '/../fixtures/json-schema-draft-03.json')
172-
);
173-
}
174-
175-
return $this->jsonSchemaDraft03;
176-
}
177-
178-
/**
179-
* @return object
180-
*/
181-
private function getJsonSchemaDraft04()
182-
{
183-
if (!$this->jsonSchemaDraft04) {
184-
$this->jsonSchemaDraft04 = json_decode(
185-
file_get_contents(__DIR__ . '/../fixtures/json-schema-draft-04.json')
186-
);
187-
}
188-
189-
return $this->jsonSchemaDraft04;
190-
}
191123
}

tests/Constraints/LongArraysTest.php

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the JsonSchema package.
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
namespace JsonSchema\Tests\Constraints;
11+
12+
use JsonSchema\Constraints\Factory;
13+
use JsonSchema\SchemaStorage;
14+
use JsonSchema\Validator;
15+
16+
class LongArraysTest extends VeryBaseTestCase
17+
{
18+
public function testLongStringArray()
19+
{
20+
$schema =
21+
'{
22+
"type":"object",
23+
"properties":{
24+
"p_array":{
25+
"type":"array",
26+
"items":{"type":"string"}
27+
}
28+
}
29+
}';
30+
31+
$tmp = new \stdClass();
32+
$tmp->p_array = array_map(function ($i) {
33+
return "#".$i;
34+
}, range(1, 100000));
35+
$input = json_encode($tmp);
36+
37+
$schemaStorage = new SchemaStorage($this->getUriRetrieverMock(json_decode($schema)));
38+
$schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json');
39+
40+
$validator = new Validator(new Factory($schemaStorage));
41+
$validator->check(json_decode($input), $schema);
42+
$this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true));
43+
}
44+
45+
public function testLongNumberArray()
46+
{
47+
$schema =
48+
'{
49+
"type":"object",
50+
"properties":{
51+
"p_array":{
52+
"type":"array",
53+
"items":{"type":"number"}
54+
}
55+
}
56+
}';
57+
58+
$tmp = new \stdClass();
59+
$tmp->p_array = array_map(function ($i) {
60+
return rand(1, 1000) / 1000.0;
61+
}, range(1, 100000));
62+
$input = json_encode($tmp);
63+
64+
$schemaStorage = new SchemaStorage($this->getUriRetrieverMock(json_decode($schema)));
65+
$schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json');
66+
67+
$validator = new Validator(new Factory($schemaStorage));
68+
$validator->check(json_decode($input), $schema);
69+
$this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true));
70+
}
71+
72+
public function testLongIntegerArray()
73+
{
74+
$schema =
75+
'{
76+
"type":"object",
77+
"properties":{
78+
"p_array":{
79+
"type":"array",
80+
"items":{"type":"integer"}
81+
}
82+
}
83+
}';
84+
85+
$tmp = new \stdClass();
86+
$tmp->p_array = array_map(function ($i) {
87+
return $i;
88+
}, range(1, 100000));
89+
$input = json_encode($tmp);
90+
91+
$schemaStorage = new SchemaStorage($this->getUriRetrieverMock(json_decode($schema)));
92+
$schema = $schemaStorage->getSchema('http://www.my-domain.com/schema.json');
93+
94+
$validator = new Validator(new Factory($schemaStorage));
95+
$validator->check(json_decode($input), $schema);
96+
$this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true));
97+
}
98+
99+
private static function millis()
100+
{
101+
$mt = explode(' ', microtime());
102+
return $mt[1] * 1000 + round($mt[0] * 1000);
103+
}
104+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the JsonSchema package.
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
namespace JsonSchema\Tests\Constraints;
11+
12+
use Prophecy\Argument;
13+
14+
/**
15+
* @package JsonSchema\Tests\Constraints
16+
*/
17+
abstract class VeryBaseTestCase extends \PHPUnit_Framework_TestCase
18+
{
19+
/** @var object */
20+
private $jsonSchemaDraft03;
21+
22+
/** @var object */
23+
private $jsonSchemaDraft04;
24+
25+
/**
26+
* @param object $schema
27+
* @return object
28+
*/
29+
protected function getUriRetrieverMock($schema)
30+
{
31+
$relativeTestsRoot = realpath(__DIR__ . '/../../vendor/json-schema/JSON-Schema-Test-Suite/remotes');
32+
33+
$jsonSchemaDraft03 = $this->getJsonSchemaDraft03();
34+
$jsonSchemaDraft04 = $this->getJsonSchemaDraft04();
35+
36+
$uriRetriever = $this->prophesize('JsonSchema\UriRetrieverInterface');
37+
$uriRetriever->retrieve('http://www.my-domain.com/schema.json')
38+
->willReturn($schema)
39+
->shouldBeCalled();
40+
41+
$uriRetriever->retrieve(Argument::any())
42+
->will(function ($args) use ($jsonSchemaDraft03, $jsonSchemaDraft04, $relativeTestsRoot) {
43+
if ('http://json-schema.org/draft-03/schema' === $args[0]) {
44+
return $jsonSchemaDraft03;
45+
} elseif ('http://json-schema.org/draft-04/schema' === $args[0]) {
46+
return $jsonSchemaDraft04;
47+
} elseif (0 === strpos($args[0], 'http://localhost:1234')) {
48+
$urlParts = parse_url($args[0]);
49+
return json_decode(file_get_contents($relativeTestsRoot . $urlParts['path']));
50+
} elseif (0 === strpos($args[0], 'http://www.my-domain.com')) {
51+
$urlParts = parse_url($args[0]);
52+
return json_decode(file_get_contents($relativeTestsRoot . '/folder' . $urlParts['path']));
53+
}
54+
});
55+
return $uriRetriever->reveal();
56+
}
57+
58+
/**
59+
* @return object
60+
*/
61+
private function getJsonSchemaDraft03()
62+
{
63+
if (!$this->jsonSchemaDraft03) {
64+
$this->jsonSchemaDraft03 = json_decode(
65+
file_get_contents(__DIR__ . '/../fixtures/json-schema-draft-03.json')
66+
);
67+
}
68+
69+
return $this->jsonSchemaDraft03;
70+
}
71+
72+
/**
73+
* @return object
74+
*/
75+
private function getJsonSchemaDraft04()
76+
{
77+
if (!$this->jsonSchemaDraft04) {
78+
$this->jsonSchemaDraft04 = json_decode(
79+
file_get_contents(__DIR__ . '/../fixtures/json-schema-draft-04.json')
80+
);
81+
}
82+
83+
return $this->jsonSchemaDraft04;
84+
}
85+
}

0 commit comments

Comments
 (0)