Skip to content

Commit 407d439

Browse files
committed
Add all valid properties to Schema
- Fixes the problem that undefined properties do not throw errors when they are accessed but return their default values. - Also improved annotations for Schema, which can always be a Reference. - Fixed implementation of default values to consider null. - Improve handling of scalar types. Fixes #36
1 parent 13b9175 commit 407d439

File tree

5 files changed

+116
-21
lines changed

5 files changed

+116
-21
lines changed

src/SpecBaseObject.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ public function __construct(array $data)
6262
continue;
6363
}
6464

65-
if ($type === Type::STRING || $type === Type::ANY) {
66-
$this->_properties[$property] = $data[$property];
67-
} elseif ($type === Type::BOOLEAN) {
65+
if ($type === Type::BOOLEAN) {
6866
if (!\is_bool($data[$property])) {
6967
$this->_errors[] = "property '$property' must be boolean, but " . gettype($data[$property]) . " given.";
7068
continue;
@@ -85,7 +83,7 @@ public function __construct(array $data)
8583
$this->_errors[] = "property '$property' must be array of strings, but array has " . gettype($item) . " element.";
8684
}
8785
$this->_properties[$property][] = $item;
88-
} elseif ($type[0] === Type::ANY || $type[0] === Type::BOOLEAN || $type[0] === Type::INTEGER) { // TODO simplify handling of scalar types
86+
} elseif ($type[0] === Type::ANY || Type::isScalar($type[0])) {
8987
$this->_properties[$property][] = $item;
9088
} else {
9189
$this->_properties[$property][] = $this->instantiate($type[0], $item);
@@ -104,14 +102,16 @@ public function __construct(array $data)
104102
$this->_errors[] = "property '$property' must be map<string, string>, but entry '$key' is of type " . \gettype($item) . '.';
105103
}
106104
$this->_properties[$property][$key] = $item;
107-
} elseif ($type[1] === Type::ANY || $type[1] === Type::BOOLEAN || $type[1] === Type::INTEGER) { // TODO simplify handling of scalar types
105+
} elseif ($type[1] === Type::ANY || Type::isScalar($type[1])) {
108106
$this->_properties[$property][$key] = $item;
109107
} else {
110108
$this->_properties[$property][$key] = $this->instantiate($type[1], $item);
111109
}
112110
}
113111
break;
114112
}
113+
} elseif ($type === Type::ANY || Type::isScalar($type)) {
114+
$this->_properties[$property] = $data[$property];
115115
} else {
116116
$this->_properties[$property] = $this->instantiate($type, $data[$property]);
117117
}
@@ -272,7 +272,7 @@ protected function addError(string $error, $class = '')
272272

273273
protected function hasProperty(string $name): bool
274274
{
275-
return isset($this->_properties[$name]) || isset(static::attributes()[$name]);
275+
return isset($this->_properties[$name]) || isset($this->attributes()[$name]);
276276
}
277277

