Created
July 14, 2015 10:36
-
-
Save Ocramius/eb32c0d2ac600b9af5f0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace BLAHBLAH\GraphLoader; | |
use Doctrine\Common\Persistence\Mapping\ClassMetadata; | |
use Doctrine\Common\Persistence\ObjectManager; | |
use Doctrine\Instantiator\Instantiator; | |
use BLAHBLAH\Exception\UnexpectedValueException; | |
final class ArrayObjectGraphLoader implements ArrayObjectGraphLoaderInterface | |
{ | |
/** | |
* @var ObjectManager | |
*/ | |
private $objectManager; | |
/** | |
* @var Instantiator | |
*/ | |
private $instantiator; | |
public function __construct(ObjectManager $objectManager) | |
{ | |
$this->objectManager = $objectManager; | |
$this->instantiator = new Instantiator(); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function loadObjectGraphFromArray(array $fixtureData) | |
{ | |
$instancesByClass = []; | |
foreach ($fixtureData as $className => $classInstancesData) { | |
$instancesByClass[$className] = $this->loadClassInstances($className, $classInstancesData); | |
} | |
$graph = []; | |
foreach ($instancesByClass as $classInstances) { | |
$graph = array_merge($graph, array_values($this->loadClassData($classInstances, $instancesByClass))); | |
} | |
return $graph; | |
} | |
/** | |
* @param string $className | |
* @param array[] $instancesData | |
* | |
* @return object[][]|array[][] | |
*/ | |
private function loadClassInstances($className, array $instancesData) | |
{ | |
$metadata = $this->objectManager->getClassMetadata($className); | |
$className = $metadata->getName(); | |
$properties = $this->getReflectionPropertiesByName($metadata); | |
return array_map( | |
function (array $instanceData) use ($metadata, $className, $properties) { | |
return [ | |
'object' => $this->instantiator->instantiate($className), | |
'data' => $instanceData, | |
'className' => $className, | |
'metadata' => $metadata, | |
'properties' => $properties, | |
]; | |
}, | |
$instancesData | |
); | |
} | |
/** | |
* @param ClassMetadata $metadata | |
* | |
* @return \ReflectionProperty[] all accessible and indexed by property name | |
*/ | |
private function getReflectionPropertiesByName(ClassMetadata $metadata) | |
{ | |
$propertiesByName = []; | |
foreach ($this->getInheritanceClasses($metadata->getReflectionClass()) as $reflectionClass) { | |
foreach ($reflectionClass->getProperties() as $reflectionProperty) { | |
$reflectionProperty->setAccessible(true); | |
$propertiesByName[$reflectionProperty->getName()] = $reflectionProperty; | |
} | |
} | |
return $propertiesByName; | |
} | |
private function loadClassData(array $classInstances, array $instancesByClassName) | |
{ | |
return array_values(array_map( | |
function (array $instanceData) use ($instancesByClassName) { | |
$this->populateObjectDefaults($instanceData['object'], $instanceData['properties']); | |
return $this->populateInstance($instanceData, $instancesByClassName); | |
}, | |
$classInstances | |
)); | |
} | |
/** | |
* @param array $instanceData | |
* @param array $instancesByClassName | |
* | |
* @return object | |
*/ | |
private function populateInstance(array $instanceData, array $instancesByClassName) | |
{ | |
/* @var $instance object */ | |
$instance = $instanceData['object']; | |
/* @var $metadata \Doctrine\Common\Persistence\Mapping\ClassMetadata */ | |
$metadata = $instanceData['metadata']; | |
/* @var $properties \ReflectionProperty[] */ | |
$properties = $instanceData['properties']; | |
foreach ($instanceData['data'] as $field => $value) { | |
$this->populateObjectField($instance, $field, $value, $metadata, $properties, $instancesByClassName); | |
} | |
return $instance; | |
} | |
/** | |
* @param object $object | |
* @param \ReflectionProperty[] $reflectionProperties | |
*/ | |
private function populateObjectDefaults($object, array $reflectionProperties) | |
{ | |
foreach ($reflectionProperties as $name => $reflectionProperty) { | |
$reflectionProperty->setValue( | |
$object, | |
$reflectionProperty->getDeclaringClass()->getDefaultProperties()[$name] | |
); | |
} | |
} | |
/** | |
* @param object $object | |
* @param string $field | |
* @param mixed $value | |
* @param ClassMetadata $metadata | |
* @param \ReflectionProperty[] $reflectionProperties | |
* @param object[][] $instancesByClassName indexed by class and reference name | |
*/ | |
private function populateObjectField( | |
$object, | |
$field, | |
$value, | |
ClassMetadata $metadata, | |
array $reflectionProperties, | |
array $instancesByClassName | |
) { | |
// @todo MC-3850 FIX temp boolean value assigment | |
if (is_bool($value)) { | |
$value = $value ? 'Y' : 'N'; | |
} | |
if (! isset($reflectionProperties[$field])) { | |
throw new UnexpectedValueException(sprintf( | |
'Reflection property for field "%s#$%s" could not be found', | |
$metadata->getName(), | |
$field | |
)); | |
} | |
if ($metadata->hasField($field)) { | |
$reflectionProperties[$field]->setValue($object, $value); | |
return; | |
} | |
if (! $metadata->hasAssociation($field)) { | |
throw new UnexpectedValueException(sprintf( | |
'No field or association found for data entry "%s"', | |
$field | |
)); | |
} | |
if (! $metadata->isCollectionValuedAssociation($field)) { | |
$reflectionProperties[$field]->setValue($object, $this->resolveReference($value, $instancesByClassName)); | |
return; | |
} | |
if (! is_array($value)) { | |
throw new UnexpectedValueException(sprintf( | |
'Value for association "%s" was expected to be an array of references, %s given', | |
$field, | |
is_object($value) ? get_class($value) : gettype($value) | |
)); | |
} | |
$reflectionProperties[$field]->setValue( | |
$object, | |
array_map( | |
function ($reference) use ($instancesByClassName) { | |
return $this->resolveReference($reference, $instancesByClassName); | |
}, | |
$value | |
) | |
); | |
} | |
/** | |
* @param string $reference | |
* @param object[][] $instancesByClassName indexed by class and reference name | |
* | |
* @return object | |
* | |
* @throws UnexpectedValueException | |
*/ | |
private function resolveReference($reference, array $instancesByClassName) | |
{ | |
if (null === $reference) { | |
return; | |
} | |
list($className, $identifier) = explode('#', ltrim($reference, '@'), 2); | |
if (! isset($instancesByClassName[$className][$className . '#' . $identifier]['object'])) { | |
throw new UnexpectedValueException(sprintf('Reference "%s" could not be resolved', $reference)); | |
} | |
return $instancesByClassName[$className][$className . '#' . $identifier]['object']; | |
} | |
/** | |
* @param \ReflectionClass $class | |
* | |
* @return \ReflectionClass[] | |
*/ | |
private function getInheritanceClasses(\ReflectionClass $class) | |
{ | |
if (! $parent = $class->getParentClass()) { | |
return [$class]; | |
} | |
return array_merge([$class], $this->getInheritanceClasses($parent)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment