Symfony crud generated an index where there are no link fields

Symfony Team Doctrine: Generate: crud generated controller <of the form and its views. But the index does not contain other many-to-one table link fields.

Entity Model:

<?php namespace Acme\Bundle\AdminBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Albums * * @ORM\Table(name="albums", indexes={@ORM\Index(name="IDX_F4E2474F3D8E604F", columns={"parent"})}) * @ORM\Entity */ class Albums { /** * @var integer * * @ORM\Column(name="id", type="integer", nullable=false) * @ORM\Id * @ORM\GeneratedValue(strategy="SEQUENCE") * @ORM\SequenceGenerator(sequenceName="albums_id_seq", allocationSize=1, initialValue=1) */ private $id; /** * @var string * * @ORM\Column(name="name", type="string", length=60, nullable=false) */ private $name; /** * @var integer * * @ORM\Column(name="sort", type="integer", nullable=false) */ private $sort; /** * @var \ParentAlbums * * @ORM\ManyToOne(targetEntity="ParentAlbums") * @ORM\JoinColumns({ * @ORM\JoinColumn(name="parent", referencedColumnName="id") * }) */ private $parent; /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set name * * @param string $name * @return Albums */ public function setName($name) { $this->name = $name; return $this; } /** * Get name * * @return string */ public function getName() { return $this->name; } /** * Set sort * * @param integer $sort * @return Albums */ public function setSort($sort) { $this->sort = $sort; return $this; } /** * Get sort * * @return integer */ public function getSort() { return $this->sort; } /** * Set parent * * @param \Acme\Bundle\AdminBundle\Entity\ParentAlbums $parent * @return Albums */ public function setParent(\Acme\Bundle\AdminBundle\Entity\ParentAlbums $parent = null) { $this->parent = $parent; return $this; } /** * Get parent * * @return \Acme\Bundle\AdminBundle\Entity\ParentAlbums */ public function getParent() { return $this->parent; } } 

Index.html.twig - section of the table header:

 <thead> <tr> <th>Id</th> <th>Name</th> <th>Sort</th> <th>{{ 'views.index.actions'|trans({}, 'JordiLlonchCrudGeneratorBundle') }}</th> </tr> </thead> 

enter image description here

+7
php crud symfony doctrine2 symfony-forms
source share
2 answers

This is the normal behavior of DoctrineCrudGenerator because the generator uses only the Doctrine\ORM\Mapping\ClassMetadataInfo::$fieldMappings to generate the table, but the ParentAlbum-ManyToOne association is in the Doctrine\ORM\Mapping\ClassMetadataInfo::$associationMappings . This way it will never be recognized in crud generation.

To demonstrate a possible solution, I use the Comment object of the symfony-demo application:

 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier < fabien@symfony.com > * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity * * Defines the properties of the Comment entity to represent the blog comments. * See http://symfony.com/doc/current/book/doctrine.html#creating-an-entity-class * * Tip: if you have an existing database, you can generate these entity class automatically. * See http://symfony.com/doc/current/cookbook/doctrine/reverse_engineering.html * * @author Ryan Weaver < weaverryan@gmail.com > * @author Javier Eguiluz < javier.eguiluz@gmail.com > */ class Comment { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") */ private $id; /** * @ORM\ManyToOne(targetEntity="Post", inversedBy="comments") * @ORM\JoinColumn(nullable=false) */ private $post; /** * @ORM\Column(type="text") * @Assert\NotBlank(message="Please don't leave your comment blank!") * @Assert\Length( * min = "5", * minMessage = "Comment is too short ({{ limit }} characters minimum)", * max = "10000", * maxMessage = "Comment is too long ({{ limit }} characters maximum)" * ) */ private $content; /** * @ORM\Column(type="string") * @Assert\Email() */ private $authorEmail; /** * @ORM\Column(type="datetime") * @Assert\DateTime() */ private $publishedAt; public function __construct() { $this->publishedAt = new \DateTime(); } /** * @Assert\True(message = "The content of this comment is considered spam.") */ public function isLegitComment() { $containsInvalidCharacters = false !== strpos($this->content, '@'); return !$containsInvalidCharacters; } public function getId() { return $this->id; } public function getContent() { return $this->content; } public function setContent($content) { $this->content = $content; } public function getAuthorEmail() { return $this->authorEmail; } public function setAuthorEmail($authorEmail) { $this->authorEmail = $authorEmail; } public function getPublishedAt() { return $this->publishedAt; } public function setPublishedAt($publishedAt) { $this->publishedAt = $publishedAt; } public function getPost() { return $this->post; } public function setPost(Post $post = null) { $this->post = $post; } } 

