forked from cebe/php-openapi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSpecBaseObject.php
More file actions
197 lines (178 loc) · 6.74 KB
/
Copy pathSpecBaseObject.php
File metadata and controls
197 lines (178 loc) · 6.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<?php
namespace cebe\openapi;
use cebe\openapi\exceptions\ReadonlyPropertyException;
use cebe\openapi\exceptions\UnknownPropertyException;
/**
* Base class for all spec objects.
*
* Implements property management and validation basics.
*
* @author Carsten Brandt <mail@cebe.cc>
*/
abstract class SpecBaseObject
{
private $_properties = [];
private $_errors = [];
/**
* @return array array of attributes available in this object.
*/
abstract protected function attributes(): array;
/**
* Perform validation on this object, check data against OpenAPI Specification rules.
*
* Call `addError()` in case of validation errors.
*/
abstract protected function performValidation();
/**
* Create an object from spec data.
* @param array $data spec data read from YAML or JSON
*/
public function __construct(array $data)
{
foreach($this->attributes() as $property => $type) {
if (!isset($data[$property])) {
continue;
}
if ($type === 'string' || $type === 'any') {
$this->_properties[$property] = $data[$property];
} elseif ($type === 'boolean') {
if (!is_bool($data[$property])) {
$this->_errors[] = "property '$property' must be boolean, but " . gettype($data[$property]) . " given.";
continue;
}
$this->_properties[$property] = (bool) $data[$property];
} elseif (is_array($type)) {
if (!is_array($data[$property])) {
$this->_errors[] = "property '$property' must be array, but " . gettype($data[$property]) . " given.";
continue;
}
switch (count($type)) {
case 1:
// array
$this->_properties[$property] = [];
foreach($data[$property] as $item) {
if ($type[0] === 'string') {
if (!is_string($item)) {
$this->_errors[] = "property '$property' must be array of strings, but array has " . gettype($item) . " element.";
}
$this->_properties[$property][] = $item;
} else {
// TODO implement reference objects
$this->_properties[$property][] = new $type[0]($item);
}
}
break;
case 2:
// map
if ($type[0] !== 'string') {
throw new \Exception('Invalid map key type: ' . $type[0]);
}
$this->_properties[$property] = [];
foreach($data[$property] as $key => $item) {
if ($type[1] === 'string') {
if (!is_string($item)) {
$this->_errors[] = "property '$property' must be map<string, string>, but entry '$key' is of type " . gettype($item) . ".";
}
$this->_properties[$property][$key] = $item;
} else {
// TODO implement reference objects
$this->_properties[$property][$key] = new $type[1]($item);
}
}
break;
}
} else {
$this->_properties[$property] = new $type($data[$property]);
}
unset($data[$property]);
}
foreach($data as $additionalProperty => $value) {
$this->_properties[$additionalProperty] = $value;
}
}
/**
* Validate object data according to OpenAPI spec.
* @return bool whether the loaded data is valid according to OpenAPI spec
* @see getErrors()
*/
public function validate(): bool
{
foreach($this->_properties as $k => $v) {
if ($v instanceof self) {
$v->validate();
}
}
$this->performValidation();
return count($this->getErrors()) === 0;
}
/**
* @return string[] list of validation errors according to OpenAPI spec.
* @see validate()
*/
public function getErrors(): array
{
$errors = [$this->_errors];
foreach($this->_properties as $k => $v) {
if ($v instanceof self) {
$errors[] = $v->getErrors();
}
}
return array_merge(...$errors);
}
/**
* @param string $error error message to add.
*/
protected function addError(string $error)
{
$this->_errors[] = $error;
}
protected function hasProperty(string $name): bool
{
return isset($this->_properties[$name]) || isset(static::attributes()[$name]);
}
protected function requireProperties(array $names)
{
foreach ($names as $name) {
if (!isset($this->_properties[$name])) {
$this->addError("Missing required property: $name");
}
}
}
protected function validateEmail(string $property)
{
if (!empty($this->$property) && strpos($this->$property, '@') === false) {
$this->addError(__CLASS__ . '::$'.$property.' does not seem to be a valid email address: ' . $this->$property);
}
}
protected function validateUrl(string $property)
{
if (!empty($this->$property) && strpos($this->$property, '//') === false) {
$this->addError(__CLASS__ . '::$'.$property.' does not seem to be a valid URL: ' . $this->$property);
}
}
public function __get($name)
{
if (isset($this->_properties[$name])) {
return $this->_properties[$name];
}
if (isset(static::attributes()[$name])) {
return is_array(static::attributes()[$name]) ? [] : null;
}
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
public function __set($name, $value)
{
throw new ReadonlyPropertyException('Setting read-only property: ' . get_class($this) . '::' . $name);
}
public function __isset($name)
{
if (isset($this->_properties[$name]) || isset(static::attributes()[$name])) {
return $this->__get($name) === null;
}
return false;
}
public function __unset($name)
{
throw new ReadonlyPropertyException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
}
}