doctrine-extensions / DoctrineExtensions

Doctrine2 behavioral extensions, Translatable, Sluggable, Tree-NestedSet, Timestampable, Loggable, Sortable
MIT License
4.01k stars 1.26k forks source link

SoftDeleteable is ignored and delete records hardly #2750

Open nacesprin opened 5 months ago

nacesprin commented 5 months ago

Environment

Package

show

``` $ composer show --latest gedmo/doctrine-extensions appuser@b6948851163f:/appdata/www$ composer show --latest gedmo/doctrine-extensions name : gedmo/doctrine-extensions descrip. : Doctrine behavioral extensions keywords : Blameable, behaviors, doctrine, extensions, gedmo, loggable, nestedset, odm, orm, sluggable, sortable, timestampable, translatable, tree, uploadable versions : * v3.14.0 latest : v3.14.0 type : library license : MIT License (MIT) (OSI approved) https://spdx.org/licenses/MIT.html#licenseText homepage : http://gediminasm.org/ source : [git] https://github.com/doctrine-extensions/DoctrineExtensions.git 3b5b5cba476b4ae32a55ef69ef2e59d64d5893cf dist : [zip] https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/3b5b5cba476b4ae32a55ef69ef2e59d64d5893cf 3b5b5cba476b4ae32a55ef69ef2e59d64d5893cf path : /appdata/www/vendor/gedmo/doctrine-extensions names : gedmo/doctrine-extensions support email : gediminas.morkevicius@gmail.com issues : https://github.com/doctrine-extensions/DoctrineExtensions/issues source : https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.14.0 wiki : https://github.com/Atlantic18/DoctrineExtensions/tree/main/doc autoload psr-4 Gedmo\ => src/ requires behat/transliterator ^1.2 doctrine/annotations ^1.13 || ^2.0 doctrine/collections ^1.2 || ^2.0 doctrine/common ^2.13 || ^3.0 doctrine/event-manager ^1.2 || ^2.0 doctrine/persistence ^2.2 || ^3.0 php ^7.4 || ^8.0 psr/cache ^1 || ^2 || ^3 symfony/cache ^5.4 || ^6.0 || ^7.0 symfony/deprecation-contracts ^2.1 || ^3.0 requires (dev) doctrine/cache ^1.11 || ^2.0 doctrine/dbal ^3.2 doctrine/doctrine-bundle ^2.3 doctrine/mongodb-odm ^2.3 doctrine/orm ^2.14.0 friendsofphp/php-cs-fixer ^3.14.0 nesbot/carbon ^2.71 || 3.x-dev as 3.0 phpstan/phpstan ^1.10.2 phpstan/phpstan-doctrine ^1.0 phpstan/phpstan-phpunit ^1.0 phpunit/phpunit ^9.6 rector/rector ^0.18 symfony/console ^5.4 || ^6.0 || ^7.0 symfony/phpunit-bridge ^6.0 || ^7.0 symfony/yaml ^5.4 || ^6.0 || ^7.0 suggests doctrine/mongodb-odm to use the extensions with the MongoDB ODM doctrine/orm to use the extensions with the ORM conflicts doctrine/dbal <3.2 doctrine/mongodb-odm <2.3 doctrine/orm <2.14.0 || 2.16.0 || 2.16.1 sebastian/comparator <2.0 ```

Doctrine packages

show

``` appuser@b6948851163f:/appdata/www$ composer show --latest 'doctrine/*' Color legend: - patch or minor release available - update recommended - major release available - update possible - up to date version Direct dependencies required in composer.json: doctrine/dbal 3.8.0 3.8.0 Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management. doctrine/doctrine-bundle 2.11.1 2.11.1 Symfony DoctrineBundle doctrine/doctrine-fixtures-bundle 3.5.1 3.5.1 Symfony DoctrineFixturesBundle doctrine/doctrine-migrations-bundle 3.3.0 3.3.0 Symfony DoctrineMigrationsBundle doctrine/orm 2.17.4 2.17.4 Object-Relational-Mapper for PHP Transitive dependencies not required in composer.json: doctrine/annotations 2.0.1 2.0.1 Docblock Annotations Parser doctrine/cache 2.2.0 2.2.0 PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others. doctrine/collections 2.1.4 2.1.4 PHP Doctrine Collections library that adds additional functionality on top of PHP arrays. doctrine/common 3.4.3 3.4.3 PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more. doctrine/data-fixtures 1.7.0 1.7.0 Data Fixtures for all Doctrine Object Managers doctrine/deprecations 1.1.3 1.1.3 A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages. doctrine/event-manager 2.0.0 2.0.0 The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects. doctrine/inflector 2.0.9 2.0.9 PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words. doctrine/instantiator 2.0.0 2.0.0 A small, lightweight utility to instantiate objects in PHP without invoking their constructors doctrine/lexer 2.1.0 3.0.0 PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers. doctrine/migrations 3.7.2 3.7.2 PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It i... doctrine/persistence 3.2.0 3.2.0 The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share. doctrine/sql-formatter 1.1.3 1.1.3 a PHP SQL highlighting library ```

