sonata-project / SonataAdminBundle

The missing Symfony Admin Generator
https://docs.sonata-project.org/projects/SonataAdminBundle
MIT License
2.11k stars 1.26k forks source link

Sonata\Form\Type\CollectionType doesn't work properly with deep relations entities #7589

Closed magiceupho closed 2 years ago

magiceupho commented 2 years ago

Environment

Sonata packages

show

``` $ composer show --latest 'sonata-project/*' sonata-project/admin-bundle 4.2.1 4.2.1 The missing Symfony Admin Generator sonata-project/block-bundle 4.8.0 4.8.0 Symfony SonataBlockBundle sonata-project/cache 2.2.0 2.2.0 Cache library sonata-project/doctrine-extensions 1.14.0 1.14.0 Doctrine2 behavioral extensions sonata-project/doctrine-orm-admin-bundle 4.1.0 4.1.0 Integrate Doctrine ORM into the SonataAdminBundle sonata-project/exporter 2.8.0 2.8.0 Lightweight Exporter library sonata-project/form-extensions 1.12.1 1.12.1 Symfony form extensions sonata-project/twig-extensions 1.9.0 1.9.0 Sonata twig extensions ```

Symfony packages

show

``` $ composer show --latest 'symfony/*' symfony/apache-pack v1.0.1 v1.0.1 A pack for Apache support in Symfony symfony/asset v5.3.4 v5.3.4 Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files symfony/cache v5.3.10 v5.3.10 Provides an extended PSR-6, PSR-16 (and tags) implementation symfony/cache-contracts v2.4.0 v2.4.0 Generic abstractions related to caching symfony/config v5.3.10 v5.3.10 Helps you find, load, combine, autofill and validate configuration values of any kind symfony/console v5.3.10 v5.3.10 Eases the creation of beautiful and testable command line interfaces symfony/debug-bundle v5.3.4 v5.3.4 Provides a tight integration of the Symfony Debug component into the Symfony full-stack framework symfony/dependency-injection v5.3.10 v5.3.10 Allows you to standardize and centralize the way objects are constructed in your application symfony/deprecation-contracts v2.4.0 v2.4.0 A generic function and convention to trigger deprecation notices symfony/doctrine-bridge v5.3.8 v5.3.8 Provides integration for Doctrine with various Symfony components symfony/dotenv v5.3.10 v5.3.10 Registers environment variables from a .env file symfony/error-handler v5.3.7 v5.3.7 Provides tools to manage errors and ease debugging PHP code symfony/event-dispatcher v5.3.7 v5.3.7 Provides tools that allow your application components to communicate with each other by dispatching events and listening to them symfony/event-dispatcher-contracts v2.4.0 v2.4.0 Generic abstractions related to dispatching event symfony/expression-language v5.3.7 v5.3.7 Provides an engine that can compile and evaluate expressions symfony/filesystem v5.3.4 v5.3.4 Provides basic utilities for the filesystem symfony/finder v5.3.7 v5.3.7 Finds files and directories via an intuitive fluent interface symfony/flex v1.17.2 v1.17.2 Composer plugin for Symfony symfony/form v5.3.10 v5.3.10 Allows to easily create, process and reuse HTML forms symfony/framework-bundle v5.3.10 v5.3.10 Provides a tight integration between Symfony components and the Symfony full-stack framework symfony/http-client-contracts v2.4.0 v2.4.0 Generic abstractions related to HTTP clients symfony/http-foundation v5.3.10 v5.3.10 Defines an object-oriented layer for the HTTP specification symfony/http-kernel v5.3.10 v5.3.10 Provides a structured process for converting a Request into a Response symfony/lock v5.3.10 v5.3.10 Creates and manages locks, a mechanism to provide exclusive access to a shared resource symfony/mailer v5.3.9 v5.3.9 Helps sending emails symfony/maker-bundle v1.34.1 v1.34.1 Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code. symfony/mime v5.3.8 v5.3.8 Allows manipulating MIME messages symfony/options-resolver v5.3.7 v5.3.7 Provides an improved replacement for the array_replace PHP function symfony/password-hasher v5.3.8 v5.3.8 Provides password hashing utilities symfony/polyfill-intl-grapheme v1.23.1 v1.23.1 Symfony polyfill for intl's grapheme_* functions symfony/polyfill-intl-icu v1.23.0 v1.23.0 Symfony polyfill for intl's ICU-related data and classes symfony/polyfill-intl-idn v1.23.0 v1.23.0 Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions symfony/polyfill-intl-normalizer v1.23.0 v1.23.0 Symfony polyfill for intl's Normalizer class and related functions symfony/polyfill-mbstring v1.23.1 v1.23.1 Symfony polyfill for the Mbstring extension symfony/polyfill-php73 v1.23.0 v1.23.0 Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions symfony/polyfill-php80 v1.23.1 v1.23.1 Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions symfony/polyfill-php81 v1.23.0 v1.23.0 Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions symfony/property-access v5.3.8 v5.3.8 Provides functions to read and write from/to an object or array using a simple string notation symfony/property-info v5.3.8 v5.3.8 Extracts information about PHP class' properties using metadata of popular sources symfony/proxy-manager-bridge v5.3.4 v5.3.4 Provides integration for ProxyManager with various Symfony components symfony/rate-limiter v5.3.4 v5.3.4 Provides a Token Bucket implementation to rate limit input and output in your application symfony/routing v5.3.7 v5.3.7 Maps an HTTP request to a set of configuration variables symfony/runtime v5.3.10 v5.3.10 Enables decoupling PHP applications from global state symfony/security-acl v3.2.0 v3.2.0 Symfony Security Component - ACL (Access Control List) symfony/security-bundle v5.3.8 v5.3.8 Provides a tight integration of the Security component into the Symfony full-stack framework symfony/security-core v5.3.10 v5.3.10 Symfony Security Component - Core Library symfony/security-csrf v5.3.4 v5.3.4 Symfony Security Component - CSRF Library symfony/security-guard v5.3.7 v5.3.7 Symfony Security Component - Guard symfony/security-http v5.3.10 v5.3.10 Symfony Security Component - HTTP Integration symfony/serializer v5.3.10 v5.3.10 Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON. symfony/service-contracts v2.4.0 v2.4.0 Generic abstractions related to writing services symfony/stopwatch v5.3.4 v5.3.4 Provides a way to profile code symfony/string v5.3.10 v5.3.10 Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way symfony/translation v5.3.10 v5.3.10 Provides tools to internationalize your application symfony/translation-contracts v2.4.0 v2.4.0 Generic abstractions related to translation symfony/twig-bridge v5.3.7 v5.3.7 Provides integration for Twig with various Symfony components symfony/twig-bundle v5.3.10 v5.3.10 Provides a tight integration of Twig into the Symfony full-stack framework symfony/validator v5.3.10 v5.3.10 Provides tools to validate values symfony/var-dumper v5.3.10 v5.3.10 Provides mechanisms for walking through any arbitrary PHP variable symfony/var-exporter v5.3.8 v5.3.8 Allows exporting any serializable PHP data structure to plain PHP code symfony/web-profiler-bundle v5.3.8 v5.3.8 Provides a development tool that gives detailed information about the execution of any request symfony/webpack-encore-bundle v1.12.0 v1.12.0 Integration with your Symfony app & Webpack Encore! symfony/yaml v5.3.6 v5.3.6 Loads and dumps YAML files ```

