Skip to content
This repository has been archived by the owner on Jan 31, 2020. It is now read-only.

Commit

Permalink
Merge branch 'feature/zendframework/zendframework#6142-classmethod-hy…
Browse files Browse the repository at this point in the history
…drator-optimization' into develop

Close zendframework/zendframework#6142
  • Loading branch information
Ocramius committed May 10, 2014
9 parents d6c8dae + d8f58fd + c8aca84 + 4ac816f + 7f2fed2 + b528fc5 + fb851e2 + 802bf45 + 4c11ed1 commit dc20608
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 34 deletions.
154 changes: 121 additions & 33 deletions src/Hydrator/ClassMethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,30 @@
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[]
*/
private $hydrationMethodsCache = array();

/**
* A map of extraction methods to property name to be used during extraction, indexed
* by class name and method name
*
* @var string[][]
*/
private $extractionMethodsCache = array();

/**
* Flag defining whether array keys are underscore-separated (true) or camel case (false)
*
* @var bool
*/
protected $underscoreSeparatedKeys = true;
Expand Down Expand Up @@ -110,49 +128,60 @@ public function extract($object)
{
if (!is_object($object)) {
throw new Exception\BadMethodCallException(sprintf(
'%s expects the provided $object to be a PHP object)', __METHOD__
'%s expects the provided $object to be a PHP object)',
__METHOD__
));
}

$filter = null;
$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) {
$filter = new FilterComposite(
array($object->getFilter()),
array(new MethodMatchFilter("getFilter"))
);
} else {
$filter = $this->filterComposite;
$this->extractionMethodsCache[$objectClass] = null;
}

$attributes = array();
$methods = get_class_methods($object);
// pass 1 - finding out which properties can be extracted, with which methods (populate hydration cache)
if (! isset($this->extractionMethodsCache[$objectClass])) {
$this->extractionMethodsCache[$objectClass] = array();
$filter = $this->filterComposite;
$methods = get_class_methods($object);

foreach ($methods as $method) {
if (
!$filter->filter(
get_class($object) . '::' . $method
)
) {
continue;
if ($object instanceof FilterProviderInterface) {
$filter = new FilterComposite(
array($object->getFilter()),
array(new MethodMatchFilter("getFilter"))
);
}

if (!$this->callableMethodFilter->filter(get_class($object) . '::' . $method)) {
continue;
}
foreach ($methods as $method) {
$methodFqn = $objectClass . '::' . $method;

if (! ($filter->filter($methodFqn) && $this->callableMethodFilter->filter($methodFqn))) {
continue;
}

$attribute = $method;

$attribute = $method;
if (preg_match('/^get/', $method)) {
$attribute = substr($method, 3);
if (!property_exists($object, $attribute)) {
$attribute = lcfirst($attribute);
if (strpos($method, 'get') === 0) {
$attribute = substr($method, 3);
if (!property_exists($object, $attribute)) {
$attribute = lcfirst($attribute);
}
}

$this->extractionMethodsCache[$objectClass][$method] = $attribute;
}
}

$attribute = $this->extractName($attribute, $object);
$attributes[$attribute] = $this->extractValue($attribute, $object->$method(), $object);
$values = array();

// 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 $attributes;
return $values;
}

/**
Expand All @@ -169,18 +198,77 @@ 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__
'%s expects the provided $object to be a PHP object)',
__METHOD__
));
}

$objectClass = get_class($object);

foreach ($data as $property => $value) {
$method = 'set' . ucfirst($this->hydrateName($property, $data));
if (is_callable(array($object, $method))) {
$value = $this->hydrateValue($property, $value, $data);
$object->$method($value);
$propertyFqn = $objectClass . '::$' . $property;

if (! isset($this->hydrationMethodsCache[$propertyFqn])) {
$setterName = 'set' . ucfirst($this->hydrateName($property, $data));

$this->hydrationMethodsCache[$propertyFqn] = is_callable(array($object, $setterName))
? $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()
{
$this->hydrationMethodsCache = $this->extractionMethodsCache = array();
}
}
35 changes: 34 additions & 1 deletion test/Hydrator/ClassMethodsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
namespace ZendTest\Stdlib\Hydrator;

use Zend\Stdlib\Hydrator\ClassMethods;
use ZendTest\Stdlib\TestAsset\ClassMethodsCamelCaseMissing;
use ZendTest\Stdlib\TestAsset\ClassMethodsOptionalParameters;
use ZendTest\Stdlib\TestAsset\ClassMethodsCamelCase;
use ZendTest\Stdlib\TestAsset\ArraySerializable;

/**
* Unit tests for {@see \Zend\Stdlib\Hydrator\ClassMethods}
*
* @covers \Zend\Stdlib\Hydrator\ClassMethods
* @group Zend_Stdlib
*/
class ClassMethodsTest extends \PHPUnit_Framework_TestCase
{
Expand All @@ -40,4 +42,35 @@ public function testCanExtractFromMethodsWithOptionalParameters()
{
$this->assertSame(array('foo' => 'bar'), $this->hydrator->extract(new ClassMethodsOptionalParameters()));
}

/**
* Verifies that the hydrator can act on different instance types
*/
public function testCanHydratedPromiscuousInstances()
{
/* @var $classMethodsCamelCase ClassMethodsCamelCase */
$classMethodsCamelCase = $this->hydrator->hydrate(
array('fooBar' => 'baz-tab'),
new ClassMethodsCamelCase()
);
/* @var $classMethodsCamelCaseMissing ClassMethodsCamelCaseMissing */
$classMethodsCamelCaseMissing = $this->hydrator->hydrate(
array('fooBar' => 'baz-tab'),
new ClassMethodsCamelCaseMissing()
);
/* @var $arraySerializable ArraySerializable */
$arraySerializable = $this->hydrator->hydrate(array('fooBar' => 'baz-tab'), new ArraySerializable());

$this->assertSame('baz-tab', $classMethodsCamelCase->getFooBar());
$this->assertSame('baz-tab', $classMethodsCamelCaseMissing->getFooBar());
$this->assertSame(
array(
"foo" => "bar",
"bar" => "foo",
"blubb" => "baz",
"quo" => "blubb"
),
$arraySerializable->getArrayCopy()
);
}
}

0 comments on commit dc20608

Please sign in to comment.