Completed
Push — master ( d2145e...0f5aad )
by Martijn
13s
created

ObjectType::parseDefinition()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 3
nop 1
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SwaggerGen\Swagger\Type;
4
5
/**
6
 * Basic object type definition.
7
 *
8
 * @package    SwaggerGen
9
 * @author     Martijn van der Lee <[email protected]>
10
 * @copyright  2014-2015 Martijn van der Lee
11
 * @license    https://opensource.org/licenses/MIT MIT
12
 */
13
class ObjectType extends AbstractType
14
{
15
16
	const REGEX_OBJECT_CONTENT = '(?:(\{)(.*)\})?';
17
	const REGEX_PROP_START = '/^';
18
	const REGEX_PROP_NAME = '([^?!:]+)';
19
	const REGEX_PROP_REQUIRED = '([\?!])?';
20
	const REGEX_PROP_ASSIGN = ':';
21
	const REGEX_PROP_DEFINITION = '(.+)';
22
	const REGEX_PROP_ADDITIONAL = '\.\.\.(!|.+)?';
23
	const REGEX_PROP_END = '$/';
24
25
	private $minProperties = null;
26
	private $maxProperties = null;
27
	private $discriminator = null;
28
	private $additionalProperties = null;
29
	private $required = array();
30
31
	/**
32
	 * @var Property[]
33
	 */
34
	private $properties = array();
35
36
	/**
37
	 * @var Property
38
	 */
39
	private $mostRecentProperty = null;
40
41
	protected function parseDefinition($definition)
42
	{
43
		$definition = self::trim($definition);
44
45
		$match = array();
46
		if (preg_match(self::REGEX_START . self::REGEX_FORMAT . self::REGEX_CONTENT . self::REGEX_RANGE . self::REGEX_END, $definition, $match) === 1) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
47
			// recognized format
48
		} elseif (preg_match(self::REGEX_START . self::REGEX_OBJECT_CONTENT . self::REGEX_RANGE . self::REGEX_END, $definition, $match) === 1) {
49
			$match[1] = 'object';
50
		} else {
51
			throw new \SwaggerGen\Exception("Unparseable object definition: '{$definition}'");
52
		}
53
54
55
		$this->parseFormat($definition, $match);
56
		$this->parseProperties($definition, $match);
57
		$this->parseRange($definition, $match);
58
	}
59
60
	private function parseFormat($definition, $match)
61
	{
62
		if (strtolower($match[1]) !== 'object') {
63
			throw new \SwaggerGen\Exception("Not an object: '{$definition}'");
64
		}
65
	}
66
67
	/**
68
	 * @param AbstractType|bool $type
69
	 * @return void
70
	 */
71
	private function setAdditionalProperties($type)
72
	{
73
		if ($this->additionalProperties !== null) {
74
			throw new \SwaggerGen\Exception('Additional properties may only be set once');
75
		}
76
		$this->additionalProperties = $type;
77
	}
78
79
	private function parseProperties($definition, $match)
1 ignored issue
show
Unused Code introduced by
The parameter $definition is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
80
	{
81
		if (empty($match[2])) {
82
			return;
83
		}
84
85
		while (($property = self::parseListItem($match[2])) !== '') {
86
			$prop_match = array();
87
			if (preg_match(self::REGEX_PROP_START . self::REGEX_PROP_ADDITIONAL . self::REGEX_PROP_END, $property, $prop_match) === 1) {
88
				if (empty($prop_match[1])) {
89
					$this->setAdditionalProperties(true);
90
				} else if ($prop_match[1] === '!') {
91
					$this->setAdditionalProperties(false);
92
				} else {
93
					$this->setAdditionalProperties(self::typeFactory($this, $prop_match[1], "Unparseable additional properties definition: '...%s'"));
0 ignored issues
show
Documentation introduced by
$this is of type this<SwaggerGen\Swagger\Type\ObjectType>, but the function expects a object<SwaggerGen\Swagge...Swagger\AbstractObject>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
94
				}
95
				continue;
96
			}
97
			if (preg_match(self::REGEX_PROP_START . self::REGEX_PROP_NAME . self::REGEX_PROP_REQUIRED . self::REGEX_PROP_ASSIGN . self::REGEX_PROP_DEFINITION . self::REGEX_PROP_END, $property, $prop_match) !== 1) {
98
				throw new \SwaggerGen\Exception("Unparseable property definition: '{$property}'");
99
			}
100
			$this->properties[$prop_match[1]] = new Property($this, $prop_match[3]);
101
			if ($prop_match[2] !== '!' && $prop_match[2] !== '?') {
102
				$this->required[$prop_match[1]] = true;
103
			}
104
		}
105
106
	}
107
108
	private function parseRange($definition, $match)
109
	{
110
		if (!empty($match[3])) {
111
			if ($match[4] === '' && $match[5] === '') {
112
				throw new \SwaggerGen\Exception("Empty object range: '{$definition}'");
113
			}
114
115
			$exclusiveMinimum = isset($match[3]) ? ($match[3] == '<') : null;
116
			$this->minProperties = $match[4] === '' ? null : intval($match[4]);
117
			$this->maxProperties = $match[5] === '' ? null : intval($match[5]);
118
			$exclusiveMaximum = isset($match[6]) ? ($match[6] == '>') : null;
119
			if ($this->minProperties && $this->maxProperties && $this->minProperties > $this->maxProperties) {
120
				self::swap($this->minProperties, $this->maxProperties);
121
				self::swap($exclusiveMinimum, $exclusiveMaximum);
122
			}
123
			$this->minProperties = $this->minProperties === null ? null : max(0, $exclusiveMinimum ? $this->minProperties + 1 : $this->minProperties);
124
			$this->maxProperties = $this->maxProperties === null ? null : max(0, $exclusiveMaximum ? $this->maxProperties - 1 : $this->maxProperties);
125
		}
126
	}