There are such โ€œnormalโ€ columns as id, content, authorEmail, and publishAt, and one ManyToOne association for the Post object. The following metadata is generated for this object:

 Doctrine\ORM\Mapping\ClassMetadata {#437 +name: "AppBundle\Entity\Comment" +namespace: "AppBundle\Entity" +rootEntityName: "AppBundle\Entity\Comment" +customGeneratorDefinition: null +customRepositoryClassName: null +isMappedSuperclass: false +isEmbeddedClass: false +parentClasses: [] +subClasses: [] +embeddedClasses: [] +namedQueries: [] +namedNativeQueries: [] +sqlResultSetMappings: [] +identifier: array:1 [ 0 => "id" ] +inheritanceType: 1 +generatorType: 4 +fieldMappings: array:4 [ "id" => array:9 [ "fieldName" => "id" "type" => "integer" "scale" => 0 "length" => null "unique" => false "nullable" => false "precision" => 0 "id" => true "columnName" => "id" ] "content" => array:8 [ "fieldName" => "content" "type" => "text" "scale" => 0 "length" => null "unique" => false "nullable" => false "precision" => 0 "columnName" => "content" ] "authorEmail" => array:8 [ "fieldName" => "authorEmail" "type" => "string" "scale" => 0 "length" => null "unique" => false "nullable" => false "precision" => 0 "columnName" => "authorEmail" ] "publishedAt" => array:8 [ "fieldName" => "publishedAt" "type" => "datetime" "scale" => 0 "length" => null "unique" => false "nullable" => false "precision" => 0 "columnName" => "publishedAt" ] ] +fieldNames: array:4 [ "id" => "id" "content" => "content" "authorEmail" => "authorEmail" "publishedAt" => "publishedAt" ] +columnNames: array:4 [ "id" => "id" "content" => "content" "authorEmail" => "authorEmail" "publishedAt" => "publishedAt" ] +discriminatorValue: null +discriminatorMap: [] +discriminatorColumn: null +table: array:1 [ "name" => "Comment" ] +lifecycleCallbacks: [] +entityListeners: [] +associationMappings: array:1 [ "post" => array:19 [ "fieldName" => "post" "joinColumns" => array:1 [ 0 => array:6 [ "name" => "post_id" "unique" => false "nullable" => false "onDelete" => null "columnDefinition" => null "referencedColumnName" => "id" ] ] "cascade" => [] "inversedBy" => "comments" "targetEntity" => "AppBundle\Entity\Post" "fetch" => 2 "type" => 2 "mappedBy" => null "isOwningSide" => true "sourceEntity" => "AppBundle\Entity\Comment" "isCascadeRemove" => false "isCascadePersist" => false "isCascadeRefresh" => false "isCascadeMerge" => false "isCascadeDetach" => false "sourceToTargetKeyColumns" => array:1 [ "post_id" => "id" ] "joinColumnFieldNames" => array:1 [ "post_id" => "post_id" ] "targetToSourceKeyColumns" => array:1 [ "id" => "post_id" ] "orphanRemoval" => false ] ] +isIdentifierComposite: false +containsForeignIdentifier: false +idGenerator: Doctrine\ORM\Id\IdentityGenerator {#439 -sequenceName: null } +sequenceGeneratorDefinition: null +tableGeneratorDefinition: null +changeTrackingPolicy: 1 +isVersioned: null +versionField: null +cache: null +reflClass: null +isReadOnly: false #namingStrategy: Doctrine\ORM\Mapping\DefaultNamingStrategy {#407} +reflFields: array:5 [ "id" => null "content" => null "authorEmail" => null "publishedAt" => null "post" => null ] -instantiator: Doctrine\Instantiator\Instantiator {#438} } 

You can see that regular fields are in the fieldMappings array, and the association lives in the associationMappings array. Running the crud generator for the Comment object only creates a table for "normal" columns without a post association:

 <thead> <tr> <th>Id</th> <th>Content</th> <th>Authoremail</th> <th>Publishedat</th> <th>Actions</th> </tr> </thead> 

Now, to change this behavior, you just need to โ€œmergeโ€ the associationMappings array in the fieldMappings array to generate crud. You can do this in Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator . For production, you need to override Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator and Sensio\Bundle\GeneratorBundle\Command\GenerateDoctrineCrudCommand and make changes to your class. But, as a proof of concept, I will make a quick and really dirty hack, I will add the following changes to DoctrineCrudGenerator directly:

 /** * Generates a CRUD controller. * * @author Fabien Potencier < fabien@symfony.com > */ class DoctrineCrudGenerator extends Generator { // ... /** * Generates the index.html.twig template in the final bundle. * * @param string $dir The path to the folder that hosts templates in the bundle */ protected function generateIndexView($dir) { $this->renderFile( 'crud/views/index.html.twig.twig', $dir . '/index.html.twig', array( 'bundle' => $this->bundle->getName(), 'entity' => $this->entity, 'identifier' => $this->metadata->identifier[0], // Use the function instead of the "raw" fieldMappings array // 'fields' => $this->metadata->fieldMappings, 'fields' => $this->processFieldMappings(), 'actions' => $this->actions, 'record_actions' => $this->getRecordActions(), 'route_prefix' => $this->routePrefix, 'route_name_prefix' => $this->routeNamePrefix, ) ); } // ... /** * Add the associations to the array * * @return array */ protected function processFieldMappings() { /** @var \Doctrine\ORM\Mapping\ClassMetadata $metadata */ $metadata = $this->metadata; $fields = $metadata->fieldMappings; $associationMappings = $metadata->associationMappings; foreach ($associationMappings as $k => $a) { // Add the field only if it is a ToOne association and if the targetEntity implements the __toString method if ($a['type'] & ClassMetadataInfo::TO_ONE && method_exists($a['targetEntity'], '__toString')) { $fields[$k] = array( "fieldName" => $a["fieldName"], "type" => "text", "scale" => 0, "length" => null, "unique" => false, "nullable" => false, "precision" => 0, "columnName" => $k ); } } return $fields; } } 

After the changes, and if you added __toString to the Post object, the generator produces the following code:

 <table class="records_list"> <thead> <tr> <th>Id</th> <th>Content</th> <th>Authoremail</th> <th>Publishedat</th> <th>Post</th> <th>Actions</th> </tr> </thead> <tbody> {% for entity in entities %} <tr> <td><a href="{{ path('comment_show', { 'id': entity.id }) }}">{{ entity.id }}</a></td> <td>{{ entity.content }}</td> <td>{{ entity.authorEmail }}</td> <td>{% if entity.publishedAt %}{{ entity.publishedAt|date('Ymd H:i:s') }}{% endif %}</td> <td>{{ entity.post }}</td> <td> <ul> <li> <a href="{{ path('comment_show', { 'id': entity.id }) }}">show</a> </li> <li> <a href="{{ path('comment_edit', { 'id': entity.id }) }}">edit</a> </li> </ul> </td> </tr> {% endfor %} </tbody> </table> 

You can see the message association is recognized now. You can use this as an entry point if you want to start writing your own generator. But you need to research how to process ToMany associations and how to process associations in forms, etc.

+7
source share

The crud command helps to quickly generate multiple files, but thatโ€™s not all. $ parent is a pointer to another object. The crud method cannot know what you want to display from this object. Imagine that ParentAlbums has the $ name property. you can display it very simply by changing the index.html.twig bit:

 <thead> <tr> <th>Id</th> <th>Name</th> <th>Parent</th> <th>Sort</th> <th>{{ 'views.index.actions'|trans({}, 'JordiLlonchCrudGeneratorBundle') }}</th> </tr> </thead> <tbody> {% for entity in entities %} <tr> <td>{{ entity.id }}</td> <td>{{ entity.name }}</td> <td>{{ entity.parent.name }}</td> <td>{{ entity.sort }}</td> <td> <ul> // actions here </ul> </td> </tr> {% endfor %} </tbody> 
-one
source share

All Articles