makinacorpus / DbToolsBundle

A PHP library to backup, restore and anonymize databases
https://dbtoolsbundle.readthedocs.io
MIT License
181 stars 15 forks source link

Issue on anonimization for entities with inheritance - Property App\Entity\SubComponent::$id does not exist #160

Closed the-juju closed 5 months ago

the-juju commented 5 months ago

PHP's version : 8.3 Symfony's version : 6.4 Bundle's version : 1.1.0

Hello there, I'm facing an issue while trying to anonymize my local database. I have an inheritance between classes. My child classes do not have $id attribute and when I'm trying to run the following command : php bin/console db-tools:anonymize --local-database

I'm getting the error mentionned in title :

Start anonymizing database

In AttributesLoader.php line 62:

  [ReflectionException]
  Property App\Entity\SubComponent::$id does not exist

Exception trace:
  at /srv/app/vendor/makinacorpus/db-tools-bundle/src/Anonymization/Config/Loader/AttributesLoader.php:62
 ReflectionClass->getProperty() at /srv/app/vendor/makinacorpus/db-tools-bundle/src/Anonymization/Config/Loader/AttributesLoader.php:62
 MakinaCorpus\DbToolsBundle\Anonymization\Config\Loader\AttributesLoader->load() at /srv/app/vendor/makinacorpus/db-tools-bundle/src/Anonymization/AnonymizatorFactory.php:44
 MakinaCorpus\DbToolsBundle\Anonymization\AnonymizatorFactory->getOrCreate() at /srv/app/vendor/makinacorpus/db-tools-bundle/src/Command/Anonymization/AnonymizeCommand.php:292
 MakinaCorpus\DbToolsBundle\Command\Anonymization\AnonymizeCommand->doAnonymizeDatabase() at /srv/app/vendor/makinacorpus/db-tools-bundle/src/Command/Anonymization/AnonymizeCommand.php:212
 MakinaCorpus\DbToolsBundle\Command\Anonymization\AnonymizeCommand->execute() at /srv/app/vendor/symfony/console/Command/Command.php:326
 Symfony\Component\Console\Command\Command->run() at /srv/app/vendor/symfony/console/Application.php:1096
 Symfony\Component\Console\Application->doRunCommand() at /srv/app/vendor/symfony/framework-bundle/Console/Application.php:126
 Symfony\Bundle\FrameworkBundle\Console\Application->doRunCommand() at /srv/app/vendor/symfony/console/Application.php:324
 Symfony\Component\Console\Application->doRun() at /srv/app/vendor/symfony/framework-bundle/Console/Application.php:80
 Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /srv/app/vendor/symfony/console/Application.php:175
 Symfony\Component\Console\Application->run() at /srv/app/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php:49
 Symfony\Component\Runtime\Runner\Symfony\ConsoleApplicationRunner->run() at /srv/app/vendor/autoload_runtime.php:29
 require_once() at /srv/app/bin/console:11

So far, i'm not finding any related issue or documentation. I will try to have a look directly in the code. Do you have any idea on the cause? Or is it just insupported?

Thanks, Julien

pounard commented 5 months ago

Hello @the-juju, I'd love to be able to reproduce it. Could you give us a reduced Doctrine entity code which reproduces the issue ?

Also, we are going to release a new 1.2.0 in a near future (ie. a few days) I'd love to know if that's still an issue.

the-juju commented 5 months ago

Hello @pounard sure i'll add them in attachment to this message ! Also, we're using API Platform on version 3.2

ProjectComponent.php (parent)

<?php

namespace App\Entity;

use App\Config\ProjectConfig;
use App\Repository\ProjectComponentRepository;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Uid\Uuid;

#[ORM\Entity(repositoryClass: ProjectComponentRepository::class)]
#[ORM\InheritanceType('JOINED')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap([
    'title' => ProjectComponentTitle::class,
    'text' => ProjectComponentText::class,
    'image' => ProjectComponentImage::class,
    'video' => ProjectComponentVideo::class,
    'audio' => ProjectComponentAudio::class,
])]
#[ORM\Index(columns: ['type'], name: 'idx_publication_type')]
class ProjectComponent
{
    use TimestampableEntity;

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

    #[ORM\Column(type: UuidType::NAME, nullable: true)]
    private ?Uuid $uuid = null;

    #[ORM\ManyToOne(inversedBy: 'components')]
    #[ORM\JoinColumn(nullable: false)]
    private ?Project $project = null;

    #[ORM\Column]
    private ?int $position = null;

