2015-07-23 3 views
8

Symfony 2.7.2. Lehre ORM 2.4.7. MySQL 5.6.12. PHP 5.5.0.
Ich habe eine Entität mit benutzerdefinierten ID-Generator-Strategie. Es funktioniert einwandfrei.
In einigen Fällen muss ich diese Strategie mit einer "handgemachten" ID überschreiben. Es funktioniert, wenn die Haupteinheit ohne Assoziationen gelöscht wird. Aber es funktioniert nicht mit Assoziationen. Dieses Beispiel Fehler ausgelöst:Das Überschreiben der Standard-ID-Generierungsstrategie hat keine Auswirkungen auf die Verknüpfungen

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 , CONSTRAINT FK_354053617294869C FOREIGN KEY (article_id) REFERENCES article (id) ON DELETE CASCADE)

Hier ist, wie zu reproduzieren:

  1. Install and create a Symfony2 application.
  2. Bearbeiten Sie app/config/parameters.yml mit Ihren DB-Parametern.
  3. Verwenden Sie das Beispiel AppBundle Namespace, erstellen Sie Article und Tag Entities in src/AppBundle/Entity Verzeichnis.

    <?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; 
    } 
    
  4. generieren Getter und Setter für die oben genannten Entitäten:

    php app/console doctrine:generate:entities AppBundle 
    
  5. erstellen ArticleNumberGenerator Klasse in src/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'; 
        } 
    } 
    
  6. da erstellen Tabase: .

  7. Tabellen erstellen: php app/console doctrine:schema:create.
  8. Bearbeiten Sie das Beispiel AppBundle DefaultController befindet sich in src\AppBundle\Controller. Ersetzen Sie den Inhalt mit:

    <?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() . '.'); 
        } 
    } 
    
  9. Run-Server: php app/console server:run.

  10. Navigieren Sie zu http://127.0.0.1:8000/create-default. Aktualisieren 2 mal diese Meldung zu sehen:

    Created article id a003r0.

  11. Nun navigieren Sie zu http://127.0.0.1:8000/create-handmade/test. Das erwartete Ergebnis ist:

    Created article id test1.

    sondern werden Sie den Fehler:

    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 , CONSTRAINT FK_354053617294869C FOREIGN KEY (article_id) REFERENCES article (id) ON DELETE CASCADE)

    offenbar, weil Artikel mit id "a004r0" existiert nicht.

Wenn I-Kommentar aus $article->getTags()->add($tag); in createHandmadeAction, es funktioniert - das Ergebnis:

Created article id test.

und die Datenbank aktualisiert wird entsprechend:

id  | title 
-------+---------------- 
a001r0 | Test article 204 
a002r0 | Test article 12 
a003r0 | Test article 549 
test | Test article 723 

aber nicht, wenn eine Beziehung hinzugefügt. Aus einem Grund verwendet Doctrine nicht das handgemachte id für Assoziationen, stattdessen verwendet es die Standard-Id-Generator-Strategie.

Was ist hier falsch? Wie kann ich den Entity Manager davon überzeugen, meine handgefertigten Ids für Verbände zu verwenden?

Antwort

4

Ihr Problem hängt mit dem Aufruf von $em->persist($article); zusammen, bevor Sie die ClassMetadata ändern.

Auf UnitOfWork der neuen Einheit persistierenden erzeugt id mit ArticleNumberGenerator und speichert sie in entityIdentifiers Feld. Später verwendet ManyToManyPersister diesen Wert mit Hilfe von PersistentCollection beim Füllen der Relationstabellenzeile.

Beim Aufruf flushUoW berechnet die Änderungsmenge der Entität und speichert den tatsächlichen ID-Wert - das ist der Grund, warum Sie korrekte Daten erhalten, nachdem Sie keine Assoziation hinzugefügt haben. Es werden jedoch keine Daten von entityIdentifiers aktualisiert.

Um dies zu beheben, können Sie einfach persist hinter dem Ändern des ClassMetadata-Objekts verschieben. Aber der Weg sieht immer noch aus wie Hack. IMO ist der optimale Weg, den benutzerdefinierten Generator zu schreiben, der zugewiesene ID verwendet, wenn die eine bereitgestellt wird, oder neue zu generieren.

PS. Die andere Sache, die berücksichtigt werden sollte - Ihre Art der Generierung ID ist nicht sicher, es wird doppelte IDs auf der Hochlast produzieren.

UPD Missed dass UoW nicht idGeneratorType nicht verwendet (es von Metadaten Fabrik verwendet wird, die richtigen idGenerator Wert einzustellen), so sollten Sie idGenerator

/** 
* @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(); 

    $metadata = $em->getClassMetadata(get_class($article)); 
    $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE); 
    $metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator()); 
    $article->setId($handmade); 

    $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() . '.'); 
} 

Dies funktioniert wie erwartet richtige gesetzt.

+0

Das ist es. Getestet in einer Produktions-App mit allen Arten von Assoziationen - es funktioniert. Vielen Dank! – bostaf

Verwandte Themen