Newer
Older
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Stdlib\Hydrator;
use Traversable;
use Zend\Stdlib\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\Hydrator\Filter\FilterComposite;
use Zend\Stdlib\Hydrator\Filter\FilterProviderInterface;
use Zend\Stdlib\Hydrator\Filter\GetFilter;
use Zend\Stdlib\Hydrator\Filter\HasFilter;
use Zend\Stdlib\Hydrator\Filter\IsFilter;
use Zend\Stdlib\Hydrator\Filter\MethodMatchFilter;
use Zend\Stdlib\Hydrator\Filter\OptionalParametersFilter;
use Zend\Stdlib\Hydrator\NamingStrategy\NamingStrategyInterface;
use Zend\Stdlib\Hydrator\NamingStrategy\UnderscoreNamingStrategy;
class ClassMethods extends AbstractHydrator implements HydratorOptionsInterface
{
/**
* Holds the names of the methods used for hydration, indexed by class::property name,
* false if the hydration method is not callable/usable for hydration purposes
*
* @var string[]|bool[]
*/
Angie Byron
committed
private $hydrationMethodsCache = [];
/**
* A map of extraction methods to property name to be used during extraction, indexed
* by class name and method name
*
* @var string[][]
*/
Angie Byron
committed
private $extractionMethodsCache = [];
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
/**
* Flag defining whether array keys are underscore-separated (true) or camel case (false)
*
* @var bool
*/
protected $underscoreSeparatedKeys = true;
/**
* @var \Zend\Stdlib\Hydrator\Filter\FilterInterface
*/
private $callableMethodFilter;
/**
* Define if extract values will use camel case or name with underscore
* @param bool|array $underscoreSeparatedKeys
*/
public function __construct($underscoreSeparatedKeys = true)
{
parent::__construct();
$this->setUnderscoreSeparatedKeys($underscoreSeparatedKeys);
$this->callableMethodFilter = new OptionalParametersFilter();
$this->filterComposite->addFilter('is', new IsFilter());
$this->filterComposite->addFilter('has', new HasFilter());
$this->filterComposite->addFilter('get', new GetFilter());
$this->filterComposite->addFilter('parameter', new OptionalParametersFilter(), FilterComposite::CONDITION_AND);
}
/**
* @param array|Traversable $options
* @return ClassMethods
* @throws Exception\InvalidArgumentException
*/
public function setOptions($options)
{
if ($options instanceof Traversable) {
$options = ArrayUtils::iteratorToArray($options);
} elseif (!is_array($options)) {
throw new Exception\InvalidArgumentException(
'The options parameter must be an array or a Traversable'
);
}
if (isset($options['underscoreSeparatedKeys'])) {
$this->setUnderscoreSeparatedKeys($options['underscoreSeparatedKeys']);
}
return $this;
}
/**
* @param bool $underscoreSeparatedKeys
* @return ClassMethods
*/
public function setUnderscoreSeparatedKeys($underscoreSeparatedKeys)
{
$this->underscoreSeparatedKeys = (bool) $underscoreSeparatedKeys;
if ($this->underscoreSeparatedKeys) {
$this->setNamingStrategy(new UnderscoreNamingStrategy);
} elseif ($this->getNamingStrategy() instanceof UnderscoreNamingStrategy) {
$this->removeNamingStrategy();
}
return $this;
}
/**
* @return bool
*/
public function getUnderscoreSeparatedKeys()
{
return $this->underscoreSeparatedKeys;
}
/**
* Extract values from an object with class methods
*
* Extracts the getter/setter of the given $object.
*
* @param object $object
* @return array
* @throws Exception\BadMethodCallException for a non-object $object
*/
public function extract($object)
{
if (!is_object($object)) {
throw new Exception\BadMethodCallException(sprintf(
'%s expects the provided $object to be a PHP object)',
__METHOD__
));
}
$objectClass = get_class($object);
// reset the hydrator's hydrator's cache for this object, as the filter may be per-instance
if ($object instanceof FilterProviderInterface) {
$this->extractionMethodsCache[$objectClass] = null;
}
// pass 1 - finding out which properties can be extracted, with which methods (populate hydration cache)
if (! isset($this->extractionMethodsCache[$objectClass])) {
Angie Byron
committed
$this->extractionMethodsCache[$objectClass] = [];
$filter = $this->filterComposite;
$methods = get_class_methods($object);
if ($object instanceof FilterProviderInterface) {
$filter = new FilterComposite(
Angie Byron
committed
[$object->getFilter()],
[new MethodMatchFilter('getFilter')]
);
}
foreach ($methods as $method) {
$methodFqn = $objectClass . '::' . $method;
if (! ($filter->filter($methodFqn) && $this->callableMethodFilter->filter($methodFqn))) {
continue;
}
$attribute = $method;
if (strpos($method, 'get') === 0) {
$attribute = substr($method, 3);
if (!property_exists($object, $attribute)) {
$attribute = lcfirst($attribute);
}
}
$this->extractionMethodsCache[$objectClass][$method] = $attribute;
}
}
Angie Byron
committed
$values = [];
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// pass 2 - actually extract data
foreach ($this->extractionMethodsCache[$objectClass] as $methodName => $attributeName) {
$realAttributeName = $this->extractName($attributeName, $object);
$values[$realAttributeName] = $this->extractValue($realAttributeName, $object->$methodName(), $object);
}
return $values;
}
/**
* Hydrate an object by populating getter/setter methods
*
* Hydrates an object by getter/setter methods of the object.
*
* @param array $data
* @param object $object
* @return object
* @throws Exception\BadMethodCallException for a non-object $object
*/
public function hydrate(array $data, $object)
{
if (!is_object($object)) {
throw new Exception\BadMethodCallException(sprintf(
'%s expects the provided $object to be a PHP object)',
__METHOD__
));
}
$objectClass = get_class($object);
foreach ($data as $property => $value) {
$propertyFqn = $objectClass . '::$' . $property;
if (! isset($this->hydrationMethodsCache[$propertyFqn])) {
$setterName = 'set' . ucfirst($this->hydrateName($property, $data));
Angie Byron
committed
$this->hydrationMethodsCache[$propertyFqn] = is_callable([$object, $setterName])
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
? $setterName
: false;
}
if ($this->hydrationMethodsCache[$propertyFqn]) {
$object->{$this->hydrationMethodsCache[$propertyFqn]}($this->hydrateValue($property, $value, $data));
}
}
return $object;
}
/**
* {@inheritDoc}
*/
public function addFilter($name, $filter, $condition = FilterComposite::CONDITION_OR)
{
$this->resetCaches();
return parent::addFilter($name, $filter, $condition);
}
/**
* {@inheritDoc}
*/
public function removeFilter($name)
{
$this->resetCaches();
return parent::removeFilter($name);
}
/**
* {@inheritDoc}
*/
public function setNamingStrategy(NamingStrategyInterface $strategy)
{
$this->resetCaches();
return parent::setNamingStrategy($strategy);
}
/**
* {@inheritDoc}
*/
public function removeNamingStrategy()
{
$this->resetCaches();
return parent::removeNamingStrategy();
}
/**
* Reset all local hydration/extraction caches
*/
private function resetCaches()
{
Angie Byron
committed
$this->hydrationMethodsCache = $this->extractionMethodsCache = [];