278278
protected function requireProperties(array $names)
@@ -303,13 +303,14 @@ public function __get($name)
303303
if (isset($this->_properties[$name])) {
304304
return $this->_properties[$name];
305305
}
306-
if (isset(static::attributeDefaults()[$name])) {
307-
return static::attributeDefaults()[$name];
306+
$defaults = $this->attributeDefaults();
307+
if (array_key_exists($name, $defaults)) {
308+
return $defaults[$name];
308309
}
309-
if (isset(static::attributes()[$name])) {
310-
if (is_array(static::attributes()[$name])) {
310+
if (isset($this->attributes()[$name])) {
311+
if (is_array($this->attributes()[$name])) {
311312
return [];
312-
} elseif (static::attributes()[$name] === Type::BOOLEAN) {
313+
} elseif ($this->attributes()[$name] === Type::BOOLEAN) {
313314
return false;
314315
}
315316
return null;
@@ -324,7 +325,7 @@ public function __set($name, $value)
324325

325326
public function __isset($name)
326327
{
327-
if (isset($this->_properties[$name]) || isset(static::attributeDefaults()[$name]) || isset(static::attributes()[$name])) {
328+
if (isset($this->_properties[$name]) || isset($this->attributeDefaults()[$name]) || isset($this->attributes()[$name])) {
328329
return $this->__get($name) !== null;
329330
}
330331

src/spec/PathItem.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ protected function performValidation()
109109
public function getOperations()
110110
{
111111
$operations = [];
112-
foreach (static::attributes() as $attribute => $type) {
112+
foreach ($this->attributes() as $attribute => $type) {
113113
if ($type === Operation::class && isset($this->$attribute)) {
114114
$operations[$attribute] = $this->$attribute;
115115
}

src/spec/Schema.php

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@
4141
* @property array $enum
4242
*
4343
* @property string $type
44-
* @property Schema[] $allOf
45-
* @property Schema[] $oneOf
46-
* @property Schema[] $anyOf
47-
* @property Schema|null $not
48-
* @property Schema|null $items
49-
* @property Schema[] $properties
50-
* @property Schema|bool $additionalProperties
44+
* @property Schema[]|Reference[] $allOf
45+
* @property Schema[]|Reference[] $oneOf
46+
* @property Schema[]|Reference[] $anyOf
47+
* @property Schema|Reference|null $not
48+
* @property Schema|Reference|null $items
49+
* @property Schema[]|Reference[] $properties
50+
* @property Schema|Reference|bool $additionalProperties
5151
* @property string $description
5252
* @property string $format
5353
* @property mixed $default
@@ -70,6 +70,25 @@ class Schema extends SpecBaseObject
7070
protected function attributes(): array
7171
{
7272
return [
73+
// The following properties are taken directly from the JSON Schema definition and follow the same specifications:
74+
// types from https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-4 ff.
75+
'title' => Type::STRING,
76+
'multipleOf' => Type::NUMBER,
77+
'maximum' => Type::NUMBER,
78+
'exclusiveMaximum' => Type::BOOLEAN,
79+
'minimum' => Type::NUMBER,
80+
'exclusiveMinimum' => Type::BOOLEAN,
81+
'maxLength' => Type::INTEGER,
82+
'minLength' => Type::INTEGER,
83+
'pattern' => Type::STRING,
84+
'maxItems' => Type::INTEGER,
85+
'minItems' => Type::INTEGER,
86+
'uniqueItems' => Type::BOOLEAN,
87+
'maxProperties' => Type::INTEGER,
88+
'minProperties' => Type::INTEGER,
89+
'required' => [Type::STRING],
90+
'enum' => [Type::ANY],
91+
// The following properties are taken from the JSON Schema definition but their definitions were adjusted to the OpenAPI Specification.
7392
'type' => Type::STRING,
7493
'allOf' => [Schema::class],
7594
'oneOf' => [Schema::class],
@@ -81,7 +100,7 @@ protected function attributes(): array
81100
'description' => Type::STRING,
82101
'format' => Type::STRING,
83102
'default' => Type::ANY,
84-
103+
// Other than the JSON Schema subset fields, the following fields MAY be used for further schema documentation:
85104
'nullable' => Type::BOOLEAN,
86105
'discriminator' => Discriminator::class,
87106
'readOnly' => Type::BOOLEAN,
@@ -100,6 +119,11 @@ protected function attributeDefaults(): array
100119
{
101120
return [
102121
'additionalProperties' => true,
122+
'required' => null,
123+
'enum' => null,
124+
'allOf' => null,
125+
'oneOf' => null,
126+
'anyOf' => null,
103127
];
104128
}
105129

src/spec/Type.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,22 @@ class Type
2121
const BOOLEAN = 'boolean';
2222
const OBJECT = 'object';
2323
const ARRAY = 'array';
24+
25+
/**
26+
* Indicate whether a type is a scalar type, i.e. not an array or object.
27+
*
28+
* For ANY this will return false.
29+
*
30+
* @param string $type value from one of the type constants defined in this class.
31+
* @return bool whether the type is a scalar type.
32+
*/
33+
public static function isScalar(string $type): bool
34+
{
35+
return in_array($type, [
36+
self::INTEGER,
37+
self::NUMBER,
38+
self::STRING,
39+
self::BOOLEAN,
40+
]);
41+
}
2442
}

tests/spec/SchemaTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,56 @@ public function testAllOf()
231231
$this->assertArrayHasKey('id', $refResolved->properties);
232232
$this->assertArrayHasKey('name', $person->allOf[1]->properties);
233233
}
234+
235+
/**
236+
* Ensure Schema properties are accessable and have default values.
237+
*/
238+
public function testSchemaProperties()
239+
{
240+
$schema = new Schema([]);
241+
$validProperties = [
242+
// https://github.com/OAI/OpenAPI-Specification/blob/3.0.2/versions/3.0.2.md#schema-object
243+
// The following properties are taken directly from the JSON Schema definition and follow the same specifications:
244+
'title' => null,
245+
'multipleOf' => null,
246+
'maximum' => null,
247+
'exclusiveMaximum' => false,
248+
'minimum' => null,
249+
'exclusiveMinimum' => false,
250+
'maxLength' => null,
251+
'minLength' => null,
252+
'pattern' => null,
253+
'maxItems' => null,
254+
'minItems' => null,
255+
'uniqueItems' => false,
256+
'maxProperties' => null,
257+
'minProperties' => null,
258+
'required' => null, // if set, it should not be an empty array, according to the spec
259+
'enum' => null, // if it is an array, it means restriction of values
260+
// The following properties are taken from the JSON Schema definition but their definitions were adjusted to the OpenAPI Specification.
261+
'type' => null,
262+
'allOf' => null,
263+
'oneOf' => null,
264+
'anyOf' => null,
265+
'not' => null,
266+
'items' => null,
267+
'properties' => [],
268+
'additionalProperties' => true,
269+
'description' => null,
270+
'format' => null,
271+
'default' => null,
272+
// Other than the JSON Schema subset fields, the following fields MAY be used for further schema documentation:
273+
'nullable' => false,
274+
'readOnly' => false,
275+
'writeOnly' => false,
276+
'xml' => null,
277+
'externalDocs' => null,
278+
'example' => null,
279+
'deprecated' => false,
280+
];
281+
282+
foreach($validProperties as $property => $defaultValue) {
283+
$this->assertEquals($defaultValue, $schema->$property, "testing property '$property'");
284+
}
285+
}
234286
}

0 commit comments

Comments
 (0)