    #[Groups([
        ProjectConfig::UPDATE,
        ProjectConfig::READ,
        ProjectConfig::PUBLIC_READ,
    ])]
    private ?string $type = null;

    public function __construct()
    {
        $this->uuid = Uuid::v7();
    }

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

    public function getUuid(): ?Uuid
    {
        return $this->uuid;
    }

    public function setUuid(Uuid $uuid): static
    {
        $this->uuid = $uuid;

        return $this;
    }

    public function getProject(): ?Project
    {
        return $this->project;
    }

    public function setProject(?Project $project): static
    {
        $this->project = $project;

        return $this;
    }

    public function getPosition(): ?int
    {
        return $this->position;
    }

    public function setPosition(int $position): static
    {
        $this->position = $position;

        return $this;
    }

    public function setType(string $type): static
    {
        $this->type = $type;

        return $this;
    }
}

ProjectComponentImage.php

<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use App\Config\ProjectConfig;
use App\Contracts\ProjectComponentInterface;
use App\Contracts\ProjectMediaComponentInterface;
use App\Enum\ComponentType;
use App\Repository\BlockImageRepository;
use Doctrine\ORM\Mapping as ORM;
use Exception;
use Override;
use Symfony\Component\Serializer\Attribute\Groups;

#[ORM\Entity(repositoryClass: BlockImageRepository::class)]
#[ORM\Table(name: 'component_image')]
#[ApiResource]
class ProjectComponentImage extends ProjectComponent implements ProjectComponentInterface, ProjectMediaComponentInterface
{
    #[ORM\Column(length: 255, nullable: true)]
    #[Groups([
        ProjectConfig::UPDATE,
        ProjectConfig::READ,
        ProjectConfig::PUBLIC_READ,
    ])]
    private ?string $url = null;

    #[ORM\Column(length: 255, nullable: true)]
    #[Groups([
        ProjectConfig::UPDATE,
        ProjectConfig::READ,
        ProjectConfig::PUBLIC_READ,
    ])]
    private ?string $thumbnail_url = null;

    #[ORM\Column(length: 20, nullable: true)]
    #[Groups([
        ProjectConfig::UPDATE,
        ProjectConfig::READ,
        ProjectConfig::PUBLIC_READ,
    ])]
    private ?string $mime_type = null;

    public function getUrl(): ?string
    {
        return $this->url;
    }

    public function setUrl(?string $url): static
    {
        $this->url = $url;

        return $this;
    }

    public function getThumbnailUrl(): ?string
    {
        return $this->thumbnail_url;
    }

    public function setThumbnailUrl(?string $thumbnail_url): static
    {
        $this->thumbnail_url = $thumbnail_url;

        return $this;
    }

    public function getMimeType(): ?string
    {
        return $this->mime_type;
    }

    public function setMimeType(?string $mime_type): static
    {
        $this->mime_type = $mime_type;

        return $this;
    }

    #[Override]
    public function fill(array $data): ProjectComponentInterface
    {
        $this->setUrl($data['url']);
        $this->setThumbnailUrl($data['thumbnail_url']);
        $this->setMimeType($data['mime_type']);

        return $this;
    }

    /**
     * @throws Exception
     */
    #[Override]
    public function getType(): string
    {
        return ComponentType::getValue(ComponentType::IMAGE);
    }

    #[Override]
    public function getMainUrl(): ?string
    {
        return $this->url;
    }

    #[Override]
    public function getSecondaryUrl(): ?string
    {
        return $this->thumbnail_url;
    }
}
pounard commented 5 months ago

OK so @SimonMellerin's intuition was right, it's due to inheritance. I'll try to reproduce today, if I can fix it easily it will be in the next 1.2.0 release.

To be honest, we didn't test every inheritance use cases yet, I guess it's the right time to start.

@the-juju in the meantime, if you really need it, you can still bypass entity anonymization and use YAML configuration for directly anonymizing the database columns.

the-juju commented 5 months ago

No emergency on my side. I've just read an article about your bundle, and i wanted to give it a try. I'll try to dig deeper in the config to see if I can even "skip" anonymization for that tables (not needed as I wanted to anonymize users related tables)

Keep up the good work @pounard @SimonMellerin 🤜💥🤛

pounard commented 5 months ago

So far, i'm not finding any related issue or documentation. I will try to have a look directly in the code.

That's one documentation witness, as we didn't have fully implemented all kinds of doctrine inheritance support, we didn't have documented yet either. We're going to fix that!

pounard commented 5 months ago

Should be fixed in there https://github.com/makinacorpus/DbToolsBundle/pull/164 - ready for next release!

the-juju commented 5 months ago

Wonderful! Looking forward to try the next release

pounard commented 5 months ago

@the-juju Feel free to test the new release: https://github.com/makinacorpus/DbToolsBundle/releases/tag/1.2.0

the-juju commented 5 months ago

I confirm that everything went well with 1.2.0 Good job @pounard and thanks !