Skip to content

Instantly share code, notes, and snippets.

@Ocramius
Created July 14, 2015 10:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ocramius/eb32c0d2ac600b9af5f0 to your computer and use it in GitHub Desktop.
Save Ocramius/eb32c0d2ac600b9af5f0 to your computer and use it in GitHub Desktop.
<?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