PHP version

$ php -v
appuser@b6948851163f:/appdata/www$ php -v
PHP 8.1.1 (cli) (built: Dec 21 2021 19:46:50) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.1, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.1, Copyright (c), by Zend Technologies
    with Xdebug v3.2.1, Copyright (c) 2002-2023, by Derick Rethans

Subject

I am using EasyAdmin (4.8.12) After installed SoftDeleteable as instructions in documentation, the SoftDeleteable listener is not working properly.

Minimal repository with the bug

config/packages/stof_doctrine_extensions.yaml
# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html
# See the official DoctrineExtensions documentation for more details: https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc
stof_doctrine_extensions:
    default_locale: en_US

    # Only used if you activated the Uploadable extension
    uploadable:
        # Default file path: This is one of the three ways you can configure the path for the Uploadable extension
        default_file_path:       "%kernel.project_dir%/public/uploads"

        # Mime type guesser class: Optional. By default, we provide an adapter for the one present in the Mime component of Symfony
        mime_type_guesser_class: Stof\DoctrineExtensionsBundle\Uploadable\MimeTypeGuesserAdapter

        # Default file info class implementing FileInfoInterface: Optional. By default we provide a class which is prepared to receive an UploadedFile instance.
        default_file_info_class: Stof\DoctrineExtensionsBundle\Uploadable\UploadedFileInfo
    orm:
        default:
            timestampable: true
            softdeleteable: true
    mongodb:
        default: ~
config/packages/doctrine.yaml
doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'

        # IMPORTANT: You MUST configure your server version,
        # either here or in the DATABASE_URL env var (see .env file)
        #server_version: '15'
    orm:
        auto_generate_proxy_classes: true
        enable_lazy_ghost_objects: true
        report_fields_where_declared: true
        validate_xml_mapping: true
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                type: attribute
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App
        filters:
            softdeleteable:
                class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
                enabled: true
when@test:
    doctrine:
        dbal:
            # "TEST_TOKEN" is typically set by ParaTest
            dbname_suffix: '_test%env(default::TEST_TOKEN)%'

when@dev:
    doctrine:
        dbal:
            profiling_collect_backtrace: true 

when@prod:
    doctrine:
        orm:
            auto_generate_proxy_classes: false
            proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
            query_cache_driver:
                type: pool
                pool: doctrine.system_cache_pool
            result_cache_driver:
                type: pool
                pool: doctrine.result_cache_pool

    framework:
        cache:
            pools:
                doctrine.result_cache_pool:
                    adapter: cache.app
                doctrine.system_cache_pool:
                    adapter: cache.system
src/Entity/Usuario.php

<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace App\Entity;

use App\Repository\UsuarioRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\DBAL\Types\Types;

