Completed
Push — master ( d91fed...fd66aa )
by Josh
17:36
created

Configurable::__isset()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2018 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Configurator\Traits;
9
10
use InvalidArgumentException;
11
use RuntimeException;
12
use Traversable;
13
use s9e\TextFormatter\Configurator\Collections\Collection;
14
use s9e\TextFormatter\Configurator\Collections\NormalizedCollection;
15
16
/**
17
* Provides magic __get, __set, __isset and __unset implementations
18
*/
19
trait Configurable
20
{
21
	/**
22
	* Magic getter
23
	*
24
	* Will return $this->foo if it exists, then $this->getFoo() or will throw an exception if
25
	* neither exists
26
	*
27
	* @param  string $propName
28
	* @return mixed
29
	*/
30
	public function __get($propName)
31
	{
32
		$methodName = 'get' . ucfirst($propName);
33
34
		// Look for a getter, e.g. getDefaultTemplate()
35
		if (method_exists($this, $methodName))
36
		{
37
			return $this->$methodName();
38
		}
39
40
		if (!property_exists($this, $propName))
41
		{
42
			throw new RuntimeException("Property '" . $propName . "' does not exist");
43
		}
44
45
		return $this->$propName;
46
	}
47
48
	/**
49
	* Magic setter
50
	*
51
	* Will call $this->setFoo($propValue) if it exists, otherwise it will set $this->foo.
52
	* If $this->foo is a NormalizedCollection, we do not replace it, instead we clear() it then
53
	* fill it back up. It will not overwrite an object with a different incompatible object (of a
54
	* different, non-extending class) and it will throw an exception if the PHP type cannot match
55
	* without incurring data loss.
56
	*
57
	* @param  string $propName
58
	* @param  mixed  $propValue
59
	* @return void
60
	*/
61
	public function __set($propName, $propValue)
62
	{
63
		$methodName = 'set' . ucfirst($propName);
64
65
		// Look for a setter, e.g. setDefaultChildRule()
66
		if (method_exists($this, $methodName))
67
		{
68
			$this->$methodName($propValue);
69
70
			return;
71
		}
72
73
		// If the property isn't already set, we just create/set it
74
		if (!isset($this->$propName))
75
		{
76
			$this->$propName = $propValue;
77
78
			return;
79
		}
80
81
		// If we're trying to replace a NormalizedCollection, instead we clear it then
82
		// iteratively set new values
83
		if ($this->$propName instanceof NormalizedCollection)
84
		{
85
			if (!is_array($propValue)
86
			 && !($propValue instanceof Traversable))
87
			{
88
				throw new InvalidArgumentException("Property '" . $propName . "' expects an array or a traversable object to be passed");
89
			}
90
91
			$this->$propName->clear();
92
93
			foreach ($propValue as $k => $v)
94
			{
95
				$this->$propName->set($k, $v);
96
			}
97
98
			return;
99
		}
100
101
		// If this property is an object, test whether they are compatible. Otherwise, test if PHP
102
		// types are compatible
103
		if (is_object($this->$propName))
104
		{
105
			if (!($propValue instanceof $this->$propName))
106
			{
107
				throw new InvalidArgumentException("Cannot replace property '" . $propName . "' of class '" . get_class($this->$propName) . "' with instance of '" . get_class($propValue) . "'");
108
			}
109
		}
110
		else
111
		{
112
			// Test whether the PHP types are compatible
113
			$oldType = gettype($this->$propName);
114
			$newType = gettype($propValue);
115
116
			// If the property is a boolean, we'll accept "true" and "false" as strings
117
			if ($oldType === 'boolean')
118
			{
119
				if ($propValue === 'false')
120
				{
121
					$newType   = 'boolean';
122
					$propValue = false;
123
				}
124
				elseif ($propValue === 'true')
125
				{
126
					$newType   = 'boolean';
127
					$propValue = true;
128
				}
129
			}
130
131
			if ($oldType !== $newType)
132
			{
133
				// Test whether the PHP type roundtrip is lossless
134
				$tmp = $propValue;
135
				settype($tmp, $oldType);
136
				settype($tmp, $newType);
137
138
				if ($tmp !== $propValue)
139
				{
140
					throw new InvalidArgumentException("Cannot replace property '" . $propName . "' of type " . $oldType . ' with value of type ' . $newType);
141
				}
142
143
				// Finally, set the new value to the correct type
144
				settype($propValue, $oldType);
145
			}
146
		}
147
148
		$this->$propName = $propValue;
149
	}
150
151
	/**
152
	* Test whether a property is set
153
	*
154
	* @param  string $propName
155
	* @return bool
156
	*/
157
	public function __isset($propName)
158
	{
159
		$methodName = 'isset' . ucfirst($propName);
160
161
		if (method_exists($this, $methodName))
162
		{
163
			return $this->$methodName();
164
		}
165
166
		return isset($this->$propName);
167
	}
168
169
	/**
170
	* Unset a property, if the class supports it
171
	*
172
	* @param  string $propName
173
	* @return void
174
	*/
175
	public function __unset($propName)
176
	{
177
		$methodName = 'unset' . ucfirst($propName);
178
179
		if (method_exists($this, $methodName))
180
		{
181
			$this->$methodName();
182
183
			return;
184
		}
185
186
		if (!isset($this->$propName))
187
		{
188
			return;
189
		}
190
191
		if ($this->$propName instanceof Collection)
192
		{
193
			$this->$propName->clear();
194
195
			return;
196
		}
197
198
		throw new RuntimeException("Property '" . $propName . "' cannot be unset");
199
	}
200
}