PHP version

$ php -v
PHP 8.0.6 (cli) (built: May  4 2021 23:31:41) ( NTS Visual C++ 2019 x64 )
Copyright (c) The PHP Group
Zend Engine v4.0.6, Copyright (c) Zend Technologies
    with Xdebug v3.0.4, Copyright (c) 2002-2021, by Derick Rethans

Subject

Hi,

I make a disc (CD and vinyl) database site and the mysql database is a little bit complex: The entity Disc has among others a OneToMany relation with the entity Track. The entity Track has among others two OneToMany relations with TrackOrchestra and TrackPerson entities, which are two intermediary entities. The TrackOrchestra entity has two ManyToOne relations with the Orchestra entity and the Conductor entity (which is optional). The TrackPerson entity has two ManyToOne relations with the Person entity and the Instrument entity (which is optional).

Here is the configureFormFields function of the TrackAdmin:

protected function configureFormFields(FormMapper $form): void
    {
        $form
            ->add('disc', ModelListType::class)
            ->add('trackNb')
            ->add('piece', ModelListType::class)
            ->add('orchestras', CollectionType::class, [
                'required' => false,
                'by_reference' => false,
            ], [
                'edit' => 'inline',
                'inline' => 'table',
            ])
            ->add('soloists', CollectionType::class, [
                'required' => false,
            ], [
                'edit' => 'inline',
                'inline' => 'table',
            ])
        ;
    }