#[ORM\Entity(repositoryClass: UsuarioRepository::class)]
class Usuario extends BaseEntity implements UserInterface, PasswordAuthenticatedUserInterface
{

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 180, type: Types::STRING, unique: true, nullable: false)]
    #[Assert\Email]
    #[Assert\NotBlank]
    private ?string $email = null;

    #[ORM\Column(type: Types::JSON)]
    private array $roles = [];

    /**
     * @var string The hashed password
     */
    #[ORM\Column(type: Types::STRING)]
    private ?string $password = null;

    #[ORM\Column(length: 255, nullable: true)]
    private ?string $nombre = null;

    #[ORM\Column(length: 255, nullable: true)]
    private ?string $apellidos = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): static
    {
        $this->email = $email;

        return $this;
    }

    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUserIdentifier(): string
    {
        return (string) $this->email;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): static
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @see PasswordAuthenticatedUserInterface
     */
    public function getPassword(): string
    {
        return $this->password;
    }

    public function setPassword(string $password): static
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials(): void
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

    public function getNombre(): ?string
    {
        return $this->nombre;
    }

    public function setNombre(?string $nombre): static
    {
        $this->nombre = $nombre;

        return $this;
    }

    public function getApellidos(): ?string
    {
        return $this->apellidos;
    }

    public function setApellidos(?string $apellidos): static
    {
        $this->apellidos = $apellidos;

        return $this;
    }

    public function getNombreCompleto(): ?string
    {
        $nombre = $this->getNombre();
        $apellidos = $this->getApellidos();
        return implode(", ", [$nombre, $apellidos]);
    }
}
src/Entity/BaseEntity.php
<?php

declare(strict_types=1);

namespace App\Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
use Gedmo\Timestampable\Traits\TimestampableEntity;

#MappedSuperclass
#[Gedmo\SoftDeleteable(fieldName: 'deletedAt', timeAware: true, hardDelete: false)]
class BaseEntity
{
    use TimestampableEntity;
    use SoftDeleteableEntity;
}

Steps to reproduce

All data are fake:

Delete ID = 23:

image image image image

Expected results

Keep record ID = 23 and set "Deleted At" field with current date.

Actual results

Record ID = 23 is hard deleted.

Investigation

Inspecting listener SoftDeleteableListener with XDebug, I get $config empty: image

I was inspected with Timestampable listener and $config was filled with data instead.

Here you can see with XDebug, that variable $this->name is "SoftDeleteable": image

mbabker commented 5 months ago

There is a test case for the mapped superclass scenario your setup describes.

Adding dd($this->softDeleteableListener->getConfiguration($this->em, self::MAPPED_SUPERCLASS_CHILD_CLASS)); to the start of that test case shows what looks to be the right configuration for that entity:

array:5 [
  "softDeleteable" => true
  "fieldName" => "deletedAt"
  "timeAware" => false
  "hardDelete" => true
  "useObjectClass" => "Gedmo\Tests\SoftDeleteable\Fixture\Entity\Child"
]

I also went ahead and added the TimestampableEntity trait to the mapped superclass and the TimestampableListener to that test case and dumping the entire static config reference in the subscriber's getConfiguration() method gives me this:

array:2 [
  "SoftDeleteable" => array:9 [
    [snip]
    "Gedmo\Tests\SoftDeleteable\Fixture\Entity\Child" => array:5 [
      "softDeleteable" => true
      "fieldName" => "deletedAt"
      "timeAware" => false
      "hardDelete" => true
      "useObjectClass" => "Gedmo\Tests\SoftDeleteable\Fixture\Entity\Child"
    ]
    [snip]
  ]
  "Timestampable" => array:1 [
    "Gedmo\Tests\SoftDeleteable\Fixture\Entity\Child" => array:3 [
      "create" => array:1 [
        0 => "createdAt"
      ]
      "update" => array:1 [
        0 => "updatedAt"
      ]
      "useObjectClass" => "Gedmo\Tests\SoftDeleteable\Fixture\Entity\Child"
    ]
  ]
]

So, the config does appear to be loaded in properly.

nacesprin commented 5 months ago

OKay @mbabker . Excuse me, but I have not clearly how do I must to continue. What do you need exactly from my side? Do you need I execute the test case and write down here the result of the dd() function?

Chris53897 commented 4 months ago

@nacesprin in your example you wrote. #MappedSuperclass Is it a correct PHP Attribute or just a comment?

The Entity Mapping is missing in doctrine.yaml. I use the StofDoctrineExtensionsBundle so i am not sure for your case.

      # StofDoctrineExtensionsBundle (https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/configuration.html#add-the-extensions-to-your-mapping)
      gedmo_loggable:
        type: attribute
        prefix: Gedmo\Loggable\Entity
        dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Loggable/Entity"
        alias: GedmoLoggable # (optional) it will default to the name set for the mapping
        is_bundle: false