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 namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; class Comment { private $id; private $post; private $content; private $authorEmail; private $publishedAt; public function __construct() { $this->publishedAt = new \DateTime(); } 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.