2015-11-30 17 views
5

Ho alcune entiti con relazioni e attributi comuni. Quindi, voglio semplificare il mio schema usando la mappatura ereditaria.Molti-a-uno, auto-referenziamento con mappatura dell'ereditarietà

Ho creato un BaseData mappedsuperclass e le mie altre entità lo espandono. Questa classe BaseData ha le relazioni comuni di cui ho bisogno in ogni entità.

Funziona con molti-a-uno relazione, come

/** 
* @ORM\MappedSuperclass 
*/ 
class BaseData 
{ 

    /** 
    * @ORM\ManyToOne(targetEntity="Service") 
    * @ORM\JoinColumn(name="service_id", referencedColumnName="id") 
    */ 
    protected $service; 

Ma diventa un po 'più complicato con autoreferenziale.

Per esempio, dal momento che voglio creare un riferimento genitore, ho provato:

/** 
* @ORM\MappedSuperclass 
*/ 
class BaseData 
{ 

    /** 
    * @ORM\ManyToOne(targetEntity="BaseData") 
    * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true) 
    */ 
    protected $parent; 

Ovviamente, portano a un TableNotFoundException quando cerco di interrogare questa entità: QLSTATE[42S02]: Base table or view not found: 1146 Table 'project.base_data' doesn't exist.

Quindi, ho provato AssociationOverrides, ma sembra che non consenta di modificare l'entità di destinazione.

Quindi, c'è un modo per creare qualche riferimento su una MappedSuperclass? E a proposito, ha senso?

Molte grazie in anticipo!

Aggiornamento

Ecco l'anwser:

ho definito il protected $parent e protected $children nel mio BaseData mappedSuperClass come previsto. Li ho annotati con altre informazioni di cui ho bisogno. ad esempio:

/** 
* @ORM\MappedSuperclass 
*/ 
class BaseData 
{ 

    /** 
    * @Datagrid\Column(field="parent.id", title="datagrid.parent_id", visible=false, safe=false) 
    * @Serializer\Expose 
    * @Serializer\Groups({"foo"}) 
    */ 
    protected $parent; 

    /** 
    * @Serializer\Expose 
    * @Serializer\Groups({"elastica"}) 
    */ 
    protected $children; 

Poi, aggiungo il rapporto ORM con l'evento loadClassMetadata.

/** 
* @param LoadClassMetadataEventArgs $eventArgs 
*/ 
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) 
{ 
    // the $metadata is all the mapping info for this class 
    $classMetadata = $eventArgs->getClassMetadata(); 
    $reflObj = new \ReflectionClass($classMetadata->name); 
    if($reflObj) { 
     if ($reflObj->isSubclassOf('CoreBundle\Entity\BaseData')) { 
      $fieldMapping = array(
       'targetEntity' => $classMetadata->name, 
       'fieldName'  => 'parent', 
       'inversedBy' => 'children', 
       'JoinColumn' => array(
        'name'     => 'parent_id', 
        'referencedColumnName' => 'id', 
        'nullable'    => true, 
        'onDelete'    => 'SET NULL', 
       ), 
      ); 

      $classMetadata->mapManyToOne($fieldMapping); 


      $fieldMapping = array(
       'fieldName'  => 'children', 
       'targetEntity' => $classMetadata->name, 
       'mappedBy'  => 'parent', 
      ); 
      $classMetadata->mapOneToMany($fieldMapping); 
     } 
    } 
} 

Registrare l'evento, e il gioco è fatto.

Ora, ogni classe che estende la superClass BaseData ottiene la relazione. Per esempio, php app/console doctrine:generate:entities MyBundle volontà genera il seguente codice all'interno dell'ente SubClass:

/** 
* Set parent 
* 
* @param \MyBundle\Entity\Subclass $parent 
* 
* @return Subclass 
*/ 
public function setParent(\MyBundle\Entity\Subclass $parent = null) 
{ 
    $this->parent = $parent; 

    return $this; 
} 

/** 
* Get parent 
* 
* @return \MyBundle\Entity\Subclass 
*/ 
public function getParent() 
{ 
    return $this->parent; 
} 

/** 
* Add child 
* 
* @param \MyBundle\Entity\Subclass $child 
* 
* @return Subclass 
*/ 
public function addChild(\MyBundle\Entity\Subclass $child) 
{ 
    $this->children[] = $child; 

    return $this; 
} 

/** 
* Remove child 
* 
* @param \MyBundle\Entity\Subclass $child 
*/ 
public function removeChild(\MyBundle\Entity\Subclass $child) 
{ 
    $this->children->removeElement($child); 
} 

/** 
* Get children 
* 
* @return \Doctrine\Common\Collections\Collection 
*/ 
public function getChildren() 
{ 
    return $this->children; 
} 
+0

Si potrebbe fare di sé solo come riferimento per le classi figlio come MappedSupperclass non è di per sé un'entità. Dalla documentazione: Una superclasse mappata non può essere un'entità, non è interrogabile e le relazioni persistenti definite da una superclasse mappata devono essere unidirezionali (con una sola parte proprietaria). Ciò significa che le associazioni One-To-Many non sono possibili su una superclasse mappata.Inoltre, le associazioni da molti a molti sono possibili solo se la superclasse mappata viene utilizzata solo in una sola entità al momento. –

+0

L'ho indovinato, ma il mio obiettivo era quello di rendere l'autoreferenzialità che funziona per ogni classe di figlio. Voglio dire, scrivendo 'targetEntity =" BaseData "', speravo che ogni classe figlia usasse il proprio nome. Quindi, suppongo, devo fare manualmente l'auto-referenza del genitore in ogni classe figlia, non è vero? E grazie per il vostro aiuto. – TiPi

+1

Nessun problema! Come @MappedSuperclass non è un'entità in sé, sì, immagino, si può fare solo nelle classi figlie. post scriptum IMHO, @MappedSuperclass è molto utile per tutti i tipi di dizionari - nell'ultimo progetto, ad esempio, avevo un BaseDictionaryDocument, con due campi id e sysName. E tutti i dizionari sembravano semplicemente "classe BlahBlahDocument estende BaseDictionaryDocument {}" e, naturalmente, un'annotazione con nome tabella/raccolta in alto. –

risposta

2

È possibile rimuovere la mappatura @ORM\ManyToOne(targetEntity="BaseData") e creare un listener di eventi per l'evento loadClassMetadata. (Non ho la prova il seguente codice, questo è solo un punto di partenza) Qualcosa di simile a questo:

class TestEvent 
{ 
    public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) 
    { 
     $classMetadata = $eventArgs->getClassMetadata(); 
     $class = $classMetadata->getName(); 
     $fieldMapping = array(
      'fieldName' => 'parent', 
      'targetEntity' => $class, 
     ); 
     $classMetadata->mapManyToOne($fieldMapping); 
    } 
} 

Una cosa importante da notare è che un ascoltatore sarà in ascolto per tutte le entità nella vostra applicazione.

Vedi Doctrine docs about events E How to register event listener in symfony2

+0

Ho provato la soluzione e sono riuscito a farlo funzionare come volevo. Aggiornerò la mia domanda per integrare l'anser. – TiPi

+0

Sono contento che mi abbia aiutato –