Skip to content

Instantly share code, notes, and snippets.

@Ocramius
Created March 3, 2015 16:57
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Ocramius/e2f020b0f56bc10a637a to your computer and use it in GitHub Desktop.
Save Ocramius/e2f020b0f56bc10a637a to your computer and use it in GitHub Desktop.
Simple array-based fixture loader for Doctrine ORM
return [
'Foo' = [
'Foo#1' => [
'bar' => 1,
'baz' => 2,
'tab' => '@Bar#1',
],
'Foo#2' => [
'bar' => 1,
'baz' => 2,
'tab' => '@Foo#1',
],
],
'Bar' = [
'Bar#1' => [
'a' => 1,
'b' => 2,
],
]
];
<?php
namespace FixtureLoader;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Instantiator\Instantiator;
class SimpleFixtureLoader
{
/**
* @var ObjectManager
*/
private $objectManager;
/**
* @var Instantiator
*/
private $instantiator;
public function __construct(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
$this->instantiator = new Instantiator();
}
public function loadFromArray(array $fixtureData)
{
$instancesByClass = [];
foreach ($fixtureData as $className => $classInstancesData) {
$instancesByClass[$className] = $this->loadClassInstances($className, $classInstancesData);
}
return call_user_func_array(
'array_merge',
array_merge(
[[]], // empty array, or `array_merge` won't work with < 1 elements to merge
array_map(
function ($classInstances) use ($instancesByClass) {
return $this->loadClassData($classInstances, $instancesByClass);
},
$instancesByClass
)
)
);
}
/**
* @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) {
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 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
) {
if (! isset($reflectionProperties[$field])) {
throw new \UnexpectedValueException(sprintf(
'Reflection property for field "%s" could not be found',
$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 null;
}
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