Symfony 2.7.2. Doctrine ORM 2.4.7. MySQL 5.6.12. PHP 5.5.0.
Ho un'entità con strategia generatore ID personalizzato. Funziona perfettamente.
In alcune circostanze devo scavalcare questa strategia con un ID "fatto a mano". Funziona quando l'entità principale viene svuotata senza associazioni. Ma non funziona con le associazioni. Questo errore è gettato esempio:L'override della strategia di generazione dell'identificatore predefinito non ha alcun effetto sulle associazioni
An exception occurred while executing 'INSERT INTO articles_tags (article_id, tag_id) VALUES (?, ?)' with params ["a004r0", 4]:
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (
sf-test1
.articles_tags
, CONSTRAINTFK_354053617294869C
FOREIGN KEY (article_id
) REFERENCESarticle
(id
) ON DELETE CASCADE)
Ecco come riprodurre:
- Install and create a Symfony2 application.
- Modifica
app/config/parameters.yml
con i parametri del DB. Utilizzando lo spazio dei nomi
AppBundle
, creareArticle
eTag
nella directorysrc/AppBundle/Entity
.<?php // src/AppBundle/Entity/Article.php namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="article") */ class Article { /** * @ORM\Column(type="string") * @ORM\Id * @ORM\GeneratedValue(strategy="CUSTOM") * @ORM\CustomIdGenerator(class="AppBundle\Doctrine\ArticleNumberGenerator") */ protected $id; /** * @ORM\Column(type="string", length=255) */ protected $title; /** * @ORM\ManyToMany(targetEntity="Tag", inversedBy="articles" ,cascade={"all"}) * @ORM\JoinTable(name="articles_tags") **/ private $tags; public function setId($id) { $this->id = $id; } }
<?php // src/AppBundle/Entity/Tag.php namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; /** * @ORM\Entity * @ORM\Table(name="tag") */ class Tag { /** * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue */ protected $id; /** * @ORM\Column(type="string", length=255) */ protected $name; /** * @ORM\ManyToMany(targetEntity="Article", mappedBy="tags") **/ private $articles; }
Generare getter e setter per gli enti di cui sopra:
php app/console doctrine:generate:entities AppBundle
Creare
ArticleNumberGenerator
classesrc/AppBundle/Doctrine
:<?php // src/AppBundle/Doctrine/ArticleNumberGenerator.php namespace AppBundle\Doctrine; use Doctrine\ORM\Id\AbstractIdGenerator; use Doctrine\ORM\Query\ResultSetMapping; class ArticleNumberGenerator extends AbstractIdGenerator { public function generate(\Doctrine\ORM\EntityManager $em, $entity) { $rsm = new ResultSetMapping(); $rsm->addScalarResult('id', 'article', 'string'); $query = $em->createNativeQuery('select max(`id`) as id from `article` where `id` like :id_pattern', $rsm); $query->setParameter('id_pattern', 'a___r_'); $idMax = (int) substr($query->getSingleScalarResult(), 1, 3); $idMax++; return 'a' . str_pad($idMax, 3, '0', STR_PAD_LEFT) . 'r0'; } }
Creare da tabase: .
- Creare tabelle:
php app/console doctrine:schema:create
. Modificare l'esempio AppBundle
DefaultController
situato insrc\AppBundle\Controller
. Sostituire il contenuto con: server di<?php // src/AppBundle/Controller/DefaultController.php namespace AppBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use AppBundle\Entity\Article; use AppBundle\Entity\Tag; class DefaultController extends Controller { /** * @Route("/create-default") */ public function createDefaultAction() { $tag = new Tag(); $tag->setName('Tag ' . rand(1, 99)); $article = new Article(); $article->setTitle('Test article ' . rand(1, 999)); $article->getTags()->add($tag); $em = $this->getDoctrine()->getManager(); $em->getConnection()->beginTransaction(); $em->persist($article); try { $em->flush(); $em->getConnection()->commit(); } catch (\RuntimeException $e) { $em->getConnection()->rollBack(); throw $e; } return new Response('Created article id ' . $article->getId() . '.'); } /** * @Route("/create-handmade/{handmade}") */ public function createHandmadeAction($handmade) { $tag = new Tag(); $tag->setName('Tag ' . rand(1, 99)); $article = new Article(); $article->setTitle('Test article ' . rand(1, 999)); $article->getTags()->add($tag); $em = $this->getDoctrine()->getManager(); $em->getConnection()->beginTransaction(); $em->persist($article); $metadata = $em->getClassMetadata(get_class($article)); $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE); $article->setId($handmade); try { $em->flush(); $em->getConnection()->commit(); } catch (\RuntimeException $e) { $em->getConnection()->rollBack(); throw $e; } return new Response('Created article id ' . $article->getId() . '.'); } }
Run:
php app/console server:run
.Passare a http://127.0.0.1:8000/create-default. Refresh 2 volte per vedere questo messaggio:
Created article id a003r0.
Ora, passare a http://127.0.0.1:8000/create-handmade/test. Il risultato atteso è:
Created article id test1.
ma invece si otterrà l'errore:
An exception occurred while executing 'INSERT INTO articles_tags (article_id, tag_id) VALUES (?, ?)' with params ["a004r0", 4]:
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (
sf-test1
.articles_tags
, CONSTRAINTFK_354053617294869C
FOREIGN KEY (article_id
) REFERENCESarticle
(id
) ON DELETE CASCADE), ovviamente, perché l'articolo con
id
"a004r0" non esiste.
Se io commento-out $article->getTags()->add($tag);
in createHandmadeAction
, funziona - il risultato è:
Created article id test.
e il database viene aggiornato di conseguenza:
id | title
-------+----------------
a001r0 | Test article 204
a002r0 | Test article 12
a003r0 | Test article 549
test | Test article 723
ma non quando un rapporto è aggiunto. Per una ragione, Doctrine non usa il id
fatto a mano per le associazioni, ma utilizza la strategia di generatore Id predefinita.
Cosa c'è che non va qui? Come convincere il gestore di entità a utilizzare i miei ID fatti a mano per le associazioni?
Questo è tutto. Testato in un'app di produzione con tutti i tipi di associazioni: funziona. Grazie! – bostaf