Symfony command doctrine:generate:crud generated controller< form and its views. But, index does not contain other table references fields "many-to-one".
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 - table head section:
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Sort</th>
<th>{{ 'views.index.actions'|trans({}, 'JordiLlonchCrudGeneratorBundle') }}</th>
</tr>
</thead>
This is the normal behaviour of the DoctrineCrudGenerator, because the generator uses only the Doctrine\ORM\Mapping\ClassMetadataInfo::$fieldMappings
array to generate the table, but the ParentAlbum-ManyToOne association is located in the Doctrine\ORM\Mapping\ClassMetadataInfo::$associationMappings
array. So it never will be recognized on crud generation.
To demonstrate a possible solution I use the the Comment entity of the symfony-demo application:
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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 <[email protected]>
* @author Javier Eguiluz <[email protected]>
*/
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 "normal" columns like id, content, authorEmail and publishedAt and one ManyToOne association to the Post entity. For this entity the following metadata are generated:
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 the normal fields are located in the fieldMappings array, while the association lives in the associationMappings array. Running the crud generator for the Comment entity produces the table only for the "normal" columns without the 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 behaviour, you simply have to "merge" the associationMappings array in the fieldMappings array on crud generation.
You can do that in the Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator
. For production, you have to override the
Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator
and the Sensio\Bundle\GeneratorBundle\Command\GenerateDoctrineCrudCommand
and make the changes in your own class. But just as a proof of concept I will do quick and really dirty hack, an add the
following changes to the DoctrineCrudGenerator directly:
/**
* Generates a CRUD controller.
*
* @author Fabien Potencier <[email protected]>
*/
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 the __toString to the Post entity, 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('Y-m-d 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 post association is recognized now. You can use this as an entry point if you want to start writing your own generator. But you have to investigate, how to handle ToMany associations and how to handle associations in forms and so on ..
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With