saeven / circlical-collection-hydrator
Doctrine + Laminas Collections 的 Hydrator 策略。
dev-main
2023-09-11 14:48 UTC
Requires
- php: ^7.4 || ~8.0.0 || ~8.1.0
- doctrine/doctrine-laminas-hydrator: *
- doctrine/inflector: *
Requires (Dev)
- laminas/laminas-coding-standard: 2.4.0
- phpspec/phpspec: 7.4.0
- phpstan/phpstan: 1.10.33
- squizlabs/php_codesniffer: 3.6.2
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(); } }
在这些小改动之后,您的集合将不再发出导致重复键的更新语句。