From 36855040bd1488e8f6c30ac80383d8aed31a986b Mon Sep 17 00:00:00 2001 From: Jano Paetzold Date: Thu, 30 Jul 2026 14:03:56 +0200 Subject: [PATCH] Doctrine 3 preparation: Mark abstract entity classes as MappedSuperclass; use resolve_target_entities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newsletter, Recipient and PendingOptIn are abstract classes with property-level ORM attributes but no class-level mapping. Doctrine only picked the mappings up implicitly through reflection of the concrete subclasses — undocumented behaviour that Doctrine ORM 3 no longer guarantees. This adds #[ORM\MappedSuperclass] so each base owns its mapping, including the $newsletters association, and let the concrete subclasses inherit it. Binds the NewsletterInterface target to the concrete class through resolve_target_entities instead of redeclaring the association in the test dummies — the same mechanism downstream implementors use (and the shape the tests/Fixtures/App classes already have). Drops the @ ORM\Entity pseudo-docblock templates and update the README to match. Note: removing the Dummy\Recipient override reverts its newsletter join to the abstract Recipient's id-based (primary key) column. The Dummy referenced uuid instead; that non-PK case is not needed, so we let it inherit the base. No test depends on it. --- README.md | 6 +++--- src/Entity/Newsletter.php | 5 +---- src/Entity/PendingOptIn.php | 10 +--------- src/Entity/Recipient.php | 11 +---------- tests/Entity/Dummy/PendingOptIn.php | 10 ---------- tests/Entity/Dummy/Recipient.php | 10 ---------- tests/Fixtures/config/doctrine.php | 5 +++++ 7 files changed, 11 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 4908f65..1b32fa6 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,9 @@ to copy the templates: mkdir src/AppBundle/Newsletter cp vendor/webfactory/newsletter-registration-bundle/app-class-templates/* src/AppBundle/Newsletter/* -If you want to implement the interfaces by yourself, you could extend the corresponding abstract classes like in the -templates above and add class level Doctrine ORM annotations (find template for them in the abstract classes). For -customizing, see the "Customizing" section below. +If you want to implement the interfaces by yourself, extend the corresponding abstract classes and add +`#[ORM\Entity(repositoryClass: MyRepo::class)]` (and any desired `#[ORM\Table(...)]` constraints) on your concrete +subclass. For customizing, see the "Customizing" section below. In either case, configure Doctrine's interface mapping to deal with your custom entity class: diff --git a/src/Entity/Newsletter.php b/src/Entity/Newsletter.php index 2f0dac4..4d40f62 100644 --- a/src/Entity/Newsletter.php +++ b/src/Entity/Newsletter.php @@ -4,10 +4,7 @@ use Doctrine\ORM\Mapping as ORM; -/** - * @ ORM\Entity(repositoryClass="\Webfactory\NewsletterRegistrationBundle\Entity\NewsletterRepositoryInterface") - * @ ORM\Table("wfd_newsletterNewsletter") - */ +#[ORM\MappedSuperclass] abstract class Newsletter implements NewsletterInterface { #[ORM\Id] diff --git a/src/Entity/PendingOptIn.php b/src/Entity/PendingOptIn.php index 7bf4c6d..bab9cdc 100644 --- a/src/Entity/PendingOptIn.php +++ b/src/Entity/PendingOptIn.php @@ -11,15 +11,7 @@ use Webfactory\NewsletterRegistrationBundle\Exception\EmailAddressDoesNotMatchHashOfPendingOptInException; use Webfactory\NewsletterRegistrationBundle\StartRegistration\Type as StartRegistrationType; -/** - * @ ORM\Entity(repositoryClass="PendingOptInRepository") - * @ ORM\Table( - * uniqueConstraints={ - * @ ORM\UniqueConstraint(columns={"emailAddressHash"}), - * @ ORM\UniqueConstraint(columns={"uuid"}), - * } - * ) - */ +#[ORM\MappedSuperclass] abstract class PendingOptIn implements PendingOptInInterface { #[ORM\Id] diff --git a/src/Entity/Recipient.php b/src/Entity/Recipient.php index 2ba8fa1..c90c727 100644 --- a/src/Entity/Recipient.php +++ b/src/Entity/Recipient.php @@ -8,16 +8,7 @@ use Doctrine\ORM\Mapping as ORM; use Ramsey\Uuid\Uuid; -/** - * @ ORM\Entity() - * @ ORM\Table( - * name="wfd_newsletterRecipient", - * uniqueConstraints={ - * @ ORM\UniqueConstraint(columns={"email"}), - * @ ORM\UniqueConstraint(columns={"uuid"}), - * } - * ) - */ +#[ORM\MappedSuperclass] abstract class Recipient implements RecipientInterface { /** diff --git a/tests/Entity/Dummy/PendingOptIn.php b/tests/Entity/Dummy/PendingOptIn.php index 8bf8c1a..00bfcb7 100644 --- a/tests/Entity/Dummy/PendingOptIn.php +++ b/tests/Entity/Dummy/PendingOptIn.php @@ -2,19 +2,9 @@ namespace Webfactory\NewsletterRegistrationBundle\Tests\Entity\Dummy; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: PendingOptInRepository::class)] class PendingOptIn extends \Webfactory\NewsletterRegistrationBundle\Entity\PendingOptIn { - /** - * @var Collection - */ - #[ORM\ManyToMany(targetEntity: Newsletter::class)] - #[ORM\JoinTable( - joinColumns: [new ORM\JoinColumn(referencedColumnName: 'uuid', onDelete: 'CASCADE')], - inverseJoinColumns: [new ORM\JoinColumn(onDelete: 'CASCADE')] - )] - protected Collection $newsletters; } diff --git a/tests/Entity/Dummy/Recipient.php b/tests/Entity/Dummy/Recipient.php index d096cd9..0d853d1 100644 --- a/tests/Entity/Dummy/Recipient.php +++ b/tests/Entity/Dummy/Recipient.php @@ -2,19 +2,9 @@ namespace Webfactory\NewsletterRegistrationBundle\Tests\Entity\Dummy; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: RecipientRepository::class)] class Recipient extends \Webfactory\NewsletterRegistrationBundle\Entity\Recipient { - /** - * @var Collection - */ - #[ORM\ManyToMany(targetEntity: Newsletter::class)] - #[ORM\JoinTable( - joinColumns: [new ORM\JoinColumn(referencedColumnName: 'uuid', onDelete: 'CASCADE')], - inverseJoinColumns: [new ORM\JoinColumn(onDelete: 'CASCADE')] - )] - protected Collection $newsletters; } diff --git a/tests/Fixtures/config/doctrine.php b/tests/Fixtures/config/doctrine.php index 040bafc..ee9ec20 100644 --- a/tests/Fixtures/config/doctrine.php +++ b/tests/Fixtures/config/doctrine.php @@ -1,6 +1,8 @@ extension('doctrine', [ @@ -10,6 +12,9 @@ ], 'orm' => [ 'auto_generate_proxy_classes' => true, + 'resolve_target_entities' => [ + NewsletterInterface::class => Newsletter::class, + ], 'mappings' => [ 'BundleEntities' => [ 'is_bundle' => false,