Skip to content

Commit 42efaca

Browse files
committed
v0.1.0
1 parent 00bd7ae commit 42efaca

File tree

5 files changed

+295
-40
lines changed

5 files changed

+295
-40
lines changed

README.md

Lines changed: 156 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,171 @@
22

33
High definition PHP structures with JSON-schema based validation.
44

5+
## Installation
6+
7+
```
8+
composer require swaggest/json-schema
9+
```
10+
511
## Usage
612

13+
Structure definition can be done either with `json-schema` or with
14+
`PHP` class extending `Swaggest\JsonSchema\Structure\ClassStructure`
15+
716
### Validating JSON data against given schema
817

18+
Define your json-schema
19+
```php
20+
$schemaJson = <<<'JSON'
21+
{
22+
"type": "object",
23+
"properties": {
24+
"id": {
25+
"type": "integer"
26+
},
27+
"name": {
28+
"type": "string"
29+
},
30+
"orders": {
31+
"type": "array",
32+
"items": {
33+
"$ref": "#/definitions/order"
34+
}
35+
}
36+
},
37+
"required":["id"],
38+
"definitions": {
39+
"order": {
40+
"type": "object",
41+
"properties": {
42+
"id": {
43+
"type": "integer"
44+
},
45+
"price": {
46+
"type": "number"
47+
},
48+
"updated": {
49+
"type": "string",
50+
"format": "date-time"
51+
}
52+
},
53+
"required":["id"]
54+
}
55+
}
56+
}
57+
JSON;
58+
```
59+
60+
Load it
961
```php
62+
$schema = SchemaLoader::create()->readSchema(json_decode($schemaJson));
63+
```
1064

65+
Validate data
66+
```php
67+
$schema->import(json_decode(<<<'JSON'
68+
{
69+
"id": 1,
70+
"name":"John Doe",
71+
"orders":[
72+
{
73+
"id":1
74+
},
75+
{
76+
"price":1.0
77+
}
78+
]
79+
}
80+
JSON
81+
)); // Exception: Required property missing: id at #->properties:orders->items[1]->#/definitions/order
1182
```
1283

13-
### Validated PHP classes
84+
### PHP structured classes with validation
1485

1586
```php
16-
$schema = new Schema();
17-
18-
$schema->properties = Properties::create()->....;
19-
$schema->type = Type::create('object');
20-
21-
$schema->reset();
22-
$schema->import(json_decode('...'));
23-
24-
$schema->setPreserveNulls(true);
25-
try {
26-
$data = $schema->import(json_decode('...'));
27-
$data->property->deeper->olala;
28-
$data->additionalProperties[0];
29-
$data->property->additionalProperties[0];
30-
} catch (Exception $e) {
31-
$e->getMessage(); // Invalid value for properties::property->properties::deeper, object expected
87+
/**
88+
* @property int $quantity PHPDoc defined dynamic properties will be validated on every set
89+
*/
90+
class Example extends ClassStructure
91+
{
92+
/* Native (public) properties will be validated only on import and export of structure data */
93+
94+
/** @var int */
95+
public $id;
96+
public $name;
97+
/** @var Order[] */
98+
public $orders;
99+
100+
/**
101+
* Define your properties
102+
*
103+
* @param Properties|static $properties
104+
* @param Schema $ownerSchema
105+
*/
106+
public static function setUpProperties($properties, Schema $ownerSchema)
107+
{
108+
$properties->id = Schema::integer();
109+
$properties->name = Schema::string();
110+
111+
$properties->quantity = Schema::integer();
112+
$properties->quantity->minimum = 0;
113+
114+
$properties->orders = Schema::create();
115+
$properties->orders->items = Order::schema();
116+
117+
$ownerSchema->required = array(self::names()->id);
118+
}
119+
}
120+
121+
/**
122+
*
123+
*/
124+
class Order extends ClassStructure
125+
{
126+
public $id;
127+
public $dateTime;
128+
public $price;
129+
130+
/**
131+
* @param Properties|static $properties
132+
* @param Schema $ownerSchema
133+
*/
134+
public static function setUpProperties($properties, Schema $ownerSchema)
135+
{
136+
$properties->id = Schema::integer();
137+
$properties->dateTime = Schema::string();
138+
$properties->dateTime->format = Schema::FORMAT_DATE_TIME;
139+
$properties->price = Schema::number();
140+
141+
$ownerSchema->required[] = self::names()->id;
142+
}
32143
}
33144
```
145+
146+
Validation of dynamic properties is performed on set,
147+
this can help to find source of invalid data at cost of
148+
some performance drop
149+
```php
150+
$example = new Example();
151+
$example->quantity = -1; // Exception: Value more than 0 expected, -1 received
152+
```
153+
154+
Validation of native properties is performed only on import/export
155+
```php
156+
$example = new Example();
157+
$example->quantity = 10;
158+
Example::export($example); // Exception: Required property missing: id
159+
```
160+
161+
Error messages provide a path to invalid data
162+
```php
163+
$example = new Example();
164+
$example->id = 1;
165+
$example->name = 'John Doe';
166+
167+
$order = new Order();
168+
$order->dateTime = (new \DateTime())->format(DATE_RFC3339);
169+
$example->orders[] = $order;
170+
171+
Example::export($example); // Exception: Required property missing: id at #->properties:orders->items[0]
172+
```

src/MagicMap.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ public function __set($name, $column)
1414

1515
public function &__get($name)
1616
{
17-
return $this->_arrayOfData[$name];
17+
if (isset($this->_arrayOfData[$name])) {
18+
return $this->_arrayOfData[$name];
19+
} else {
20+
$tmp = null;
21+
return $tmp;
22+
}
1823
}
1924