Minimal repository with the bug

Steps to reproduce

Expected results

I would like TrackAdmin to be able to fill the Collection of Orchestra with (or not) the Conductor and the smae for the Person/Instrument and be able to add more rows.

Actual results

The Sonata\Form\Type\CollectionType fields doesn't appear and the add button either, I can't do anything.

Capture d’écran 2021-11-05 092123

Am I doing something wrong ? Can you help me please ?

Regards

EliaCools commented 2 years ago

I know that you need to add the "allow_add" => true to display the add button. But other than that i'm having issues with the collectiontype as well.

jordisala1991 commented 2 years ago

Did you tried the suggestion? @magiceupho

magiceupho commented 2 years ago

Hi @EliaCools,

This option doesn't exist in "Sonata\Form\Type\CollectionType", it is only for the "Sonata\AdminBundle\Form\Type\CollectionType". Capture d’écran 2021-11-28 170930

Regards

jordisala1991 commented 2 years ago

Why don't you use the Sonata Collection Type? Is this behavior expected @VincentLanglet ?

magiceupho commented 2 years ago

@jordisala1991 sorry, I don't understand... This is the Sonata Collection Type....

jordisala1991 commented 2 years ago

Sorry, I mean the Sonata Admin Collection Type

magiceupho commented 2 years ago

Do you mean Sonata\AdminBundle\Form\Type\CollectionType ? Could you explain to me why use Sonata\AdminBundle\Form\Type\CollectionType rather than the Sonata\Form\Type\CollectionType ?

jordisala1991 commented 2 years ago

Reading docs it should work with both options:

https://docs.sonata-project.org/projects/SonataAdminBundle/en/4.x/reference/form_types/#sonata-form-type-collectiontype https://docs.sonata-project.org/projects/SonataAdminBundle/en/4.x/reference/form_types/#sonata-adminbundle-form-type-collectiontype

VincentLanglet commented 2 years ago

Why don't you use the Sonata Collection Type? Is this behavior expected @VincentLanglet ?

I don't see why this would be supposed to work.

Track has a OtoM with TrackOrchestra, so

->add('trackOrchestras')

could work.

But here, the code is ->add('orchestras'). How the form is supposed to create both a TrackOrchestra and an Orchestra here.

Is it even possible with the Symfony Collection Type in a classical form ? If not, I don't see why it would be possible with our Form type.

magiceupho commented 2 years ago

The OneToMany property is 'orchestras' in the Track class.

Here is the Track class:

/**
 * @ORM\Entity(repositoryClass=TrackRepository::class)
 */
class Track
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private int $id;

    /**
     * @ORM\Column(type="string", length=20)
     */
    private string $trackNb;

    /**
     * @ORM\Column(type="json", nullable=false)
     */
    private array $durations;

    /**
     * @ORM\ManyToOne(targetEntity=Disc::class, inversedBy="tracks")
     * @ORM\JoinColumn(name="disc_id", referencedColumnName="id", nullable=false)
     */
    private Disc $disc;

    /**
     * @ORM\ManyToOne(targetEntity=Piece::class)
     * @ORM\JoinColumn(name="piece_id", referencedColumnName="id", nullable=false)
     */
    private Piece $piece;

    /**
     * @ORM\OneToMany(targetEntity=TrackOrchestra::class, mappedBy="track", cascade={"remove"})
     */
    private Collection|TrackOrchestra $orchestras;

    /**
     * @ORM\OneToMany(targetEntity=TrackPerson::class, mappedBy="track", cascade={"remove"})
     */
    private Collection|TrackPerson $soloists;

    public function __construct()
    {
        $this->orchestras = new ArrayCollection();
        $this->soloists = new ArrayCollection();
    }

    // getters & setters

}

TrackOrchestra class:

/**
 * @ORM\Entity(repositoryClass=TrackOrchestraRepository::class)
 * @ORM\Table(name="track_orchestra")
 */