127
128
	private function setDiscriminator($discriminator)
129
	{
130
		if (!empty($this->discriminator)) {
131
			throw new \SwaggerGen\Exception("Discriminator may only be set once, "
132
											. "trying to change it "
133
											. "from '{$this->discriminator}' "
134
											. "to '{$discriminator}'");
135
		}
136
		if (isset($this->properties[$discriminator]) && empty($this->required[$discriminator])) {
137
			throw new \SwaggerGen\Exception("Discriminator must be a required property, "
138
											. "property '{$discriminator}' is not required");
139
		}
140
		$this->discriminator = $discriminator;
141
	}
142
143
	/**
144
	 * @param string $command The comment command
145
	 * @param string $data Any data added after the command
146
	 * @return \SwaggerGen\Swagger\Type\AbstractType|boolean
147
	 */
148
	public function handleCommand($command, $data = null)
149
	{
150
		switch (strtolower($command)) {
151
			// type name description...
152
			case 'additionalproperties':
153
				$value = self::wordShift($data);
154
				if ($value === 'false') {
155
					$type = false;
156
				} else if ($value === 'true') {
157
					$type = true;
158
				} else {
159
					$type = self::typeFactory($this, $value, "Unparseable additional properties definition: '%s'");
0 ignored issues
show
Documentation introduced by
$this is of type this<SwaggerGen\Swagger\Type\ObjectType>, but the function expects a object<SwaggerGen\Swagge...Swagger\AbstractObject>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Security Bug introduced by
It seems like $value defined by self::wordShift($data) on line 153 can also be of type false; however, SwaggerGen\Swagger\Type\...ractType::typeFactory() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
160
				}
161
				$this->setAdditionalProperties($type);
162
				return $this;
163
			case 'discriminator':
164
				$discriminator = self::wordShift($data);
165
				$this->setDiscriminator($discriminator);
166
				return $this;
167
			case 'property':
168
			case 'property?':
169
			case 'property!':
170
				$definition = self::wordShift($data);
171
				if (empty($definition)) {
172
					throw new \SwaggerGen\Exception("Missing property definition");
173
				}
174
175
				$name = self::wordShift($data);
176
				if (empty($name)) {
177
					throw new \SwaggerGen\Exception("Missing property name: '{$definition}'");
178
				}
179
180
				$readOnly = null;
181
				$required = false;
182
				$propertySuffix = substr($command, -1);
183
				if ($propertySuffix === '!') {
184
					$readOnly = true;
185
				} else if ($propertySuffix !== '?') {
186
					$required = true;
187
				}
188
189
				if (($name === $this->discriminator) && !$required) {
190
					throw new \SwaggerGen\Exception("Discriminator must be a required property, "
191
													. "property '{$name}' is not required");
192
				}
193
194
				unset($this->required[$name]);
195
				if ($required) {
196
					$this->required[$name] = true;
197
				}
198
199
				$this->mostRecentProperty = new Property($this, $definition, $data, $readOnly);
200
				$this->properties[$name] = $this->mostRecentProperty;
201
				return $this;
202
203
			case 'min':
204
				$this->minProperties = intval($data);
205
				if ($this->minProperties < 0) {
206
					throw new \SwaggerGen\Exception("Minimum less than zero: '{$data}'");
207
				}
208
				if ($this->maxProperties !== null && $this->minProperties > $this->maxProperties) {
209
					throw new \SwaggerGen\Exception("Minimum greater than maximum: '{$data}'");
210
				}
211
				$this->minProperties = intval($data);
212
				return $this;
213
214
			case 'max':
215
				$this->maxProperties = intval($data);
216
				if ($this->minProperties !== null && $this->minProperties > $this->maxProperties) {
217
					throw new \SwaggerGen\Exception("Maximum less than minimum: '{$data}'");
218
				}
219
				if ($this->maxProperties < 0) {
220
					throw new \SwaggerGen\Exception("Maximum less than zero: '{$data}'");
221
				}
222
				return $this;
223
		}
224
225
		// Pass through to most recent Property
226
		if ($this->mostRecentProperty && $this->mostRecentProperty->handleCommand($command, $data)) {
227
			return $this;
228
		}
229
230
		return parent::handleCommand($command, $data);
231
	}
232
233
	public function toArray()
234
	{
235
		return self::arrayFilterNull(array_merge(array(
236
					'type' => 'object',
237
					'required' => array_keys($this->required),
238
					'properties' => self::objectsToArray($this->properties),
239
					'minProperties' => $this->minProperties,
240
					'maxProperties' => $this->maxProperties,
241
					'discriminator' => $this->discriminator,
242
					'additionalProperties' => $this->additionalProperties instanceof AbstractType ? $this->additionalProperties->toArray() : $this->additionalProperties,
243
								), parent::toArray()));
244
	}
245
246
	public function __toString()
247
	{
248
		return __CLASS__;
249
	}
250
251
}
252