Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony crud generated index view, where is not have references fields

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>

enter image description here

like image 865
Жасулан Бердибеков Avatar asked May 25 '15 09:05

Жасулан Бердибеков


1 Answers

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 ..

like image 108
skroczek Avatar answered Nov 04 '22 12:11

skroczek