doctrine-extensions / DoctrineExtensions

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

A [SoftDeleteable] cycle has been detected, so a topological sort is not possible. The getCycle() method provides the list of nodes that form the cycle. #2795

Open imajim opened 2 months ago

imajim commented 2 months ago

Hello, i have an issue with sofrdelete on Symfony 6.3 and doctrine extension 3 softdeleteable

i would like soft delete an entity User but i've got a doctrine error :

image

Could you help me ? Thx ^^

UserController :

#[Route("/delete/{userId}", name: "app_admin_deleteUser", methods: ["POST"])]
    public function deleteUser(Request $request, $userId)
    {

        $user = $this->em->getRepository(AdminUser::class)->find($userId);
        $formDelete = $this->createDeleteForm($user);
        $formDelete->handleRequest($request);
        if ($formDelete->isSubmitted() && $formDelete->isValid()) {
            $this->em->remove($user);
            $this->em->flush();
            $this->session->getFlashBag()->add('success', 'Utilisateur supprimé.');

        }

        return $this->redirectToRoute('app_admin_users');

    }

My User Entity :

<?php

namespace App\Entity\Admin;

use App\Classes\AdminUserTrait;
use App\Entity\Application\AdminUserExtraData;
use App\Entity\Application\Connection;
use App\Entity\Application\Dossier;
use App\Entity\Application\DossierConsultation;
use App\Entity\Application\Evenement;
use App\Entity\Application\Invitation;
use App\Entity\Application\Notification;
use App\Entity\Application\Prospect;
use App\Repository\Admin\AdminUserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Webingco\AdminBundle\Entity\AdminUserModel as BaseAdminUser;

#[ORM\Entity(repositoryClass: AdminUserRepository::class)]
#[Gedmo\SoftDeleteable(fieldName: 'deletedAt', timeAware: false, hardDelete: true)]
class AdminUser extends BaseAdminUser
{
    use AdminUserTrait;
    public $level;
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private $id;

    #[ORM\OneToMany(targetEntity: Dossier::class, mappedBy: 'commercial', orphanRemoval: true)]
    private $dossiers;

    #[ORM\OneToOne(targetEntity: AdminUserExtraData::class, mappedBy: 'adminUser', cascade: ['persist', 'remove'])]
    private $adminUserExtraData;

    #[ORM\JoinTable(name: 'admin_user_responsables')]
    #[ORM\ManyToMany(targetEntity: AdminUser::class, inversedBy: 'commerciaux')]
    private $responsables;

    #[ORM\ManyToMany(targetEntity: AdminUser::class, mappedBy: 'responsables')]
    private $commerciaux;

    #[ORM\OneToMany(mappedBy: 'user', targetEntity: Notification::class, orphanRemoval: true)]
    private $notifications;

    #[ORM\OneToMany(mappedBy: 'user', targetEntity: DossierConsultation::class, orphanRemoval: true)]
    private $dossierConsultations;

    #[ORM\OneToOne(mappedBy: 'user', targetEntity: Connection::class, cascade: ['persist', 'remove'])]
    private $connection;

    #[ORM\OneToMany(mappedBy: 'user', targetEntity: Prospect::class, orphanRemoval: true)]
    private $prospects;

    #[ORM\OneToMany(mappedBy: 'user', targetEntity: Invitation::class, orphanRemoval: true)]
    private $invitations;

    #[ORM\ManyToMany(targetEntity: AdminUser::class, mappedBy: 'prospecteursCommerciaux')]
    private Collection $prospecteurs;

    #[ORM\JoinTable(name: 'admin_user_prospecteurs_commerciaux')]
    #[ORM\JoinColumn(name: 'prospecteur_id')]
    #[ORM\InverseJoinColumn(name: 'commercial_id')]
    #[ORM\ManyToMany(targetEntity: AdminUser::class, inversedBy: 'prospecteurs')]
    private Collection $prospecteursCommerciaux;

    #[ORM\OneToMany(mappedBy: 'user', targetEntity: Evenement::class, orphanRemoval: true)]
    private $evenements;

    #[ORM\Column(name: 'deletedAt', type: Types::DATETIME_MUTABLE, nullable: true)]
    private $deletedAt;

    public function __construct()
    {
        $this->dossiers = new ArrayCollection();
        $this->responsables = new ArrayCollection();
        $this->commerciaux = new ArrayCollection();
        $this->notifications = new ArrayCollection();
        $this->dossierConsultations = new ArrayCollection();
        $this->prospects = new ArrayCollection();
        $this->invitations = new ArrayCollection();
        $this->prospecteursCommerciaux = new ArrayCollection();
        $this->prospecteurs = new ArrayCollection();
        $this->evenements = new ArrayCollection();
    }
....
    public function getDeletedAt(): ?\DateTime
    {
        return $this->deletedAt;
    }

    public function setDeletedAt(?\DateTime $deletedAt): self
    {
        $this->deletedAt = $deletedAt;
        return $this;
    }

}

AdminUserModel Entity Superclass :

<?php

namespace Webingco\AdminBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Gedmo\Mapping\Annotation as Gedmo;
use Webingco\AdminBundle\Classes\AdminUserTrait;

#[ORM\MappedSuperclass]
#[UniqueEntity(fields: ['email'], message: 'Cette adresse email est déja utilisée.')]
class AdminUserModel implements UserInterface, PasswordAuthenticatedUserInterface
{
    use AdminUserTrait;

    #[ORM\Column(type: 'string', length: 180, unique: true)]
    private $email;

    #[ORM\Column(type: 'json')]
    private $roles = [];

    /**
     * @var string The hashed password
     */
    #[ORM\Column(type: 'string')]
    private $password;

    #[ORM\Column(type: 'boolean')]
    private $isVerified = false;

    #[ORM\Column(type: 'boolean')]
    private $isEnabled;

    #[ORM\Column(type: 'datetime')]
    #[Gedmo\Timestampable(on: 'create')]
    private $createdAt;

    #[ORM\Column(type: 'datetime')]
    #[Gedmo\Timestampable(on: 'update')]
    private $updatedAt;

    #[ORM\Column(type: 'string', length: 100, nullable: true)]
    private $lastname;

    #[ORM\Column(type: 'string', length: 100, nullable: true)]
    private $firstname;

    #[ORM\Column(type: 'string', length: 20, nullable: true)]
    private $phone;

    #[ORM\Column(type: 'string', length: 50, nullable: true)]
    private $ip;

    #[ORM\Column(type: 'datetime', nullable: true)]
    private $lastLogin;

    private $plainPassword = null;

...

}

Doctrine.yaml :

doctrine:
    dbal:
        password: '%env(DATABASE_PASSWORD)%'
        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: '13'
    orm:
        filters:
            softdeleteable:
                class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
                enabled: true
        dql:
            string_functions:
                JSON_CONTAINS: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Mysql\JsonContains
                FIELD: DoctrineExtensions\Query\Mysql\Field
        auto_generate_proxy_classes: true
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        auto_mapping: true
        report_fields_where_declared: true
        mappings:
            gedmo_tree:
                prefix: Gedmo\Tree\Entity
                dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Tree/Entity"
                alias: GedmoTree # (optional) it will default to the name set for the mapping
                is_bundle: false
            App:
                is_bundle: false
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App

stof_doctrine_extensions.yaml :

stof_doctrine_extensions:
    default_locale: fr_FR
    orm:
        default:
            sluggable: true
            timestampable: true
            tree: true
            softdeleteable: true