saeven/circlical-collection-hydrator

Doctrine + Laminas Collections 的 Hydrator 策略。

dev-main 2023-09-11 14:48 UTC

This package is auto-updated.

Last update: 2024-09-11 16:56:08 UTC


README

这是一个构建在现有 hydrator 之上的集合 hydrator,它可以防止当 Laminas Forms 和 Collections 涉及时,Doctrine 发生重复键更新。这个问题可以在这里找到证据

使用方法

安装非常简单,在通过 composer 安装该包之后,在您的表单 Factory 中,您将为您的集合指定 hydrator 策略为 "CollectionDiffStrategy"。

    <?php
    
    declare(strict_types=1);
    
    namespace HydrationTest\Factory\Form;
    
    use Doctrine\Laminas\Hydrator\DoctrineObject as DoctrineHydrator;
    use Doctrine\ORM\EntityManager;
    use HydrationTest\Form\Hydrator\CollectionDiffStrategy;
    use HydrationTest\Form\IngredientAmountFieldset;
    use HydrationTest\Form\RecipeForm;
    use Laminas\Form\FormElementManager;
    use Laminas\ServiceManager\Factory\FactoryInterface;
    use Psr\Container\ContainerInterface;
    
    class RecipeFormFactory implements FactoryInterface
    {
        public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null)
        {
            $hydrator = new DoctrineHydrator($container->get(EntityManager::class), false);
            $hydrator->addStrategy('ingredient_amounts', new CollectionDiffStrategy(true));
    
            return (new RecipeForm(
                $container->get(FormElementManager::class)->get(IngredientAmountFieldset::class, $options ?? []),
                $options ?? []
            ))
                ->setHydrator($hydrator)
                ->setObject($options['recipe']);
        }
    }

然后,在您的 fieldset Factory 中,您将应用 CollectionComparatorHydrator 来返回新类型的干净对象。

    <?php
    
    declare(strict_types=1);
    
    namespace HydrationTest\Factory\Form;
    
    use Doctrine\ORM\EntityManager;
    use HydrationTest\Entity\Ingredient;
    use HydrationTest\Entity\IngredientAmount;
    use HydrationTest\Form\Hydrator\CollectionComparatorHydrator;
    use HydrationTest\Form\IngredientAmountFieldset;
    use Laminas\ServiceManager\Factory\FactoryInterface;
    use Psr\Container\ContainerInterface;
    
    class IngredientAmountFieldsetFactory implements FactoryInterface
    {
        public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null)
        {
            $recipe = $options['recipe'];
    
            /** @var  EntityManager $entityManager */
            $entityManager = $container->get(EntityManager::class);
            $collectionHydrator = new CollectionComparatorHydrator($entityManager, function (array $data, object $object) use ($entityManager, $recipe) {
                $ingredient = $entityManager->getRepository(Ingredient::class)->findOneBy(['id' => $data['ingredient']]);
                return new IngredientAmount($recipe, $ingredient, $data['tablespoons'] ?? 0);
            }, false);
    
    
            return (new IngredientAmountFieldset($entityManager, 'ingredient_amounts'))
                ->setHydrator($collectionHydrator)
                ->setObject(new IngredientAmount($options['recipe'], null, null));
        }
    }

最后一步,我们必须在 fieldset 通过的对象上实现必要的比较器;请参阅 CollectionDiffInterface。

    <?php
    
    declare(strict_types=1);
    
    namespace HydrationTest\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use HydrationTest\Model\CollectionDiffInterface;
    
    /**
     * @ORM\Entity
     * @ORM\Table(name="recipes_ingredients");
     */
    class IngredientAmount implements CollectionDiffInterface
    {
        /**
         * @ORM\Id
         * @ORM\ManyToOne(targetEntity="Recipe", inversedBy="ingredient_amounts")
         * @ORM\JoinColumn(name="recipe_id", referencedColumnName="id", onDelete="cascade")
         *
         * @var Recipe
         */
        private $recipe;
    
        /**
         * @ORM\Id
         * @ORM\ManyToOne(targetEntity="Ingredient")
         * @ORM\JoinColumn(name="ingredient_id", referencedColumnName="id", onDelete="cascade")
         *
         * @var ?Ingredient
         */
        private $ingredient;
    
        /**
         * @ORM\Column(type="integer", nullable=false, options={"default":0, "unsigned":true})
         *
         * @var ?int
         */
        private $tablespoons;
    
        public function __construct(Recipe $recipe, ?Ingredient $ingredient, ?int $tablespoons)
        {
            $this->recipe = $recipe;
            $this->ingredient = $ingredient;
            $this->tablespoons = $tablespoons;
        }
    
        public function getIngredient(): Ingredient
        {
            return $this->ingredient;
        }
    
        public function setTablespoons(int $tablespoons): void
        {
            $this->tablespoons = $tablespoons;
        }
    
        public function getTablespoons(): int
        {
            return $this->tablespoons;
        }
    
        public function getDiffIdentifier(): string
        {
            return $this->recipe->getId() . '-' . $this->ingredient->getId();
        }
    
        public function copyValuesFrom(object $object): void
        {
            if (!$object instanceof IngredientAmount) {
                return;
            }
    
            $this->tablespoons = $object->getTablespoons();
        }
    }

在这些小改动之后,您的集合将不再发出导致重复键的更新语句。