Last active
February 6, 2019 13:18
-
-
Save Wharenn/33da22b344d10415a80c15956c671a46 to your computer and use it in GitHub Desktop.
StringReferenceProcessor to resolve id references to others fixtures when not set in a relation
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 | |
declare(strict_types=1); | |
namespace App\Bundle\DependencyInjection; | |
use App\Fixtures\StringReferenceProcessor; | |
use Sonata\ArticleBundle\Model\FragmentInterface; | |
use Sonata\BlockBundle\Model\BlockInterface; | |
use Symfony\Component\Config\FileLocator; | |
use Symfony\Component\Config\Loader\LoaderInterface; | |
use Symfony\Component\DependencyInjection\ContainerBuilder; | |
use Symfony\Component\DependencyInjection\Loader; | |
use Symfony\Component\HttpKernel\DependencyInjection\Extension; | |
/** | |
* Class AppExtension. | |
* | |
* @author Romain Mouillard <romain.mouillard@ekino.com> | |
*/ | |
class AppExtension extends Extension | |
{ | |
public function load(array $configs, ContainerBuilder $container): void | |
{ | |
$configuration = new Configuration(); | |
$config = $this->processConfiguration($configuration, $configs); | |
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); | |
$loader->load('services.xml'); | |
$fixturesConfig = [ | |
FragmentInterface::class => ['fields'], | |
BlockInterface::class => ['settings'], | |
]; | |
$stringReference = $container->getDefinition(StringReferenceProcessor::class); | |
$stringReference->setArgument('$config', $fixturesConfig); | |
} | |
} |
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
App\Entity\Block: | |
block_test_{a, b, c}: | |
page: '@page_test' | |
parent: '@block_contaier_test' | |
name: Block Test <current()> | |
type: block.type | |
enabled: true | |
position: 1 | |
settings: | |
brand: | |
media: '#media_test->id' | |
url: http://www.github.com/ | |
menu: | |
elements: | |
- { media: '#media_test_<current()>->id' } |
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
<?xml version="1.0" ?> | |
<container xmlns="http://symfony.com/schema/dic/services" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | |
<services> | |
<defaults autowire="true" autoconfigure="true" public="false" /> | |
<service id="App\Fixtures\StringReferenceProcessor" class="App\Fixtures\StringReferenceProcessor"> | |
<tag name="fidry_alice_data_fixtures.processor" /> | |
<call method="setLogger"> | |
<argument type="service" id="logger" on-invalid="ignore"/> | |
</call> | |
</service> | |
</services> | |
</container> |
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 | |
declare(strict_types=1); | |
namespace App\Fixtures; | |
use Doctrine\ORM\EntityManagerInterface; | |
use Fidry\AliceDataFixtures\ProcessorInterface; | |
use Psr\Log\LoggerAwareTrait; | |
use Psr\Log\NullLogger; | |
use Symfony\Component\PropertyAccess\PropertyAccess; | |
use Symfony\Component\PropertyAccess\PropertyAccessor; | |
/** | |
* Class StringReferenceProcessor. | |
* | |
* Alice fixtures 3.x does not resolve anymore the id references to others fixtures when not set in a relation. | |
* This is an issue with array fields like sonata block settings or sonata fragment fields which are arrays and | |
* might contain such references to other fixtures (a media id for example). | |
* | |
* This processor resolves this new pattern in the fixtures yaml: | |
* | |
* #fixture_name->property | |
* | |
* Resolve is done after all entities are persisted a first time, so ids are available to replace the pattern. | |
* | |
* @author Romain Mouillard <romain.mouillard@ekino.com> | |
*/ | |
final class StringReferenceProcessor implements ProcessorInterface | |
{ | |
use LoggerAwareTrait; | |
/** | |
* @var EntityManagerInterface | |
*/ | |
private $entityManager; | |
/** | |
* @var array | |
*/ | |
private $fixtures = []; | |
/** | |
* @var PropertyAccessor | |
*/ | |
private $propertyAccessor; | |
/** | |
* Fields to resolve in classes. | |
* Keys are classes or interfaces, values an array of fields to search in to resolve. | |
* | |
* @var array | |
*/ | |
private $config; | |
public function __construct(EntityManagerInterface $entityManager, array $config = []) | |
{ | |
$this->propertyAccessor = PropertyAccess::createPropertyAccessor(); | |
$this->logger = new NullLogger(); | |
$this->entityManager = $entityManager; | |
$this->config = $config; | |
} | |
public function preProcess(string $id, $object): void | |
{ | |
// Keep track of all fixtures id and associated object | |
$this->fixtures[$id] = $object; | |
} | |
public function postProcess(string $id, $object): void | |
{ | |
// Retrieve configs to apply on this object | |
$configs = array_filter( | |
$this->config, | |
function ($key) use ($object) { | |
// We want config which applies to the object class | |
return $object instanceof $key; | |
}, | |
ARRAY_FILTER_USE_KEY | |
); | |
// Return early if this object is not handled by this processor | |
if (count($configs) === 0) { | |
return; | |
} | |
foreach ($configs as $config => $fields) { | |
$this->resolve($fields, $id, $object); | |
} | |
} | |
/** | |
* Resolve a set fields of the given object | |
* This method is recursively analysing the fields on the provided object | |
* and replaces any instances of "#fixture_name->property" by the actual value. | |
* | |
* @param array $fields an array of fields to resolve | |
* @param string $id the identifier of the object in the fixtures | |
* @param object $object the object that is the subject of resolve | |
*/ | |
private function resolve(array $fields, string $id, $object): void | |
{ | |
$needsPersist = false; | |
foreach ($fields as $field) { | |
$isFieldUpdated = false; | |
$settingsArray = $this->propertyAccessor->getValue($object, $field); | |
// No need to carry on if no array in field | |
if (!$settingsArray || !is_array($settingsArray)) { | |
continue; | |
} | |
// Walk into fields to find some "#fixture_name->property" pattern | |
// Field value is passed as reference, any change to it will be applied in the settings | |
// NeedsPersist value is passed as reference since it can be updated within the callable | |
array_walk_recursive($settingsArray, function (&$value) use ($id, &$isFieldUpdated): void { | |
$matches = []; | |
if (preg_match('/\#([a-zA-Z0-9_]*)->(.*)/', (string) $value, $matches)) { | |
$this->logger->debug(sprintf('Found pattern to replace in fixture "%s": %s', $id, $value)); | |
$fixtureName = $matches[1]; | |
$fixtureProperty = $matches[2]; | |
if (!isset($this->fixtures[$fixtureName])) { | |
throw new \UnexpectedValueException(sprintf('Could not find fixture with name "%s"', $fixtureName)); | |
} | |
$fixtureObject = $this->fixtures[$fixtureName]; | |
$resolvedValue = $this->propertyAccessor->getValue($fixtureObject, $fixtureProperty); | |
$this->logger->debug(sprintf('Resolved pattern "%s" to "%s"', $value, $resolvedValue)); | |
$value = $resolvedValue; | |
$isFieldUpdated = true; | |
} | |
}); | |
// Update field in the object if modified | |
if ($isFieldUpdated) { | |
$this->propertyAccessor->setValue($object, $field, $settingsArray); | |
$needsPersist = true; | |
} | |
} | |
// Persists changes on the entity | |
if ($needsPersist) { | |
$this->entityManager->persist($object); | |
$this->entityManager->flush(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment