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

Related object boolean property not shown correctly #6795

Closed Martin1982 closed 3 years ago

Martin1982 commented 3 years ago

Environment

Sonata packages

show

``` $ composer show --latest 'sonata-project/*' sonata-project/admin-bundle 3.80.0 3.88.0 The missing Symfony Admin Generator sonata-project/block-bundle 3.21.0 4.5.0 Symfony SonataBlockBundle sonata-project/cache 2.0.1 2.1.0 Cache library sonata-project/cache-bundle 3.2.1 3.2.1 This bundle provides caching services sonata-project/classification-bundle 3.14.0 3.14.0 Symfony SonataClassificationBundle sonata-project/datagrid-bundle 3.2.0 3.2.0 Symfony SonataDatagridBundle sonata-project/doctrine-extensions 1.11.0 1.11.0 Doctrine2 behavioral extensions sonata-project/doctrine-orm-admin-bundle 3.26.0 3.27.0 Integrate Doctrine ORM into the SonataAdminBundle sonata-project/easy-extends-bundle 2.5.0 2.5.0 Symfony SonataEasyExtendsBundle sonata-project/exporter 2.5.0 2.5.0 Lightweight Exporter library sonata-project/form-extensions 1.8.1 1.8.1 Symfony form extensions sonata-project/formatter-bundle 4.3.0 4.3.0 Symfony SonataFormatterBundle sonata-project/intl-bundle 2.10.0 2.10.0 Symfony SonataIntlBundle sonata-project/media-bundle 3.29.0 3.29.0 Symfony SonataMediaBundle sonata-project/seo-bundle 2.12.0 2.12.0 Symfony SonataSeoBundle sonata-project/twig-extensions 1.5.0 1.5.0 Sonata twig extensions sonata-project/user-bundle 4.10.1 4.10.1 Symfony SonataUserBundle ```

Symfony packages

show

``` $ composer show --latest 'symfony/*' symfony/acl-bundle v2.0.0 v2.0.0 Symfony AclBundle symfony/asset v4.4.18 v5.2.1 Symfony Asset Component symfony/browser-kit v4.4.18 v5.2.1 Symfony BrowserKit Component symfony/cache v4.4.18 v5.2.1 Symfony Cache component with PSR-6, PSR-16, and tags symfony/cache-contracts v2.2.0 v2.2.0 Generic abstractions related to caching symfony/config v4.4.18 v5.2.1 Symfony Config Component symfony/console v4.4.18 v5.2.1 Symfony Console Component symfony/css-selector v4.4.18 v5.2.1 Symfony CssSelector Component symfony/debug v4.4.18 v4.4.18 Symfony Debug Component symfony/dependency-injection v4.4.18 v5.2.1 Symfony DependencyInjection Component symfony/deprecation-contracts v2.2.0 v2.2.0 A generic function and convention to trigger deprecation notices symfony/doctrine-bridge v4.4.18 v5.2.1 Symfony Doctrine Bridge symfony/dom-crawler v4.4.18 v5.2.1 Symfony DomCrawler Component symfony/dotenv v4.4.18 v5.2.1 Registers environment variables from a .env file symfony/error-handler v4.4.18 v5.2.1 Symfony ErrorHandler Component symfony/event-dispatcher v4.4.18 v5.2.1 Symfony EventDispatcher Component symfony/event-dispatcher-contracts v1.1.9 v2.2.0 Generic abstractions related to dispatching event symfony/expression-language v4.4.18 v5.2.1 Symfony ExpressionLanguage Component symfony/filesystem v4.4.18 v5.2.1 Symfony Filesystem Component symfony/finder v4.4.18 v5.2.1 Symfony Finder Component symfony/flex v1.11.0 v1.11.0 Composer plugin for Symfony symfony/form v4.4.18 v5.2.1 Symfony Form Component symfony/framework-bundle v4.4.18 v5.2.1 Symfony FrameworkBundle symfony/http-client v4.4.18 v5.2.1 Symfony HttpClient component symfony/http-client-contracts v2.3.1 v2.3.1 Generic abstractions related to HTTP clients symfony/http-foundation v4.4.18 v5.2.1 Symfony HttpFoundation Component symfony/http-kernel v4.4.18 v5.2.1 Symfony HttpKernel Component symfony/inflector v4.4.18 v5.2.1 Symfony Inflector Component symfony/intl v4.4.18 v5.2.1 A PHP replacement layer for the C intl extension that includes additional data from the ICU library. symfony/mailer v4.4.18 v5.2.1 Symfony Mailer Component symfony/maker-bundle v1.28.0 v1.28.0 Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code. symfony/messenger v4.4.18 v5.2.1 Symfony Messenger Component symfony/mime v4.4.18 v5.2.1 A library to manipulate MIME messages symfony/monolog-bridge v4.4.18 v5.2.1 Symfony Monolog Bridge symfony/monolog-bundle v3.6.0 v3.6.0 Symfony MonologBundle symfony/options-resolver v4.4.18 v5.2.1 Symfony OptionsResolver Component symfony/orm-pack v1.0.8 v2.1.0 A pack for the Doctrine ORM symfony/phpunit-bridge v5.2.1 v5.2.1 Symfony PHPUnit Bridge symfony/polyfill-intl-grapheme v1.22.0 v1.22.0 Symfony polyfill for intl's grapheme_* functions symfony/polyfill-intl-icu v1.22.0 v1.22.0 Symfony polyfill for intl's ICU-related data and classes symfony/polyfill-intl-idn v1.22.0 v1.22.0 Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions symfony/polyfill-intl-normalizer v1.22.0 v1.22.0 Symfony polyfill for intl's Normalizer class and related functions symfony/polyfill-mbstring v1.22.0 v1.22.0 Symfony polyfill for the Mbstring extension symfony/polyfill-php72 v1.22.0 v1.22.0 Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions symfony/polyfill-php73 v1.22.0 v1.22.0 Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions symfony/polyfill-php80 v1.22.0 v1.22.0 Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions symfony/process v4.4.18 v5.2.1 Symfony Process Component symfony/profiler-pack v1.0.5 v1.0.5 A pack for the Symfony web profiler symfony/property-access v4.4.18 v5.2.1 Symfony PropertyAccess Component symfony/psr-http-message-bridge v2.0.2 v2.0.2 PSR HTTP message bridge symfony/routing v4.4.18 v5.2.1 Symfony Routing Component symfony/security-acl v3.1.1 v3.1.1 Symfony Security Component - ACL (Access Control List) symfony/security-bundle v4.4.18 v5.2.1 Symfony SecurityBundle symfony/security-core v4.4.18 v5.2.1 Symfony Security Component - Core Library symfony/security-csrf v4.4.18 v5.2.1 Symfony Security Component - CSRF Library symfony/security-guard v4.4.18 v5.2.1 Symfony Security Component - Guard symfony/security-http v4.4.18 v5.2.1 Symfony Security Component - HTTP Integration symfony/serializer v4.4.18 v5.2.1 Symfony Serializer Component symfony/service-contracts v2.2.0 v2.2.0 Generic abstractions related to writing services symfony/stopwatch v4.4.18 v5.2.1 Symfony Stopwatch Component symfony/string v5.2.1 v5.2.1 Symfony String component symfony/swiftmailer-bundle v3.5.1 v3.5.1 Symfony SwiftmailerBundle symfony/templating v4.4.18 v5.2.1 Symfony Templating Component symfony/translation v4.4.18 v5.2.1 Symfony Translation Component symfony/translation-contracts v2.3.0 v2.3.0 Generic abstractions related to translation symfony/twig-bridge v4.4.18 v5.2.1 Symfony Twig Bridge symfony/twig-bundle v4.4.18 v5.2.1 Symfony TwigBundle symfony/validator v4.4.18 v5.2.1 Symfony Validator Component symfony/var-dumper v4.4.18 v5.2.1 Symfony mechanism for exploring and dumping PHP variables symfony/var-exporter v4.4.18 v5.2.1 A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code symfony/web-profiler-bundle v4.4.18 v5.2.1 Symfony WebProfilerBundle symfony/webpack-encore-bundle v1.9.0 v1.9.0 Integration with your Symfony app & Webpack Encore! symfony/yaml v4.4.18 v5.2.1 Symfony Yaml Component ```

PHP version

$ php -v
PHP 7.4.13 (cli) (built: Nov 28 2020 06:24:43) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.13, Copyright (c), by Zend Technologies

Subject

When showing a boolean value of a related object property it always shows as null which translates to false.

Minimal repository with the bug

Tested fromm 3.80

Steps to reproduce

Create an object which refers to the related object

/**
 * Class TrackScore
 *
 * @ORM\Table(name="track_score")
 * @ORM\Entity(repositoryClass="Spinnin\Repository\TrackScoreRepository")
 */
class TrackScore
{

    /**
     * @var Track
     *
     * @ORM\ManyToOne(targetEntity="Spinnin\Entity\Track")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="track_id", referencedColumnName="id", onDelete="CASCADE")
     * })
     */
    protected Track $track;

    /**
     * @return Track
     */
    public function getTrack(): Track
    {
        return $this->track;
    }