class TrackOrchestra
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private int $id;

    /**
     * @ORM\ManyToOne(targetEntity=Track::class, inversedBy="orchestras")
     * @ORM\JoinColumn(nullable=false)
     */
    private Track $track;

    /**
     * @ORM\ManyToOne(targetEntity=Orchestra::class)
     * @ORM\JoinColumn(nullable=false)
     */
    private Orchestra $orchestra;

    /**
     * @ORM\ManyToOne(targetEntity=Person::class)
     * @ORM\JoinColumn(nullable=true)
     */
    private Person|null $conductor;

    // getters & setters

}

TrackPerson class:

/**
 * @ORM\Entity(repositoryClass=TrackPersonRepository::class)
 * @ORM\Table(name="track_person")
 */
class TrackPerson
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private int $id;

    /**
     * @ORM\ManyToOne(targetEntity=Track::class, inversedBy="soloists")
     * @ORM\JoinColumn(nullable=false)
     */
    private Track $track;

    /**
     * @ORM\ManyToOne(targetEntity=Person::class)
     * @ORM\JoinColumn(nullable=false)
     */
    private Person $person;

    /**
     * @ORM\ManyToOne(targetEntity=Instrument::class)
     * @ORM\JoinColumn(nullable=true)
     */
    private Instrument|null $instrument;

    // getters & setters

}

Orchestra class:

/**
 * @ORM\Entity(repositoryClass=OrchestraRepository::class)
 */
class Orchestra
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private int $id;

    /**
     * @ORM\Column(type="string", length=80)
     */
    private string $name;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private string $slug;

    // getters & setters

}

Person class:

/**
 * @ORM\Entity(repositoryClass=PersonRepository::class)
 */
class Person
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private int $id;

    /**
     * @ORM\Column(type="string", length=30)
     */
    private string $firstName;

    /**
     * @ORM\Column(type="string", length=30)
     */
    private string $lastName;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private string $name;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private string $slug;

    // getters & setters

}

Instrument class:

/**
 * @ORM\Entity(repositoryClass=InstrumentRepository::class)
 */
class Instrument
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private int $id;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private string $name;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private string $slug;

    // getters & setters

}

The form know how to handle this, it display data when there is some in database:

Capture d’écran 2021-11-28 215645

As you can see, the data from the database is displayed, but without the possibility to add more rows. However, the Sonata\AdminBundle\Form\Type\CollectionType work great in more simple and less deep situation. That's why I've open this issue.

Have you any idea please ?

Regards

VincentLanglet commented 2 years ago

So you tried both Sonata\Form\Type\CollectionType and Sonata\AdminBundle\Form\Type\CollectionType and none of them work ?

This seems weird. I use Sonata\AdminBundle\Form\Type\CollectionType all the time and it works fine. The view is supposed to be https://github.com/sonata-project/SonataAdminBundle/blob/4.x/src/Resources/views/Form/form_admin_fields.html.twig#L408-L428

And I see no reason for not displaying an add button with this code as long as allow_add is set to true.

Some dump might be needed to see where is this code. Or a providing a reproducer.

magiceupho commented 2 years ago

I have open this issue to talk about the problem I found with the Sonata\Form\Type\CollectionType, which is a powerfull FormType, not the Sonata\AdminBundle\Form\Type\CollectionType.

Despite this, I tried to use the Sonata\AdminBundle\Form\Type\CollectionType and this work, but this doesn't fit my needs because I need to use the Sonata\AdminBundle\Form\Type\ModelListType in the TrackOrchestra's fields and I don't find how to use the Sonata\AdminBundle\Form\Type\ModelListType in a Symfony Form Type.

Once again, there is no allow_add option in the Sonata\Form\Type\CollectionType.

Which dump do you need to help with this issue ? How can I provide you a reproducer please ?

Regards

magiceupho commented 2 years ago

Hi,

I have solved the issue ! It was a permission problem... I forgot to set ROLE_ADMIN_TRACK_ORCHESTRA_ALL and ROLE_ADMIN_TRACK_PERSON_ALL in the role_hierarchy of the config/security.yaml.

Thank you for your responses, for your time and sorry for inconvenience. Thank you for this great admin generator !

Regards

VincentLanglet commented 2 years ago

WDYT about adding a reminder to the doc @magiceupho. This way the next user won't forget to give the ADD rights.

Do you mind making a PR to improve https://docs.sonata-project.org/projects/SonataAdminBundle/en/4.x/reference/form_types/#sonata-form-type-collectiontype https://docs.sonata-project.org/projects/SonataAdminBundle/en/4.x/reference/form_types/#sonata-adminbundle-form-type-collectiontype ?