2025
public function offsetExists($offset)
@@ -24,7 +29,12 @@ public function offsetExists($offset)
2429

2530
public function &offsetGet($offset)
2631
{
27-
return $this->_arrayOfData[$offset];
32+
if (isset($this->_arrayOfData[$offset])) {
33+
return $this->_arrayOfData[$offset];
34+
} else {
35+
$tmp = null;
36+
return $tmp;
37+
}
2838
}
2939

3040
public function offsetSet($offset, $value)

src/Schema.php

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Swaggest\JsonSchema;
44

55

6+
use PhpLang\ScopeExit;
67
use Swaggest\JsonSchema\Constraint\Properties;
78
use Swaggest\JsonSchema\Constraint\Ref;
89
use Swaggest\JsonSchema\Constraint\Type;
@@ -14,6 +15,7 @@
1415
use Swaggest\JsonSchema\Exception\ObjectException;
1516
use Swaggest\JsonSchema\Exception\StringException;
1617
use Swaggest\JsonSchema\Exception\TypeException;
18+
use Swaggest\JsonSchema\Structure\ClassStructure;
1719
use Swaggest\JsonSchema\Structure\ObjectItem;
1820

1921
class Schema extends MagicMap
@@ -216,23 +218,31 @@ private function process($data, $import = true, $path = '#')
216218
if ($this->maximum !== null) {
217219
if ($this->exclusiveMaximum === true) {
218220
if ($data >= $this->maximum) {
219-
$this->fail(new NumericException('Maximum value exceeded', NumericException::MAXIMUM), $path);
221+
$this->fail(new NumericException(
222+
'Value less or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
223+
NumericException::MAXIMUM), $path);
220224
}
221225
} else {
222226
if ($data > $this->maximum) {
223-
$this->fail(new NumericException('Maximum value exceeded', NumericException::MAXIMUM), $path);
227+
$this->fail(new NumericException(
228+
'Value less than ' . $this->minimum . ' expected, ' . $data . ' received',
229+
NumericException::MAXIMUM), $path);
224230
}
225231
}
226232
}
227233

228234
if ($this->minimum !== null) {
229235
if ($this->exclusiveMinimum === true) {
230236
if ($data <= $this->minimum) {
231-
$this->fail(new NumericException('Minimum value exceeded', NumericException::MINIMUM), $path);
237+
$this->fail(new NumericException(
238+
'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
239+
NumericException::MINIMUM), $path);
232240
}
233241
} else {
234242
if ($data < $this->minimum) {
235-
$this->fail(new NumericException('Minimum value exceeded', NumericException::MINIMUM), $path);
243+
$this->fail(new NumericException(
244+
'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
245+
NumericException::MINIMUM), $path);
236246
}
237247
}
238248
}
@@ -255,6 +265,15 @@ private function process($data, $import = true, $path = '#')
255265
} else {
256266
$result = new $this->objectItemClass;
257267
}
268+
269+
if ($result instanceof ClassStructure) {
270+
if ($result->__validateOnSet) {
271+
$result->__validateOnSet = false;
272+
$validateOnSetHandler = new ScopeExit(function()use($result){
273+
$result->__validateOnSet = true;
274+
});
275+
}
276+
}
258277
}
259278

260279
if ($this->properties !== null) {
@@ -341,7 +360,8 @@ private function process($data, $import = true, $path = '#')
341360
$value = $items[$index]->process($value, $import, $path . '->items:' . $index);
342361
} else {
343362
if ($additionalItems instanceof Schema) {
344-
$value = $additionalItems->process($value, $import, $path . '->' . $pathItems);
363+
$value = $additionalItems->process($value, $import, $path . '->' . $pathItems
364+
. '[' . $index . ']');
345365
} elseif ($additionalItems === false) {
346366
$this->fail(new ArrayException('Unexpected array item'), $path);
347367
}

src/Structure/ClassStructure.php

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,19 @@ abstract class ClassStructure extends ObjectItem implements ClassStructureContra
1414
*/
1515
public static function schema()
1616
{
17-
$schema = new Schema();
18-
$schema->type = new Type(Type::OBJECT);
19-
$properties = new Properties();
20-
$schema->properties = $properties;
21-
$schema->objectItemClass = get_called_class();
22-
static::setUpProperties($properties, $schema);
17+
static $schemas = array();
18+
$className = get_called_class();
19+
$schema = &$schemas[$className];
20+
21+
if (null === $schema) {
22+
$schema = new Schema();
23+
$schema->type = new Type(Type::OBJECT);
24+
$properties = new Properties();
25+
$schema->properties = $properties;
26+
$schema->objectItemClass = get_called_class();
27+
static::setUpProperties($properties, $schema);
28+
}
29+
2330
return $schema;
2431
}
2532

@@ -53,15 +60,15 @@ static function create()
5360
}
5461

5562
protected $__hasNativeProperties = true;
56-
protected $__validateOnSet = true;
63+
protected $__validateOnSet = true; // todo skip validation during import
5764

5865
public function jsonSerialize()
5966
{
6067
if ($this->__hasNativeProperties) {
6168
$result = new \stdClass();
6269
foreach (static::schema()->properties->toArray() as $name => $schema) {
6370
$value = $this->$name;
64-
if (null !== $value || array_key_exists($name, $this->_arrayOfData)) {
71+
if ((null !== $value) || array_key_exists($name, $this->_arrayOfData)) {
6572
$result->$name = $value;
6673
}
6774
}
@@ -94,6 +101,4 @@ public function __set($name, $column)
94101
$this->_arrayOfData[$name] = $column;
95102
return $this;
96103
}
97-
98-
99104
}

0 commit comments

Comments
 (0)