    /**
     * @param Track $track
     *
     * @return TrackScore
     */
    public function setTrack(Track $track): TrackScore
    {
        $this->track = $track;

        return $this;
    }
}

Create an object with a boolean property


/**
 * Track
 *
 * @ORM\Table(name="track", options={"collate"="utf8mb4_general_ci", "charset"="utf8mb4"})
 * @ORM\Entity(repositoryClass="Spinnin\Repository\TrackRepository")
 */
class Track extends AbstractTimestampableEntity
{
    /**
     * @var bool
     *
     * @ORM\Column(name="is_remix", type="boolean", nullable=false)
     */
    private $isRemix = false;

    /**
     * @param bool $isRemix
     *
     * @return Track
     */
    public function setIsRemix($isRemix): Track
    {
        $this->isRemix = (bool) $isRemix;

        return $this;
    }

    /**
     * Get isRemix
     *
     * @return bool
     */
    public function isRemix(): bool
    {
        return $this->isRemix;
    }
}

Create an admin for the related entity with a list view setup

/**
 * Class ScoutedTracksAdmin
 */
class ScoutedTracksAdmin extends AbstractAdmin
{
    /**
     * Configure fields to be listed
     *
     * @param ListMapper $list
     *
     * @return void
     */
    protected function configureListFields(ListMapper $list): void
    {
        $list->add('track.trackname', null, ['label' => 'Title'])
            ->add('track.user.username', null, ['label' => 'User'])
            ->add('track.genre', null, ['label' => 'Genre'])
            ->add('track.isRemix', null, ['label' => 'Remix'])
            ->add('_action', 'actions', [
                'actions' => [
                    'show' => ['template' => 'track/crud/scout__list_action_show.html.twig'],
                    'edit' => [],
                ],
            ]);
    }
}

Expected results

When isRemix is true have the admin show 'yes'

Actual results

The admin shows 'no' while the related object holds a 'true' value

Additional information

When dumping the variables I can see value is set to null while the track object has a isRemix property value set to true

// vendor/sonata-project/admin-bundle/src/Resources/views/CRUD/base_list_field.html.twig
<td ...>
{{ dump(object.track.isRemix) }}
{{ dump(value) }}

Output

vendor/twig/twig/src/Extension/DebugExtension.php:61:boolean true
vendor/twig/twig/src/Extension/DebugExtension.php:61:null
VincentLanglet commented 3 years ago

You messed up your property/getter.

When looking for foo, we're trying getFoo, hasFoo, isFoo. Here it's isRemix, so we try getIsRemix, hasIsRemix and isIsRemix.

If your getter is isRemix(), your property should be called remix.

But maybe we can add the name of the field in the list of the getter we try.

Martin1982 commented 3 years ago

You are correct, when adding the method the list-values (and filters) work like expected;

    /**
     * Shim method for isRemix
     * Related to issue; https://github.com/sonata-project/SonataAdminBundle/issues/6795
     *
     * @return bool
     */
    public function getIsRemix(): bool
    {
        return $this->isRemix();
    }

So if there would be another way within Sonata Admin to set this getter's name or try this type of naming this issue would be a feature request.

In any other case I'll have to refactor the entity to confirm to the conventions. I'm not sure if these are documented anywhere though. I might've missed it.

Thanks for clearing this up so fast.

VincentLanglet commented 3 years ago

You can do a PR to modify this: https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Admin/BaseFieldDescription.php#L436-L440

n4huel commented 3 years ago

I ran into the same problem and I see it a bit tricky since if you create an admin for the Track entity, the getter for isRemix property is inferred correctly, the problem comes when we work with associations.

@Martin1982, another solution is using ->add('track.remix', 'boolean', ['label' => 'Remix']) but I think if you rename the property you will save some troubles, but it's only an assumption.

phansys commented 3 years ago

I ran into the same problem and I see it a bit tricky since if you create an admin for the Track entity, the getter for isRemix property is inferred correctly, the problem comes when we work with associations.

This behavior occurs because the logic at the model manager implementation (see SonataDoctrineORMAdminBundle), since the "code" option is setted there for the root model, and that option is used later as the first getter alternative here:

https://github.com/sonata-project/SonataAdminBundle/blob/8a48504af9b98c6f56dff9cc4674f12485d942d6/src/Admin/BaseFieldDescription.php#L424

Then, the getter list is inferred this way:

array:4 [▼
  0 => "isRemix"
  1 => "getIsRemix"
  2 => "isIsRemix"
  3 => "hasIsRemix"
]
VincentLanglet commented 3 years ago

By the way, if we deprecate the parameters option https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/Admin/BaseFieldDescription.php#L427-L429 we could rely on PropertyAccessor instead.

I'm not sure if this option is really used.