From a019fa047674d94c106ea470f509cadf4ee75ba6 Mon Sep 17 00:00:00 2001 From: Jan Lam Date: Thu, 10 Sep 2026 13:45:39 +0200 Subject: [PATCH 1/4] Remove annotation support --- composer.json | 2 +- src/Blamable.php | 19 ------------------- src/Listener/BlamableListener.php | 4 ---- src/Resolver/BlamableResolver.php | 11 ----------- src/Resolver/BlamableResolverInterface.php | 10 ---------- test/Listener/BlamableListenerTest.php | 19 ++++--------------- test/Resolver/BlamableResolverTest.php | 18 ++++-------------- 7 files changed, 9 insertions(+), 74 deletions(-) delete mode 100644 src/Blamable.php diff --git a/composer.json b/composer.json index aaf4e30..b567133 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "require": { "php": "^8.3", "doctrine/orm": "^2.4.0", - "hostnet/entity-tracker-component": "^2.3.0", + "hostnet/entity-tracker-component": "^3.0.0", "symfony/cache": "^6.4|^7.4" }, "require-dev": { diff --git a/src/Blamable.php b/src/Blamable.php deleted file mode 100644 index 7fdc1a1..0000000 --- a/src/Blamable.php +++ /dev/null @@ -1,19 +0,0 @@ -save($cached_item, false); } - if (null !== $this->resolver->getBlamableAnnotation($em, $entity)) { - return $this->save($cached_item, true); - } - if (null !== $this->resolver->getBlamableAttribute($em, $entity)) { return $this->save($cached_item, true); } diff --git a/src/Resolver/BlamableResolver.php b/src/Resolver/BlamableResolver.php index ca3a7c2..abbbf06 100644 --- a/src/Resolver/BlamableResolver.php +++ b/src/Resolver/BlamableResolver.php @@ -8,7 +8,6 @@ use Doctrine\ORM\EntityManagerInterface; use Hostnet\Component\EntityBlamable\Attributes\Blamable; -use Hostnet\Component\EntityBlamable\Blamable as BlamabeAnnotation; use Hostnet\Component\EntityTracker\Provider\EntityMetadataProvider; class BlamableResolver implements BlamableResolverInterface @@ -17,16 +16,6 @@ public function __construct(private EntityMetadataProvider $provider) { } - /** - * @see \Hostnet\Component\EntityBlamable\Resolver\BlamableResolverInterface::getBlamableAnnotation() - * - * @deprecated Please use the attribute instead. - */ - public function getBlamableAnnotation(EntityManagerInterface $em, $entity) - { - return $this->provider->getAnnotationFromEntity($em, $entity, BlamabeAnnotation::class); - } - public function getBlamableAttribute(EntityManagerInterface $em, $entity): ?Blamable { return $this->provider->getAttributeFromEntity(Blamable::class, $em, $entity); diff --git a/src/Resolver/BlamableResolverInterface.php b/src/Resolver/BlamableResolverInterface.php index 97898ff..db7c2a4 100644 --- a/src/Resolver/BlamableResolverInterface.php +++ b/src/Resolver/BlamableResolverInterface.php @@ -11,15 +11,5 @@ interface BlamableResolverInterface { - /** - * Return the blamable annotation - * - * @param EntityManagerInterface $em - * @param mixed $entity - * - * @deprecated Please use the attribute instead. - */ - public function getBlamableAnnotation(EntityManagerInterface $em, $entity); - public function getBlamableAttribute(EntityManagerInterface $em, $entity): ?Blamable; } diff --git a/test/Listener/BlamableListenerTest.php b/test/Listener/BlamableListenerTest.php index 0ab4afa..3563383 100644 --- a/test/Listener/BlamableListenerTest.php +++ b/test/Listener/BlamableListenerTest.php @@ -7,7 +7,6 @@ namespace Hostnet\Component\EntityBlamable\Listener; use Hostnet\Component\EntityBlamable\Attributes\Blamable; -use Hostnet\Component\EntityBlamable\Blamable as BlamableAnnotation; use Hostnet\Component\EntityTracker\Event\EntityChangedEvent; use PHPUnit\Framework\TestCase; @@ -68,8 +67,8 @@ public function testOnEntityChangedExistingEntity(): void $this->resolver ->expects($this->once()) - ->method('getBlamableAnnotation') - ->willReturn(new BlamableAnnotation()); + ->method('getBlamableAttribute') + ->willReturn(new Blamable()); $event = new EntityChangedEvent($this->em, $this->entity, new \stdClass(), []); $listener = new BlamableListener($this->resolver, $this->provider); @@ -108,8 +107,8 @@ public function testOnEntityChangedNewEntity(): void $this->resolver ->expects($this->once()) - ->method('getBlamableAnnotation') - ->willReturn(new BlamableAnnotation()); + ->method('getBlamableAttribute') + ->willReturn(new Blamable()); $event = new EntityChangedEvent($this->em, $this->entity, null, []); $listener = new BlamableListener($this->resolver, $this->provider); @@ -131,11 +130,6 @@ public function testOnEntityChangedNewEntityWithAttribute(): void ->method('getUpdatedBy') ->willReturn($by); - $this->resolver - ->expects($this->once()) - ->method('getBlamableAnnotation') - ->willReturn(null); - $this->resolver ->expects($this->once()) ->method('getBlamableAttribute') @@ -166,11 +160,6 @@ public function testOnEntityChangedNewEntityWithInterfaceOnly(): void ->expects($this->never()) ->method('getUpdatedBy'); - $this->resolver - ->expects($this->once()) - ->method('getBlamableAnnotation') - ->willReturn(null); - $this->resolver ->expects($this->once()) ->method('getBlamableAttribute') diff --git a/test/Resolver/BlamableResolverTest.php b/test/Resolver/BlamableResolverTest.php index cb03318..9508908 100644 --- a/test/Resolver/BlamableResolverTest.php +++ b/test/Resolver/BlamableResolverTest.php @@ -6,8 +6,10 @@ namespace Hostnet\Component\EntityBlamable\Resolver; +use Doctrine\ORM\EntityManagerInterface; use Hostnet\Component\EntityBlamable\Attributes\Blamable; use Hostnet\Component\EntityBlamable\Blamable as BlamableAnnotation; +use Hostnet\Component\EntityTracker\Provider\EntityMetadataProvider; use PHPUnit\Framework\TestCase; /** @@ -22,30 +24,18 @@ class BlamableResolverTest extends TestCase public function setUp(): void { $this->provider = $this - ->getMockBuilder('Hostnet\Component\EntityTracker\Provider\EntityAnnotationMetadataProvider') + ->getMockBuilder(EntityMetadataProvider::class) ->disableOriginalConstructor() ->getMock(); $this->em = $this - ->getMockBuilder('Doctrine\ORM\EntityManagerInterface') + ->getMockBuilder(EntityManagerInterface::class) ->disableOriginalConstructor() ->getMock(); $this->resolver = new BlamableResolver($this->provider); } - public function testGetBlamableAnnotation(): void - { - $entity = new \stdClass(); - - $this->provider - ->expects($this->once()) - ->method('getAnnotationFromEntity') - ->with($this->em, $entity, BlamableAnnotation::class); - - $this->resolver->getBlamableAnnotation($this->em, $entity); - } - public function testGetRevisionAttribute(): void { $entity = new \stdClass(); From 7eac3f2905c4fc92e8d4ba46519eb642a8613495 Mon Sep 17 00:00:00 2001 From: Jan Lam Date: Thu, 10 Sep 2026 13:58:35 +0200 Subject: [PATCH 2/4] Update coding conventions to PHP 8.3 and make setters fluent Add typehints to BlamableInterface and BlamableProviderInterface now that the annotation-based BC break has already happened, fix a couple of loosely-typed signatures left over from that migration, remove a dead import left behind by the annotation removal, and update the README examples to the current attribute-based API. Also turn setUpdatedBy/setUpdatedAt/setCreatedAt into fluent setters and bump the README's install example to the 2.* line. Co-Authored-By: Claude Sonnet 5 --- README.md | 92 ++++++++++------------ src/BlamableInterface.php | 17 +--- src/Listener/BlamableListener.php | 9 +-- src/Provider/BlamableProviderInterface.php | 13 +-- src/Resolver/BlamableResolver.php | 2 +- src/Resolver/BlamableResolverInterface.php | 2 +- test/Listener/BlamableListenerTest.php | 1 - test/Listener/EntityWithAttribute.php | 22 ++++-- test/Listener/EntityWithInterfaceOnly.php | 22 ++++-- test/Resolver/BlamableResolverTest.php | 1 - 10 files changed, 80 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index c2b71c9..6b74eb7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ This component lets you automatically update entities by implementing `setUpdate Requirements ------------ -The blamable component requires a minimum of php 5.4 and runs on Doctrine2. For specific requirements, please check [composer.json](../master/composer.json). +The blamable component requires a minimum of php 8.3 and runs on Doctrine2. For specific requirements, please check [composer.json](../master/composer.json). Installation ------------ @@ -32,7 +32,7 @@ Installing is pretty easy, this package is available on [packagist](https://pack ```javascript "require" : { - "hostnet/entity-blamable-component" : "1.*" + "hostnet/entity-blamable-component" : "2.*" } ``` @@ -45,14 +45,14 @@ Documentation How does it work? ----------------- -It works by putting the `@Blamable` annotation and BlamableInterface on your Entity and registering the listener on the entityChanged event, assuming you have already configured the [Entity Tracker Component](https://github.com/hostnet/entity-tracker-component/#setup). +It works by putting the `#[Blamable]` attribute and BlamableInterface on your Entity and registering the listener on the entityChanged event, assuming you have already configured the [Entity Tracker Component](https://github.com/hostnet/entity-tracker-component/#setup). For a usage example, follow the setup below. Setup ----- - - You have to add `@Blamable` to your entity + - You have to add `#[Blamable]` to your entity - You have to add the BlamableInterface to your entity - You have to implement the BlamableProviderInterface on an object and pass it to the listener @@ -72,27 +72,24 @@ use Acme\Bundle\AcmeBundle\Service\AcmeBlamableProvider; use Hostnet\Component\EntityBlamable\Listener\BlamableListener; use Hostnet\Component\EntityBlamable\Resolver\BlamableResolver; use Hostnet\Component\EntityTracker\Listener\EntityChangedListener; -use Hostnet\Component\EntityTracker\Provider\EntityAnnotationMetadataProvider; +use Hostnet\Component\EntityTracker\Provider\EntityMetadataProvider; use Hostnet\Component\EntityTracker\Provider\EntityMutationMetadataProvider; /* @var $em \Doctrine\ORM\EntityManager */ $event_manager = $em->getEventManager(); -// default doctrine annotation reader -$annotation_reader = new AnnotationReader(); - // setup required providers -$mutation_metadata_provider = new EntityMutationMetadataProvider($annotation_reader); -$annotation_metadata_provider = new EntityAnnotationMetadataProvider($annotation_reader); +$mutation_metadata_provider = new EntityMutationMetadataProvider(); +$metadata_provider = new EntityMetadataProvider(); -// pre flush event listener that uses the @Tracked/@Blamable annotation +// pre flush event listener that uses the #[Tracked]/#[Blamable] attribute $entity_changed_listener = new EntityChangedListener( - $mutation_metadata_provider, - $annotation_metadata_provider + $metadata_provider, + $mutation_metadata_provider ); -// the resolver is used to find the correct annotation -$blamable_resolver = new BlamableResolver($annotation_metadata_provider); +// the resolver is used to find the correct attribute +$blamable_resolver = new BlamableResolver($metadata_provider); // the object that will provide the username and date time. This is an // application specific implementation of the BlamableProviderInterface @@ -120,21 +117,18 @@ use Hostnet\Component\EntityBlamable\Provider\BlamableProviderInterface; class AcmeBlamableProvider implements BlamableProviderInterface { - private $username; - - public function __construct($username) + public function __construct(private string $username) { - $this->username = $username; } - public function getUpdatedBy() + public function getUpdatedBy(): string { return $this->username; } - - public function getChangedAt() + + public function getChangedAt(): \DateTimeInterface { - return new \DateTime(); + return new \DateTimeImmutable(); } } @@ -142,48 +136,46 @@ class AcmeBlamableProvider implements BlamableProviderInterface ``` #### Configuring the Entity -All we have to do now is put the `@Blamable` annotation and BlamableInterface on our Entity. +All we have to do now is put the `#[Blamable]` attribute and BlamableInterface on our Entity. ```php use Doctrine\ORM\Mapping as ORM; -use Hostnet\Component\EntityBlamable\Blamable; +use Hostnet\Component\EntityBlamable\Attributes\Blamable; use Hostnet\Component\EntityBlamable\BlamableInterface; -/** - * @ORM\Entity - * @Blamable - */ +#[ORM\Entity] +#[Blamable] class MyEntity implements BlamableInterface { - /** - * @ORM\... - */ - private $updated_by; - - /** - * @ORM\... - */ - private $updated_at; - - /** - * @ORM\... - */ - private $created_at; - - public function setUpdatedBy($by) + #[ORM\Column(nullable: true)] + private ?string $updated_by = null; + + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeInterface $updated_at = null; + + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeInterface $created_at = null; + + public function setUpdatedBy(string $by): static { $this->updated_by = $by; + + return $this; } - public function setUpdatedAt(\DateTime $at) + public function setUpdatedAt(\DateTimeInterface $at): static { - $this->changed_at = $at; + $this->updated_at = $at; + + return $this; } - - public function setCreatedAt(\DateTime $at) + + public function setCreatedAt(\DateTimeInterface $at): static { - $this->created_at = $at + $this->created_at = $at; + + return $this; } } diff --git a/src/BlamableInterface.php b/src/BlamableInterface.php index b119aa7..99a063f 100644 --- a/src/BlamableInterface.php +++ b/src/BlamableInterface.php @@ -8,23 +8,12 @@ /** * Implement on Entities to trigger the BlamableEntityListener - * - * @TODO: add (return)typehints on next BC break, when removing doctrine/annotations */ interface BlamableInterface { - /** - * @param string $by - */ - public function setUpdatedBy($by); + public function setUpdatedBy(string $by): static; - /** - * @param \DateTime $at - */ - public function setUpdatedAt(\DateTime $at); + public function setUpdatedAt(\DateTimeInterface $at): static; - /** - * @param \DateTime $at - */ - public function setCreatedAt(\DateTime $at); + public function setCreatedAt(\DateTimeInterface $at): static; } diff --git a/src/Listener/BlamableListener.php b/src/Listener/BlamableListener.php index e4c4dd5..670946b 100644 --- a/src/Listener/BlamableListener.php +++ b/src/Listener/BlamableListener.php @@ -6,7 +6,7 @@ namespace Hostnet\Component\EntityBlamable\Listener; -use Doctrine\Persistence\ObjectManager; +use Doctrine\ORM\EntityManagerInterface; use Hostnet\Component\EntityBlamable\BlamableInterface; use Hostnet\Component\EntityBlamable\Provider\BlamableProviderInterface; use Hostnet\Component\EntityBlamable\Resolver\BlamableResolverInterface; @@ -19,7 +19,7 @@ * Listens to "Events::entityChanged" * * Attempts to set updated at, created at, updated by and updated at fields - * in an entity using @Blamable and implementing the BlamableInterface + * in an entity using #[Blamable] and implementing the BlamableInterface */ class BlamableListener { @@ -30,9 +30,6 @@ public function __construct( ) { } - /** - * @param EntityChangedEvent $event - */ public function entityChanged(EntityChangedEvent $event): void { $entity = $event->getCurrentEntity(); @@ -53,7 +50,7 @@ public function entityChanged(EntityChangedEvent $event): void } } - private function isBlamable(ObjectManager $em, mixed $entity): bool + private function isBlamable(EntityManagerInterface $em, object $entity): bool { $cache_key = base64_encode('BLAMABLE-' . get_class($entity)); $cached_item = $this->is_blamable_cache->getItem($cache_key); diff --git a/src/Provider/BlamableProviderInterface.php b/src/Provider/BlamableProviderInterface.php index 987c486..9392880 100644 --- a/src/Provider/BlamableProviderInterface.php +++ b/src/Provider/BlamableProviderInterface.php @@ -6,18 +6,9 @@ namespace Hostnet\Component\EntityBlamable\Provider; -/** - * @TODO: add (return)typehints on next BC break, when removing doctrine/annotations - */ interface BlamableProviderInterface { - /** - * @return string - */ - public function getUpdatedBy(); + public function getUpdatedBy(): string; - /** - * @return \DateTime - */ - public function getChangedAt(); + public function getChangedAt(): \DateTimeInterface; } diff --git a/src/Resolver/BlamableResolver.php b/src/Resolver/BlamableResolver.php index abbbf06..2f10c80 100644 --- a/src/Resolver/BlamableResolver.php +++ b/src/Resolver/BlamableResolver.php @@ -16,7 +16,7 @@ public function __construct(private EntityMetadataProvider $provider) { } - public function getBlamableAttribute(EntityManagerInterface $em, $entity): ?Blamable + public function getBlamableAttribute(EntityManagerInterface $em, object $entity): ?Blamable { return $this->provider->getAttributeFromEntity(Blamable::class, $em, $entity); } diff --git a/src/Resolver/BlamableResolverInterface.php b/src/Resolver/BlamableResolverInterface.php index db7c2a4..90c1cdf 100644 --- a/src/Resolver/BlamableResolverInterface.php +++ b/src/Resolver/BlamableResolverInterface.php @@ -11,5 +11,5 @@ interface BlamableResolverInterface { - public function getBlamableAttribute(EntityManagerInterface $em, $entity): ?Blamable; + public function getBlamableAttribute(EntityManagerInterface $em, object $entity): ?Blamable; } diff --git a/test/Listener/BlamableListenerTest.php b/test/Listener/BlamableListenerTest.php index 3563383..1d6b9f2 100644 --- a/test/Listener/BlamableListenerTest.php +++ b/test/Listener/BlamableListenerTest.php @@ -19,7 +19,6 @@ class BlamableListenerTest extends TestCase private $resolver; private $provider; private $entity; - private $uow; public function setUp(): void { diff --git a/test/Listener/EntityWithAttribute.php b/test/Listener/EntityWithAttribute.php index a14aa75..c7138ab 100644 --- a/test/Listener/EntityWithAttribute.php +++ b/test/Listener/EntityWithAttribute.php @@ -12,14 +12,16 @@ #[Blamable] class EntityWithAttribute implements BlamableInterface { - private ?\DateTime $created_at = null; + private ?\DateTimeInterface $created_at = null; - private ?string $updated_by = null; - private ?\DateTime $updated_at = null; + private ?string $updated_by = null; + private ?\DateTimeInterface $updated_at = null; - public function setUpdatedBy($by): void + public function setUpdatedBy(string $by): static { $this->updated_by = $by; + + return $this; } public function getUpdatedBy(): ?string @@ -27,22 +29,26 @@ public function getUpdatedBy(): ?string return $this->updated_by; } - public function setUpdatedAt(\DateTime $at): void + public function setUpdatedAt(\DateTimeInterface $at): static { $this->updated_at = $at; + + return $this; } - public function getUpdatedAt(): ?\DateTime + public function getUpdatedAt(): ?\DateTimeInterface { return $this->updated_at; } - public function setCreatedAt(\DateTime $at): void + public function setCreatedAt(\DateTimeInterface $at): static { $this->created_at = $at; + + return $this; } - public function getCreatedAt(): ?\DateTime + public function getCreatedAt(): ?\DateTimeInterface { return $this->created_at; } diff --git a/test/Listener/EntityWithInterfaceOnly.php b/test/Listener/EntityWithInterfaceOnly.php index 5fc50cd..6dc9e5b 100644 --- a/test/Listener/EntityWithInterfaceOnly.php +++ b/test/Listener/EntityWithInterfaceOnly.php @@ -10,14 +10,16 @@ class EntityWithInterfaceOnly implements BlamableInterface { - private ?\DateTime $created_at = null; + private ?\DateTimeInterface $created_at = null; - private ?string $updated_by = null; - private ?\DateTime $updated_at = null; + private ?string $updated_by = null; + private ?\DateTimeInterface $updated_at = null; - public function setUpdatedBy($by): void + public function setUpdatedBy(string $by): static { $this->updated_by = $by; + + return $this; } public function getUpdatedBy(): ?string @@ -25,22 +27,26 @@ public function getUpdatedBy(): ?string return $this->updated_by; } - public function setUpdatedAt(\DateTime $at): void + public function setUpdatedAt(\DateTimeInterface $at): static { $this->updated_at = $at; + + return $this; } - public function getUpdatedAt(): ?\DateTime + public function getUpdatedAt(): ?\DateTimeInterface { return $this->updated_at; } - public function setCreatedAt(\DateTime $at): void + public function setCreatedAt(\DateTimeInterface $at): static { $this->created_at = $at; + + return $this; } - public function getCreatedAt(): ?\DateTime + public function getCreatedAt(): ?\DateTimeInterface { return $this->created_at; } diff --git a/test/Resolver/BlamableResolverTest.php b/test/Resolver/BlamableResolverTest.php index 9508908..08c3f73 100644 --- a/test/Resolver/BlamableResolverTest.php +++ b/test/Resolver/BlamableResolverTest.php @@ -8,7 +8,6 @@ use Doctrine\ORM\EntityManagerInterface; use Hostnet\Component\EntityBlamable\Attributes\Blamable; -use Hostnet\Component\EntityBlamable\Blamable as BlamableAnnotation; use Hostnet\Component\EntityTracker\Provider\EntityMetadataProvider; use PHPUnit\Framework\TestCase; From 332b4ec8f051e7046abff13e6a20e80f047df456 Mon Sep 17 00:00:00 2001 From: Jan Lam Date: Thu, 10 Sep 2026 14:18:00 +0200 Subject: [PATCH 3/4] Modernize CI checkout action and use ::class instead of get_class() actions/checkout@v2 runs on a deprecated Node.js runtime; bump to v4 to match actions/cache@v4 already in the same workflow. Also swap get_class($entity) for $entity::class now that $entity is typed object. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/main.yaml | 2 +- src/Listener/BlamableListener.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index b7896e1..19d3d82 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -15,7 +15,7 @@ jobs: php-versions: ['8.3', '8.4'] name: PHP ${{ matrix.php-versions }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} diff --git a/src/Listener/BlamableListener.php b/src/Listener/BlamableListener.php index 670946b..e73a744 100644 --- a/src/Listener/BlamableListener.php +++ b/src/Listener/BlamableListener.php @@ -52,7 +52,7 @@ public function entityChanged(EntityChangedEvent $event): void private function isBlamable(EntityManagerInterface $em, object $entity): bool { - $cache_key = base64_encode('BLAMABLE-' . get_class($entity)); + $cache_key = base64_encode('BLAMABLE-' . $entity::class); $cached_item = $this->is_blamable_cache->getItem($cache_key); if ($cached_item->isHit()) { From 3614aff3bab6f2d23c12191580ed53b43b81c344 Mon Sep 17 00:00:00 2001 From: Jan Lam Date: Mon, 14 Sep 2026 09:22:10 +0200 Subject: [PATCH 4/4] Exclude phpcs config from deploying --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index d41c778..d9f4a3b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,4 @@ .gitignore export-ignore /test export-ignore /phpunit.xml.dist export-ignore +/phpcs.xml.dist export-ignore