Skip to content

Commit 8f40bb7

Browse files
committed
#18 immutable schemas and properties
1 parent 2755282 commit 8f40bb7

File tree

13 files changed

+345
-76
lines changed

13 files changed

+345
-76
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ class User extends ClassStructure
144144
$defaultOptions = new UserOptions();
145145
$defaultOptions->autoLogin = true;
146146
$defaultOptions->groupName = 'guest';
147-
$properties->options = (clone UserOptions::schema())->setDefault(UserOptions::export($defaultOptions));
147+
// UserOptions::schema() is safe to change as it is protected with lazy cloning
148+
$properties->options = UserOptions::schema()->setDefault(UserOptions::export($defaultOptions));
148149

149150
// Dynamic (phpdoc-defined) properties can be used as well
150151
$properties->quantity = Schema::integer();

src/Constraint/Properties.php

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,31 @@
44

55
use Swaggest\JsonSchema\Exception;
66
use Swaggest\JsonSchema\Schema;
7+
use Swaggest\JsonSchema\SchemaContract;
78
use Swaggest\JsonSchema\Structure\Egg;
89
use Swaggest\JsonSchema\Structure\Nested;
910
use Swaggest\JsonSchema\Structure\ObjectItem;
1011

1112
/**
12-
* @method Schema __get($key)
13+
* @method SchemaContract __get($key)
1314
* @method Schema[] toArray()
1415
*/
1516
class Properties extends ObjectItem implements Constraint
1617
{
18+
private $__isReadOnly = false;
19+
1720
/** @var Schema[] */
1821
protected $__arrayOfData = array();
1922

2023
/** @var Schema */
2124
protected $__schema;
2225

23-
public function setSchema(Schema $schema)
26+
public function lock()
2427
{
25-
$this->__schema = $schema;
28+
$this->__isReadOnly = true;
2629
return $this;
2730
}
2831

29-
public function getSchema()
30-
{
31-
return $this->__schema;
32-
}
33-
3432
/**
3533
* @param string $name
3634
* @param mixed $column
@@ -39,6 +37,9 @@ public function getSchema()
3937
*/
4038
public function __set($name, $column)
4139
{
40+
if ($this->__isReadOnly) {
41+
throw new Exception('Trying to modify read-only Properties');
42+
}
4243
if ($column instanceof Nested) {
4344
$this->addNested($column->schema, $name);
4445
return $this;
@@ -55,30 +56,28 @@ public static function create()
5556
/** @var Schema|null */
5657
private $additionalProperties;
5758

58-
/**
59-
* @param Schema $additionalProperties
60-
* @return Properties
61-
*/
62-
public function setAdditionalProperties(Schema $additionalProperties = null)
63-
{
64-
$this->additionalProperties = $additionalProperties;
65-
return $this;
66-
}
67-
68-
6959
/** @var Egg[][] */
7060
public $nestedProperties = array();
7161

72-
/** @var Schema[] */
62+
/** @var string[] */
7363
public $nestedPropertyNames = array();
7464

75-
protected function addNested(Schema $nested, $name)
65+
/**
66+
* @param SchemaContract $nested
67+
* @param string $name
68+
* @return $this
69+
* @throws Exception
70+
*/
71+
protected function addNested(SchemaContract $nested, $name)
7672
{
77-
if (null === $nested->properties) {
73+
if ($this->__isReadOnly) {
74+
throw new Exception('Trying to modify read-only Properties');
75+
}
76+
if (null === $nested->getProperties()) {
7877
throw new Exception('Schema with properties required', Exception::PROPERTIES_REQUIRED);
7978
}
8079
$this->nestedPropertyNames[$name] = $name;
81-
foreach ($nested->properties->toArray() as $propertyName => $property) {
80+
foreach ($nested->getProperties()->toArray() as $propertyName => $property) {
8281
$this->nestedProperties[$propertyName][] = new Egg($nested, $name, $property);
8382
}
8483
return $this;

src/Schema.php

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* Class Schema
3030
* @package Swaggest\JsonSchema
3131
*/
32-
class Schema extends JsonSchema implements MetaHolder
32+
class Schema extends JsonSchema implements MetaHolder, SchemaContract
3333
{
3434
const CONST_PROPERTY = 'const';
3535

@@ -95,7 +95,7 @@ public function addPropertyMapping($dataName, $propertyName, $mapping = self::DE
9595
/**
9696
* @param mixed $data
9797
* @param Context|null $options
98-
* @return static|JsonSchema
98+
* @return SchemaContract
9999
* @throws Exception
100100
* @throws InvalidValue
101101
* @throws \Exception
@@ -124,7 +124,7 @@ public static function import($data, Context $options = null)
124124
}
125125

126126
$data = self::unboolSchema($data);
127-
if ($data instanceof Schema) {
127+
if ($data instanceof SchemaContract) {
128128
return $data;
129129
}
130130

@@ -727,13 +727,14 @@ private function processObject($data, Context $options, $path, $result = null)
727727
&& $properties !== null
728728
) {
729729
foreach ($properties as $key => $property) {
730-
if (isset($property->default)) {
730+
// todo check when property is \stdClass `{}` here (RefTest)
731+
if ($property instanceof SchemaContract && null !== $default = $property->getDefault()) {
731732
if (isset($this->__dataToProperty[$options->mapping][$key])) {
732733
$key = $this->__dataToProperty[$options->mapping][$key];
733734
}
734735
if (!array_key_exists($key, $array)) {
735736
$defaultApplied[$key] = true;
736-
$array[$key] = $property->default;
737+
$array[$key] = $default;
737738
}
738739
}
739740
}
@@ -751,7 +752,7 @@ private function processObject($data, Context $options, $path, $result = null)
751752
if (isset($deps->$key)) {
752753
$dependencies = $deps->$key;
753754
$dependencies = self::unboolSchema($dependencies);
754-
if ($dependencies instanceof Schema) {
755+
if ($dependencies instanceof SchemaContract) {
755756
$dependencies->process($data, $options, $path . '->dependencies:' . $key);
756757
} else {
757758
foreach ($dependencies as $item) {
@@ -770,7 +771,7 @@ private function processObject($data, Context $options, $path, $result = null)
770771
$prop = self::unboolSchema($properties[$key]);
771772
$propertyFound = true;
772773
$found = true;
773-
if ($prop instanceof Schema) {
774+
if ($prop instanceof SchemaContract) {
774775
$value = $prop->process(
775776
$value,
776777
isset($defaultApplied[$key]) ? $options->withDefault() : $options,
@@ -806,7 +807,7 @@ private function processObject($data, Context $options, $path, $result = null)
806807
$this->fail(new ObjectException('Additional properties not allowed'), $path . ':' . $key);
807808
}
808809

809-
if ($this->additionalProperties instanceof Schema) {
810+
if ($this->additionalProperties instanceof SchemaContract) {
810811
$value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key);
811812
}
812813

@@ -863,7 +864,7 @@ private function processArray($data, Context $options, $path, $result)
863864

864865
$pathItems = 'items';
865866
$this->items = self::unboolSchema($this->items);
866-
if ($this->items instanceof Schema) {
867+
if ($this->items instanceof SchemaContract) {
867868
$items = array();
868869
$additionalItems = $this->items;
869870
} elseif ($this->items === null) { // items defaults to empty schema so everything is valid
@@ -886,7 +887,7 @@ private function processArray($data, Context $options, $path, $result)
886887
$itemSchema = self::unboolSchema($items[$index]);
887888
$result[$key] = $itemSchema->process($value, $options, $path . '->items:' . $index);
888889
} else {
889-
if ($additionalItems instanceof Schema) {
890+
if ($additionalItems instanceof SchemaContract) {
890891
$result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems
891892
. '[' . $index . ']');
892893
} elseif (!$options->skipValidation && $additionalItems === false) {
@@ -1235,4 +1236,18 @@ private static function unboolSchemaData($data)
12351236
}
12361237
}
12371238

1239+
public function getDefault()
1240+
{
1241+
return $this->default;
1242+
}
1243+
1244+
public function getProperties()
1245+
{
1246+
return $this->properties;
1247+
}
1248+
1249+
public function getObjectItemClass()
1250+
{
1251+
return $this->objectItemClass;
1252+
}
12381253
}

src/SchemaContract.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Swaggest\JsonSchema;
4+
5+
6+
use Swaggest\JsonSchema\Constraint\Properties;
7+
8+
interface SchemaContract
9+
{
10+
/**
11+
* @param mixed $data
12+
* @param Context $options
13+
* @param string $path
14+
* @param mixed|null $result
15+
* @return array|mixed|null|object|\stdClass
16+
*/
17+
public function process($data, Context $options, $path = '#', $result = null);
18+
19+
/**
20+
* @param mixed $data
21+
* @param Context|null $options
22+
* @return array|mixed|null|object|\stdClass
23+
*/
24+
public function in($data, Context $options = null);
25+
26+
/**
27+
* @param mixed $data
28+
* @param Context|null $options
29+
* @return array|mixed|null|object|\stdClass
30+
*/
31+
public function out($data, Context $options = null);
32+
33+
/**
34+
* @return mixed
35+
*/
36+
public function getDefault();
37+
38+
/** @return null|Properties|Schema[] */
39+
public function getProperties();
40+
41+
/**
42+
* @param Context|null $options
43+
* @return Structure\ObjectItemContract
44+
*/
45+
public function makeObjectItem(Context $options = null);
46+
47+
/**
48+
* @return string
49+
*/
50+
public function getObjectItemClass();
51+
52+
}

src/Structure/ClassSchema.php

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/Structure/ClassStructureTrait.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,42 @@
44

55
use Swaggest\JsonSchema\Constraint\Properties;
66
use Swaggest\JsonSchema\Context;
7+
use Swaggest\JsonSchema\Schema;
8+
use Swaggest\JsonSchema\Wrapper;
79
use Swaggest\JsonSchema\NameMirror;
810

911
trait ClassStructureTrait
1012
{
1113
use ObjectItemTrait;
1214

1315
/**
14-
* @return ClassSchema
16+
* @return Wrapper
1517
*/
1618
public static function schema()
1719
{
1820
static $schemas = array();
1921
$className = get_called_class();
20-
$schema = &$schemas[$className];
22+
$schemaWrapper = &$schemas[$className];
2123

22-
if (null === $schema) {
23-
$schema = new ClassSchema();
24+
if (null === $schemaWrapper) {
25+
$schema = new Schema();
2426
$properties = new Properties();
2527
$schema->properties = $properties;
2628
$schema->objectItemClass = $className;
29+
$schemaWrapper = new Wrapper($schema);
2730
static::setUpProperties($properties, $schema);
31+
$properties->lock();
2832
}
2933

30-
return $schema;
34+
return $schemaWrapper;
3135
}
3236

3337
/**
3438
* @return Properties|static
3539
*/
3640
public static function properties()
3741
{
38-
return static::schema()->properties;
42+
return static::schema()->getProperties();
3943
}
4044

4145
/**
@@ -86,14 +90,14 @@ public static function create()
8690
public function jsonSerialize()
8791
{
8892
$result = new \stdClass();
89-
$properties = static::schema()->properties;
90-
foreach ($properties->toArray() as $name => $schema) {
93+
$schema = static::schema();
94+
foreach ($schema->getPropertyNames() as $name) {
9195
$value = $this->$name;
9296
if ((null !== $value) || array_key_exists($name, $this->__arrayOfData)) {
9397
$result->$name = $value;
9498
}
9599
}
96-
foreach ($properties->nestedPropertyNames as $name) {
100+
foreach ($schema->getNestedPropertyNames() as $name) {
97101
/** @var ObjectItem $nested */
98102
$nested = $this->$name;
99103
if (null !== $nested) {
@@ -121,7 +125,7 @@ public static function names()
121125
public function __set($name, $column) // todo nested schemas
122126
{
123127
if ($this->__validateOnSet) {
124-
if ($property = static::schema()->properties[$name]) {
128+
if ($property = static::schema()->getProperty($name)) {
125129
$property->out($column);
126130
}
127131
}

src/Structure/Composition.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
use Swaggest\JsonSchema\Constraint\Properties;
66
use Swaggest\JsonSchema\Constraint\Type;
77
use Swaggest\JsonSchema\Schema;
8+
use Swaggest\JsonSchema\Wrapper;
89

910
/**
1011
* @todo think of anyOf, allOf, oneOf
1112
*/
1213
class Composition extends Schema
1314
{
1415
/**
15-
* @param Schema... $schema
16+
* @param Schema|Wrapper... $schema
17+
* @throws \Swaggest\JsonSchema\Exception
1618
*/
1719
public function __construct()
1820
{
@@ -21,7 +23,7 @@ public function __construct()
2123
$this->properties = $properties;
2224

2325
foreach (func_get_args() as $arg) {
24-
if ($arg instanceof ClassSchema) {
26+
if ($arg instanceof Wrapper) {
2527
$properties->__set($arg->objectItemClass, $arg->nested());
2628
}
2729
}

0 commit comments

Comments
 (0)