diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 0aff590..4e10169 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/README.md b/README.md index 61a58db..2cfea66 100644 --- a/README.md +++ b/README.md @@ -11,24 +11,24 @@ README - [Registering the Events](#registering-the-events) - [Creating the Listener](#creating-the-listener) - [Creating an Interface for the Entity](#creating-an-interface-for-the-entity) - - [Registering the Annotation on the Entity](#registering-the-annotation-on-the-entity) + - [Registering the Attribute on the Entity](#registering-the-attribute-on-the-entity) - [What's Next?](#whats-next) - - [Extending the Tracker Annotation](#extending-the-tracker-annotation) - - [Example Annotation](#example-annotation) - - [Custom Annotation Resolvers](#custom-annotation-resolvers) + - [Extending the Tracker Attribute](#extending-the-tracker-attribute) + - [Example Attribute](#example-attribute) + - [Custom Attribute Resolvers](#custom-attribute-resolvers) - [Custom entityChanged Listener](#custom-entitychanged-listener) What is the Entity Tracker? --------------------------- The Entity Tracker Component is a library used to track changes within an Entity during a flush of the EntityManager. This makes it possible to do all sorts of things you want to automated during the `preFlush` event. -Entities become tracked when you implement the `@Tracked` annotation or a sub-class of `@Tracked`. You have total control over what happens next and which events you will use to listen to the `entityChanged` event. +Entities become tracked when you implement the `#[Tracked]` attribute or a sub-class of `Tracked`. You have total control over what happens next and which events you will use to listen to the `entityChanged` event. Let's say that every time you flush your User, you want to set when it was updated. By default, you would have to call `$user->setUpdatedAt()` manually or create a custom listener on preFlush that sets the updated at timestamp. Both are a lot of extra work and you have to write extra code to determine changes. Listening to preFlush will always trigger your listener and you don't want to make a huge if statement nor create a listener for each Entity. Requirements ------------ -The tracker component requires at least php 5.4 and runs on Doctrine2. For specific requirements, please check [composer.json](../master/composer.json) +The tracker component requires at least PHP 8.3 and runs on Doctrine2. For specific requirements, please check [composer.json](../master/composer.json) Installation ------------ @@ -52,7 +52,7 @@ Documentation How does it work? ----------------- -It works by putting an annotation on your Entity and registering your listener on our event, assuming you have already registered our event to doctrine. That's all you need to do to start tracking the Entity so it will be available in the `entityChanged` event. +It works by putting an attribute on your Entity and registering your listener on our event, assuming you have already registered our event to doctrine. That's all you need to do to start tracking the Entity so it will be available in the `entityChanged` event. Setup ----- @@ -67,23 +67,20 @@ Here's an example of a very basic setup. Setting this up will be a lot easier if use Acme\Component\Listener\ChangedAtListener; 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); +$meta_provider = new EntityMetadataProvider(); +$mutation_metadata_provider = new EntityMutationMetadataProvider(); -// pre flush event listener that uses the @Tracked annotation +// pre flush event listener that uses the Tracked attribute $entity_changed_listener = new EntityChangedListener( - $mutation_metadata_provider, - $annotation_metadata_provider + $meta_provider, + $mutation_metadata_provider ); // our example listener @@ -91,7 +88,6 @@ $listener = new ChangedAtListener(new DateTime()); // register the events $event_manager->addEventListener('preFlush', $entity_changed_listener); -$event_manager->addEventListener('prePersist', $entity_changed_listener); $event_manager->addEventListener('entityChanged', $listener); ``` @@ -131,7 +127,7 @@ class ChangedAtListener ``` #### Creating an Interface for the Entity -Additionally to the `@Tracked` annotation, we want to determine if we can set and updated_at field within our Entity. This can be done by creating the following interface for our Entity. +Additionally to the `#[Tracked]` attribute, we want to determine if we can set and updated_at field within our Entity. This can be done by creating the following interface for our Entity. ```php @@ -145,24 +141,20 @@ interface UpdatableInterface ``` -#### Registering the Annotation on the Entity -All we have to do now is put the `@Tracked` annotation and Interface on our Entity and implement the required method +#### Registering the Attribute on the Entity +All we have to do now is put the `#[Tracked]` attribute and Interface on our Entity and implement the required method ```php use Acme\Component\Listener\UpdatableInterface; use Doctrine\ORM\Mapping as ORM; -use Hostnet\Component\EntityTracker\Annotation\Tracked; +use Hostnet\Component\EntityTracker\Attributes\Tracked; -/** - * @ORM\Entity - * @Tracked - */ +#[ORM\Entity] +#[Tracked] class MyEntity implements UpdatableInterface { - /** - * @ORM\... - */ + #[ORM\Column] private $changed_at; public function setUpdatedAt(\DateTime $now) @@ -184,25 +176,21 @@ $em->flush(); ``` -### Extending the Tracker Annotation -You might want to extend the `@Tracker` annotation. This allows you to add options and additional checks within your listener. +### Extending the Tracker Attribute +You might want to extend the `Tracked` attribute. This allows you to add options and additional checks within your listener. -#### Example Annotation -In the following example, you will see how using a creating a custom annotation works. - - You have to add `@Annotation` - - You have to add `@Target({"CLASS"})` - - It has to extend `Hostnet\Component\EntityTracker\Annotation\Tracked` +#### Example Attribute +In the following example, you will see how creating a custom attribute works. + - You have to add `#[\Attribute(\Attribute::TARGET_CLASS)]` + - It has to extend `Hostnet\Component\EntityTracker\Attributes\Tracked` -Using this annotation will give us specific access to options within our listener. We can now attempt to get this annotation in the listener and we get can call `getIgnoredFields()`. This example will ignore certain fields for entities using the annotation. +Using this attribute will give us specific access to options within our listener. We can now attempt to get this attribute in the listener and we get can call `getIgnoredFields()`. This example will ignore certain fields for entities using the attribute. ```php -use Hostnet\Component\EntityTracker\Annotation\Tracked; +use Hostnet\Component\EntityTracker\Attributes\Tracked; -/** - * @Annotation - * @Target({"CLASS"}) - */ +#[\Attribute(\Attribute::TARGET_CLASS)] class Changed extends Tracked { public $ignore_fields = []; @@ -213,34 +201,34 @@ class Changed extends Tracked return ['id']; } - return $ignore_fields; + return $this->ignore_fields; } } ``` -#### Custom Annotation Resolvers -To obtain the Annotation, we have implemented resolvers. The example below shows how you could implement it yourself. +#### Custom Attribute Resolvers +To obtain the attribute, we have implemented resolvers. The example below shows how you could implement it yourself. ```php use Doctrine\ORM\EntityManagerInterface; -use Hostnet\Component\EntityTracker\Provider\EntityAnnotationMetadataProvider; +use Hostnet\Component\EntityTracker\Provider\EntityMetadataProvider; class ChangedResolver { - private $annotation = 'Changed'; + private $attribute_class = Changed::class; private $provider; - public function __construct(EntityAnnotationMetadataProvider $provider) + public function __construct(EntityMetadataProvider $provider) { $this->provider = $provider; } - public function getChangedAnnotation(EntityManagerInterface $em, $entity) + public function getChangedAttribute(EntityManagerInterface $em, $entity) { - return $this->provider->getAnnotationFromEntity($em, $entity, $this->annotation); + return $this->provider->getAttributeFromEntity($this->attribute_class, $em, $entity); } } @@ -248,7 +236,7 @@ class ChangedResolver #### Custom entityChanged Listener -The listener can now use the resolver to obtain the annotation so possible do something extra when a specific set of fields is changed. +The listener can now use the resolver to obtain the attribute so possible do something extra when a specific set of fields is changed. ```php @@ -268,11 +256,11 @@ class ChangedListener $em = $event->getEntityManager(); $entity = $event->getCurrentEntity(); - if (null === ($annotation = $this->resolver->getChangedAnnotation($em, $entity))) { + if (null === ($attribute = $this->resolver->getChangedAttribute($em, $entity))) { return; } - $preferred_changes = array_diff($annotation->getIgnoredFields(), $event->getMutatedFields()); + $preferred_changes = array_diff($attribute->getIgnoredFields(), $event->getMutatedFields()); // do something with them } diff --git a/composer.json b/composer.json index 4739881..1c049be 100644 --- a/composer.json +++ b/composer.json @@ -4,13 +4,13 @@ "license": "MIT", "minimum-stability": "stable", "require": { - "php": "^8.3", - "doctrine/annotations": "1.*,>=1.2.0", - "doctrine/common": "^3.4.4", - "doctrine/orm": "^2.19.7", - "doctrine/persistence": "^2.5.7|^3.4.0", - "psr/log": "^1.0.0|^2.0.0|^3.0.0", - "symfony/cache": "^6.4|^7.4" + "php": "^8.3", + "doctrine/common": "^3.4.4", + "doctrine/event-manager": "^1.2|^2.0", + "doctrine/orm": "^2.19.7", + "doctrine/persistence": "^2.5.7|^3.4.0", + "psr/log": "^1.0.0|^2.0.0|^3.0.0", + "symfony/cache": "^6.4|^7.4" }, "require-dev": { "hostnet/database-test-lib": "^2.0.2", diff --git a/src/Annotation/Tracked.php b/src/Annotation/Tracked.php deleted file mode 100644 index aad7dee..0000000 --- a/src/Annotation/Tracked.php +++ /dev/null @@ -1,19 +0,0 @@ -em = $em; - $this->current_entity = $current_entity; - $this->original_entity = $original_entity; - $this->mutated_fields = $mutated_fields; } - /** - * @return EntityManagerInterface - */ - public function getEntityManager() + public function getEntityManager(): EntityManagerInterface { return $this->em; } /** * This entity is not managed! - * - * @return mixed */ - public function getOriginalEntity() + public function getOriginalEntity(): ?object { return $this->original_entity; } @@ -70,10 +38,8 @@ public function getOriginalEntity() /** * The current state of the entity, the version * that is persisted and ready to be flushed - * - * @return mixed */ - public function getCurrentEntity() + public function getCurrentEntity(): object { return $this->current_entity; } @@ -81,7 +47,7 @@ public function getCurrentEntity() /** * @return string[] */ - public function getMutatedFields() + public function getMutatedFields(): array { return $this->mutated_fields; } diff --git a/src/Events.php b/src/Events.php index 4d57645..4c55a51 100644 --- a/src/Events.php +++ b/src/Events.php @@ -8,15 +8,8 @@ final class Events { - //@codingStandardsIgnoreStart /** - * @deprecated use Events::ENTITY_CHANGED instead. - */ - const entityChanged = self::ENTITY_CHANGED; - //@codingStandardsIgnoreEnd - - /** - * Thrown when @Tracked (or derived) annotations are found on the entity + * Thrown when the Tracked attribute (or a derived attribute) is found on the entity * * @var string */ diff --git a/src/Listener/EntityChangedListener.php b/src/Listener/EntityChangedListener.php index 0118624..5270338 100644 --- a/src/Listener/EntityChangedListener.php +++ b/src/Listener/EntityChangedListener.php @@ -6,10 +6,9 @@ namespace Hostnet\Component\EntityTracker\Listener; -use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Event\PreFlushEventArgs; -use Doctrine\ORM\Proxy\Proxy; use Doctrine\Persistence\ObjectManager; +use Doctrine\Persistence\Proxy; use Hostnet\Component\EntityTracker\Attributes\Tracked; use Hostnet\Component\EntityTracker\Event\EntityChangedEvent; use Hostnet\Component\EntityTracker\Events; @@ -22,7 +21,7 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter; /** - * Listener for entities that use the Tracked Annotation or attribute. + * Listener for entities that use the Tracked attribute. * * This listener will fire an "Events::ENTITY_CHANGED" event * per entity that is changed. @@ -30,7 +29,7 @@ class EntityChangedListener { public function __construct( - private EntityMetadataProvider $meta_annotation_provider, + private EntityMetadataProvider $meta_provider, private EntityMutationMetadataProvider $meta_mutation_provider, private ?LoggerInterface $logger = null, private CacheItemPoolInterface $is_tracked_cache = new ArrayAdapter() @@ -38,7 +37,7 @@ public function __construct( $this->logger = $logger ? : new NullLogger(); } - private function isTracked(ObjectManager $em, mixed $entity): bool + private function isTracked(ObjectManager $em, object $entity): bool { $cache_key = base64_encode('TRACKED-' . get_class($entity)); $cached_item = $this->is_tracked_cache->getItem($cache_key); @@ -47,11 +46,7 @@ private function isTracked(ObjectManager $em, mixed $entity): bool return $cached_item->get(); } - if (null !== $this->meta_annotation_provider->getAttributeFromEntity(Tracked::class, $em, $entity)) { - return $this->save($cached_item, true); - } - - if ($this->meta_annotation_provider->isTracked($em, $entity)) { + if (null !== $this->meta_provider->getAttributeFromEntity(Tracked::class, $em, $entity)) { return $this->save($cached_item, true); } @@ -69,8 +64,8 @@ private function save(CacheItemInterface $item, bool $value): bool /** * Pre Flush event callback * - * Checks if the entity contains an @Tracked (or derived) - * annotation or attribute. If so, it will attempt to calculate changes + * Checks if the entity contains a Tracked (or derived) + * attribute. If so, it will attempt to calculate changes * made and dispatch 'Events::ENTITY_CHANGED' with the current * and original entity states. Note that the original entity * is not managed. @@ -115,11 +110,4 @@ public function preFlush(PreFlushEventArgs $event): void } } } - - /** - * @deprecated Will be removed when removing doctrine/annotations, will break entity-tracker-bundle otherwise. - */ - public function prePersist(LifecycleEventArgs $event): void - { - } } diff --git a/src/Provider/EntityAnnotationMetadataProvider.php b/src/Provider/EntityAnnotationMetadataProvider.php deleted file mode 100644 index 70e49e4..0000000 --- a/src/Provider/EntityAnnotationMetadataProvider.php +++ /dev/null @@ -1,14 +0,0 @@ - $attribute_class */ - private $reader; - - /** - * @param Reader $reader - */ - public function __construct(Reader $reader) - { - $this->reader = $reader; - } - - /** - * @param mixed $entity - * - * @deprecated Please use the Tracked attribute instead - */ - public function isTracked(EntityManagerInterface $em, $entity): bool - { - $class = get_class($entity); - $annotations = $this->reader->getClassAnnotations($em->getClassMetadata($class)->getReflectionClass()); - - foreach ($annotations as $annotation) { - if ($annotation instanceof TrackedAnnotation) { - return true; - } - } - - return false; - } - - /** - * @param EntityManagerInterface $em - * @param mixed $entity - * @param string $annotation - * - * @deprecated Please use the Tracked attribute instead - */ - public function getAnnotationFromEntity(EntityManagerInterface $em, $entity, $annotation): mixed - { - return $this->reader->getClassAnnotation( - $em->getClassMetadata(get_class($entity))->getReflectionClass(), - $annotation - ); - } - - public function getAttributeFromEntity(string $attribute_class, EntityManagerInterface $em, mixed $entity): ?Tracked - { + public function getAttributeFromEntity( + string $attribute_class, + EntityManagerInterface $em, + object $entity + ): ?Tracked { $class = get_class($entity); if ($entity instanceof Proxy) { $class = $em->getClassMetadata($class)->getName(); diff --git a/src/Provider/EntityMutationMetadataProvider.php b/src/Provider/EntityMutationMetadataProvider.php index e420501..aa75949 100644 --- a/src/Provider/EntityMutationMetadataProvider.php +++ b/src/Provider/EntityMutationMetadataProvider.php @@ -6,7 +6,6 @@ namespace Hostnet\Component\EntityTracker\Provider; -use Doctrine\Common\Annotations\Reader; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; @@ -14,31 +13,18 @@ use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\UnitOfWork; use Psr\Log\LoggerInterface; -use Psr\Log\NullLogger; class EntityMutationMetadataProvider { - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @param LoggerInterface $logger - */ - public function __construct(Reader $unused, LoggerInterface $logger = null) + public function __construct(private ?LoggerInterface $logger = null) { - $this->logger = $logger ?: new NullLogger(); } /** * Create an entity based on the original $entity_meta and * hydrate it with the original data. - * - * @param EntityManagerInterface $em - * @param mixed $entity */ - public function createOriginalEntity(EntityManagerInterface $em, $entity): mixed + public function createOriginalEntity(EntityManagerInterface $em, object $entity): ?object { $uow = $em->getUnitOfWork(); $id_data = $uow->isInIdentityMap($entity) ? $uow->getEntityIdentifier($entity) : []; @@ -70,13 +56,10 @@ public function createOriginalEntity(EntityManagerInterface $em, $entity): mixed /** * Return the field names of fields that had their value/relation changed * - * @param EntityManagerInterface $em - * @param mixed $entity - * @param mixed $original * @return string[] * @throws \InvalidArgumentException */ - public function getMutatedFields(EntityManagerInterface $em, $entity, $original): array + public function getMutatedFields(EntityManagerInterface $em, object $entity, ?object $original): array { $mutation_data = []; /** @var \Doctrine\ORM\Mapping\ClassMetadata $metadata */ @@ -96,7 +79,7 @@ public function getMutatedFields(EntityManagerInterface $em, $entity, $original) } // New entity, everything changed. - if ($entity !== null && $original === null) { + if ($original === null) { return array_merge($fields, $associations); } @@ -126,12 +109,7 @@ public function getMutatedFields(EntityManagerInterface $em, $entity, $original) return $mutation_data; } - /** - * @param ClassMetadata $association_meta - * @param string $left - * @param string $right - */ - private function hasAssociationChanged(ClassMetadata $association_meta, $left, $right): bool + private function hasAssociationChanged(ClassMetadata $association_meta, ?object $left, ?object $right): bool { // check if the PK of the related entity has changed (thus different link) if (null !== $left && null !== $right) { @@ -152,7 +130,7 @@ function ($a, $b) { } ); if (!empty($diff)) { - $this->logger->info( + $this->logger?->info( 'Association Change detected on owning ONE side', [ 'left' => $left_values, @@ -167,18 +145,11 @@ function ($a, $b) { return $left != $right; } - /** - * @param EntityManagerInterface $em - * @param mixed $entity - */ - public function isEntityManaged(EntityManagerInterface $em, $entity): bool + public function isEntityManaged(EntityManagerInterface $em, object $entity): bool { return $em->getUnitOfWork()->getEntityState($entity) === UnitOfWork::STATE_MANAGED; } - /** - * @param EntityManagerInterface $em - */ public function getFullChangeSet(EntityManagerInterface $em): array { $change_set = []; @@ -206,16 +177,11 @@ public function getFullChangeSet(EntityManagerInterface $em): array /** * Add an entity to the change set. This also adds any elements to the * change set that are in the associations. - * - * @param EntityManagerInterface $em - * @param ClassMetadata $metadata - * @param mixed $entity - * @param array $change_set */ private function addToChangeSet( EntityManagerInterface $em, ClassMetadata $metadata, - $entity, + object $entity, array &$change_set ): void { if (!isset($change_set[$metadata->rootEntityName])) { @@ -230,16 +196,10 @@ private function addToChangeSet( } } - /** - * @param EntityManagerInterface $em - * @param ClassMetadata $metadata - * @param mixed $entity - * @param array $change_set - */ private function appendAssociations( EntityManagerInterface $em, ClassMetadata $metadata, - $entity, + object $entity, array &$change_set ): void { // does the entity have any associations? diff --git a/test/Event/EntityChangedEventTest.php b/test/Event/EntityChangedEventTest.php index 3bd539d..9d0d3cf 100644 --- a/test/Event/EntityChangedEventTest.php +++ b/test/Event/EntityChangedEventTest.php @@ -7,7 +7,6 @@ namespace Hostnet\Component\EntityTracker\Event; use Doctrine\ORM\EntityManagerInterface; -use Hostnet\Component\EntityTracker\Mocked\MockEntity; use PHPUnit\Framework\TestCase; /** @@ -18,8 +17,8 @@ class EntityChangedEventTest extends TestCase public function testAll(): void { $em = $this->createMock(EntityManagerInterface::class); - $current_entity = new MockEntity(); - $original_entity = new MockEntity(); + $current_entity = new \stdClass(); + $original_entity = new \stdClass(); $mutated_fields = ['test']; $event = new EntityChangedEvent($em, $current_entity, $original_entity, $mutated_fields); $this->assertEquals($em, $event->getEntityManager()); diff --git a/test/Functional/Entity/Address.php b/test/Functional/Entity/Address.php index 32e512f..264fa70 100644 --- a/test/Functional/Entity/Address.php +++ b/test/Functional/Entity/Address.php @@ -8,39 +8,22 @@ use Doctrine\ORM\Mapping as ORM; -/** - * @ORM\Embeddable - */ +#[ORM\Embeddable] class Address { - /** - * @ORM\Column(type="string", nullable=true) - * @var string - */ + #[ORM\Column(type: 'string', nullable: true)] public $street; - /** - * @ORM\Column(type="integer", nullable=true) - * @var int - */ + #[ORM\Column(type: 'integer', nullable: true)] public $house_number; - /** - * @ORM\Column(type="string", nullable=true) - * @var string - */ + #[ORM\Column(type: 'string', nullable: true)] public $house_number_addition; - /** - * @ORM\Column(type="string", nullable=true) - * @var string - */ + #[ORM\Column(type: 'string', nullable: true)] public $city; - /** - * @ORM\Column(type="string", nullable=true) - * @var string - */ + #[ORM\Column(type: 'string', nullable: true)] public $country; /** diff --git a/test/Functional/Entity/Author.php b/test/Functional/Entity/Author.php index 0a4fc5f..0c6efa2 100644 --- a/test/Functional/Entity/Author.php +++ b/test/Functional/Entity/Author.php @@ -9,39 +9,27 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; -/** - * @ORM\Entity - */ +#[ORM\Entity] class Author { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - * @var int - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] private $id; - /** - * @ORM\Column - * @var string - */ + #[ORM\Column(type: 'string')] public $name; /** - * @ORM\Embedded(class="Address") * @var Address */ + #[ORM\Embedded(class: 'Address')] public $address; /** - * @ORM\ManyToMany( - * targetEntity="Book", - * mappedBy="authors", - * cascade={"persist"} - * ) * @var Book[] */ + #[ORM\ManyToMany(targetEntity: 'Book', mappedBy: 'authors', cascade: ['persist'])] public $books; /** diff --git a/test/Functional/Entity/Book.php b/test/Functional/Entity/Book.php index 1e40d4c..bd4b76f 100644 --- a/test/Functional/Entity/Book.php +++ b/test/Functional/Entity/Book.php @@ -7,35 +7,24 @@ namespace Hostnet\Component\EntityTracker\Functional\Entity; use Doctrine\ORM\Mapping as ORM; -use Hostnet\Component\EntityTracker\Annotation\Tracked; +use Hostnet\Component\EntityTracker\Attributes\Tracked; -/** - * @ORM\Entity - * @Tracked - */ +#[ORM\Entity] +#[Tracked] class Book { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - * @var int - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] private $id; - /** - * @ORM\Column - * @var string - */ + #[ORM\Column(type: 'string')] public $title; /** - * @ORM\ManyToMany( - * targetEntity="Author", - * inversedBy="books" - * ) * @var Author[] */ + #[ORM\ManyToMany(targetEntity: 'Author', inversedBy: 'books')] public $authors; /** diff --git a/test/Functional/Entity/Tool.php b/test/Functional/Entity/Tool.php index e442844..9a45edc 100644 --- a/test/Functional/Entity/Tool.php +++ b/test/Functional/Entity/Tool.php @@ -8,32 +8,21 @@ use Doctrine\ORM\Mapping as ORM; -/** - * @ORM\Entity - */ +#[ORM\Entity] class Tool { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - * @var int - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] public $id; /** - * @ORM\ManyToOne( - * targetEntity="Toolbox", - * inversedBy="tools" - * ) * @var Toolbox */ + #[ORM\ManyToOne(targetEntity: 'Toolbox', inversedBy: 'tools')] public $toolbox; - /** - * @ORM\Column - * @var string - */ + #[ORM\Column(type: 'string')] public $name; /** diff --git a/test/Functional/Entity/Toolbox.php b/test/Functional/Entity/Toolbox.php index 9716a89..4fbc750 100644 --- a/test/Functional/Entity/Toolbox.php +++ b/test/Functional/Entity/Toolbox.php @@ -8,37 +8,24 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; -use Hostnet\Component\EntityTracker\Annotation\Tracked; +use Hostnet\Component\EntityTracker\Attributes\Tracked; -/** - * @ORM\Entity - * @Tracked - */ +#[ORM\Entity] +#[Tracked] class Toolbox { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - * @var int - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] public $id; - /** - * @ORM\Column(type="string", nullable=true) - * @var string - */ + #[ORM\Column(type: 'string', nullable: true)] public $tag; /** - * @ORM\OneToMany( - * targetEntity="Tool", - * mappedBy="toolbox", - * cascade={"persist"}, - * orphanRemoval=true - * ) * @var Tool[] */ + #[ORM\OneToMany(targetEntity: 'Tool', mappedBy: 'toolbox', cascade: ['persist'], orphanRemoval: true)] public $tools; /** diff --git a/test/Functional/EventListenerTest.php b/test/Functional/EventListenerTest.php index 4d9578d..9e6bc0b 100644 --- a/test/Functional/EventListenerTest.php +++ b/test/Functional/EventListenerTest.php @@ -6,12 +6,12 @@ namespace Hostnet\Component\EntityTracker\Functional; -use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\DBAL\DriverManager; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\ORMSetup; use Doctrine\ORM\Tools\SchemaTool; -use Doctrine\ORM\Tools\Setup; use Doctrine\ORM\UnitOfWork; use Hostnet\Component\DatabaseTest\MysqlPersistentConnection; use Hostnet\Component\EntityTracker\Event\EntityChangedEvent; @@ -49,8 +49,9 @@ protected function setUp(): void $this->connection = new MysqlPersistentConnection(); $params = $this->connection->getConnectionParams(); - $config = Setup::createAnnotationMetadataConfiguration([__DIR__ . '/Entity'], true, null, null, false); - $this->em = EntityManager::create($params, $config); + $config = ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/Entity'], true); + $connection = DriverManager::getConnection($params, $config); + $this->em = new EntityManager($connection, $config); $event_manager = $this->em->getEventManager(); @@ -59,21 +60,17 @@ protected function setUp(): void $schema_tool = new SchemaTool($this->em); $schema_tool->createSchema($metadata); - // default doctrine annotation reader - $annotation_reader = new AnnotationReader(); - // setup required providers - $mutation_metadata_provider = new EntityMutationMetadataProvider($annotation_reader); - $annotation_metadata_provider = new EntityMetadataProvider($annotation_reader); + $mutation_metadata_provider = new EntityMutationMetadataProvider(); + $meta_provider = new EntityMetadataProvider(); - // pre flush event listener that uses the @Tracked annotation + // pre flush event listener that uses the Tracked attribute $entity_changed_listener = new EntityChangedListener( - $annotation_metadata_provider, + $meta_provider, $mutation_metadata_provider ); $event_manager->addEventListener('preFlush', $entity_changed_listener); - $event_manager->addEventListener('prePersist', $entity_changed_listener); $event_manager->addEventListener('entityChanged', $this); $this->events = []; diff --git a/test/Listener/EntityChangedListenerTest.php b/test/Listener/EntityChangedListenerTest.php index 80049ea..d3140e4 100644 --- a/test/Listener/EntityChangedListenerTest.php +++ b/test/Listener/EntityChangedListenerTest.php @@ -8,9 +8,8 @@ use Doctrine\Common\EventManager; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Event\PreFlushEventArgs; -use Doctrine\ORM\Proxy\Proxy; +use Doctrine\Persistence\Proxy; use Hostnet\Component\EntityTracker\Attributes\Tracked; use Hostnet\Component\EntityTracker\Event\EntityChangedEvent; use Hostnet\Component\EntityTracker\Events; @@ -22,7 +21,7 @@ use Psr\Log\LoggerInterface; /** - * Listener for the Entities that use the Mutation Annotation. + * Listener for the Entities that use the Tracked attribute. * * @covers \Hostnet\Component\EntityTracker\Listener\EntityChangedListener */ @@ -30,7 +29,7 @@ class EntityChangedListenerTest extends TestCase { use ProphecyTrait; - private $meta_annotation_provider; + private $meta_provider; private $meta_mutation_provider; private $event_manager; private $em; @@ -44,23 +43,23 @@ class EntityChangedListenerTest extends TestCase protected function setUp(): void { - $this->meta_annotation_provider = $this->prophesize(EntityMetadataProvider::class); - $this->meta_mutation_provider = $this->prophesize(EntityMutationMetadataProvider::class); - $this->em = $this->prophesize(EntityManagerInterface::class); - $this->event = $this->prophesize(PreFlushEventArgs::class); - $this->event_manager = $this->prophesize(EventManager::class); - $this->logger = $this->prophesize(LoggerInterface::class); + $this->meta_provider = $this->prophesize(EntityMetadataProvider::class); + $this->meta_mutation_provider = $this->prophesize(EntityMutationMetadataProvider::class); + $this->em = $this->prophesize(EntityManagerInterface::class); + $this->event = $this->prophesize(PreFlushEventArgs::class); + $this->event_manager = $this->prophesize(EventManager::class); + $this->logger = $this->prophesize(LoggerInterface::class); $this->em->getEventManager()->willReturn($this->event_manager->reveal()); $this->listener = new EntityChangedListener( - $this->meta_annotation_provider->reveal(), + $this->meta_provider->reveal(), $this->meta_mutation_provider->reveal(), $this->logger->reveal() ); } - public function testPreFlushNoAnnotation(): void + public function testPreFlushUntracked(): void { $entity = new \stdClass(); $this->meta_mutation_provider @@ -69,8 +68,7 @@ public function testPreFlushNoAnnotation(): void $this->event_manager ->dispatchEvent(Events::ENTITY_CHANGED, Argument::type(EntityChangedEvent::class)) ->shouldNotBeCalled(); - $this->meta_annotation_provider->isTracked($this->em->reveal(), $entity)->willReturn(false); - $this->meta_annotation_provider + $this->meta_provider ->getAttributeFromEntity(Argument::any(), $this->em->reveal(), $entity) ->willReturn(null); $this->listener->preFlush(new PreFlushEventArgs($this->em->reveal())); @@ -92,10 +90,9 @@ public function testPreFlushUnmanaged(): void $this->meta_mutation_provider ->getFullChangeSet($this->em->reveal()) ->willReturn($this->genericEntityDataProvider($entity)); - $this->meta_annotation_provider->isTracked($this->em->reveal(), $entity)->willReturn(true); - $this->meta_annotation_provider + $this->meta_provider ->getAttributeFromEntity(Argument::any(), $this->em->reveal(), $entity) - ->willReturn(null); + ->willReturn(new Tracked()); $this->logger->debug(Argument::cetera())->shouldBeCalled(); $this->meta_mutation_provider->isEntityManaged($this->em->reveal(), $entity)->willReturn(true); $this->meta_mutation_provider->createOriginalEntity($this->em->reveal(), $entity)->willReturn(null); @@ -118,10 +115,9 @@ public function testPreFlushWithoutMutatedFields(): void $this->event_manager ->dispatchEvent(Events::ENTITY_CHANGED, Argument::type(EntityChangedEvent::class)) ->shouldNotBeCalled(); - $this->meta_annotation_provider->isTracked($this->em->reveal(), $entity)->willReturn(true); - $this->meta_annotation_provider + $this->meta_provider ->getAttributeFromEntity(Argument::any(), $this->em->reveal(), $entity) - ->willReturn(null); + ->willReturn(new Tracked()); $this->meta_mutation_provider->isEntityManaged($this->em->reveal(), $entity)->willReturn(true); $this->meta_mutation_provider->createOriginalEntity($this->em->reveal(), $entity)->willReturn($original); $this->meta_mutation_provider->getMutatedFields($this->em->reveal(), $entity, $entity)->willReturn([]); @@ -139,10 +135,9 @@ public function testPreFlushWithMutatedFields(): void $this->meta_mutation_provider ->getFullChangeSet($this->em->reveal()) ->willReturn($this->genericEntityDataProvider($entity)); - $this->meta_annotation_provider->isTracked($this->em->reveal(), $entity)->willReturn(true); - $this->meta_annotation_provider + $this->meta_provider ->getAttributeFromEntity(Argument::any(), $this->em->reveal(), $entity) - ->willReturn(null); + ->willReturn(new Tracked()); $this->logger->debug(Argument::cetera())->shouldBeCalled(); $this->meta_mutation_provider->isEntityManaged($this->em->reveal(), $entity)->willReturn(true); $this->meta_mutation_provider->createOriginalEntity($this->em->reveal(), $entity)->willReturn($original); @@ -154,28 +149,6 @@ public function testPreFlushWithMutatedFields(): void $this->listener->preFlush(new PreFlushEventArgs($this->em->reveal())); } - public function testPreFlushWithNewAnnotatedEntity(): void - { - $entity = new \stdClass(); - - $this->meta_mutation_provider - ->getFullChangeSet($this->em->reveal()) - ->willReturn($this->genericEntityDataProvider($entity)); - $this->meta_annotation_provider->isTracked($this->em->reveal(), $entity)->willReturn(true); - $this->meta_annotation_provider - ->getAttributeFromEntity(Argument::any(), $this->em->reveal(), $entity) - ->willReturn(null); - $this->logger->debug(Argument::cetera())->shouldBeCalled(); - $this->meta_mutation_provider->isEntityManaged($this->em->reveal(), $entity)->willReturn(true); - $this->meta_mutation_provider->createOriginalEntity($this->em->reveal(), $entity)->willReturn(null); - $this->meta_mutation_provider->getMutatedFields($this->em->reveal(), $entity, null)->willReturn(['id']); - $this->event_manager - ->dispatchEvent(Events::ENTITY_CHANGED, Argument::type(EntityChangedEvent::class)) - ->shouldBeCalledTimes(1); - - $this->listener->preFlush(new PreFlushEventArgs($this->em->reveal())); - } - public function testPreFlushWithNewAttributedEntity(): void { $entity = new \stdClass(); @@ -183,10 +156,9 @@ public function testPreFlushWithNewAttributedEntity(): void $this->meta_mutation_provider ->getFullChangeSet($this->em->reveal()) ->willReturn($this->genericEntityDataProvider($entity)); - $this->meta_annotation_provider + $this->meta_provider ->getAttributeFromEntity(Tracked::class, $this->em->reveal(), $entity) ->willReturn(new Tracked())->shouldBeCalled(); - $this->meta_annotation_provider->isTracked(Argument::cetera())->shouldNotBeCalled(); $this->logger->debug(Argument::cetera())->shouldBeCalled(); $this->meta_mutation_provider->isEntityManaged($this->em->reveal(), $entity)->willReturn(true); $this->meta_mutation_provider->createOriginalEntity($this->em->reveal(), $entity)->willReturn(null); @@ -211,8 +183,7 @@ public function testPreFlushWithNewEntityCached(): void ); $this->meta_mutation_provider->getMutatedFields($this->em->reveal(), $entity, null)->willReturn(['id']); $this->meta_mutation_provider->createOriginalEntity($this->em->reveal(), $entity)->willReturn(null); - $this->meta_annotation_provider->isTracked(Argument::cetera())->shouldNotBeCalled(); - $this->meta_annotation_provider + $this->meta_provider ->getAttributeFromEntity(Tracked::class, $this->em->reveal(), $entity) ->willReturn(new Tracked())->shouldBeCalledTimes(1); $this->event_manager @@ -232,10 +203,9 @@ public function testPreFlushWithProxy(): void $this->event_manager ->dispatchEvent(Events::ENTITY_CHANGED, Argument::type(EntityChangedEvent::class)) ->shouldNotBeCalled(); - $this->meta_annotation_provider->isTracked($this->em->reveal(), $entity)->willReturn(true); - $this->meta_annotation_provider + $this->meta_provider ->getAttributeFromEntity(Argument::any(), $this->em->reveal(), $entity) - ->willReturn(null); + ->willReturn(new Tracked()); $this->meta_mutation_provider->isEntityManaged($this->em->reveal(), $entity)->willReturn(true); $this->listener->preFlush(new PreFlushEventArgs($this->em->reveal())); } @@ -249,10 +219,9 @@ public function testPreFlushWithInitializedProxy(): void $this->meta_mutation_provider ->getFullChangeSet($this->em->reveal()) ->willReturn($this->genericEntityDataProvider($entity->reveal())); - $this->meta_annotation_provider->isTracked($this->em->reveal(), $entity->reveal())->willReturn(true); - $this->meta_annotation_provider + $this->meta_provider ->getAttributeFromEntity(Argument::any(), $this->em->reveal(), $entity) - ->willReturn(null); + ->willReturn(new Tracked()); $this->meta_mutation_provider->isEntityManaged($this->em->reveal(), $entity->reveal())->willReturn(true); $entity->__isInitialized()->willReturn(true); $this->logger->debug(Argument::cetera())->shouldBeCalled(); @@ -268,13 +237,6 @@ public function testPreFlushWithInitializedProxy(): void $this->listener->preFlush(new PreFlushEventArgs($this->em->reveal())); } - public function testPrePersist(): void - { - // This is a smoke test of an empty function which is only there for BC compatibility. - self::expectNotToPerformAssertions(); - $this->listener->prePersist(new LifecycleEventArgs(new \stdClass(), $this->em->reveal())); - } - /** * @param mixed $entity * @return array[] diff --git a/test/Mocked/MockEntity.php b/test/Mocked/MockEntity.php deleted file mode 100644 index 3b8c521..0000000 --- a/test/Mocked/MockEntity.php +++ /dev/null @@ -1,16 +0,0 @@ -reader = new AnnotationReader(); - $this->provider = new EntityMetadataProvider($this->reader); + $this->provider = new EntityMetadataProvider(); $this->em = $this->createMock('Doctrine\ORM\EntityManagerInterface'); } - /** - * @dataProvider isTrackedProvider - */ - public function testIsTracked($entity, $expected_output): void - { - $class = get_class($entity); - $reflection = new \ReflectionClass($class); - $metadata = $this->buildMetadata($entity, [], []); - $metadata - ->expects($this->once()) - ->method('getReflectionClass') - ->willReturn($reflection); - - $this->em - ->expects($this->once()) - ->method('getClassMetadata') - ->with($class) - ->willReturn($metadata); - - $this->assertEquals( - $expected_output, - $this->provider->isTracked($this->em, $entity) - ); - } - - public function isTrackedProvider(): iterable - { - return [ - [new \stdClass(), false], - [new MockEntity(), true], - ]; - } - - /** - * @dataProvider getAnnotationFromEntityProvider - * @param mixed $entity - * @param mixed $annotation - * @param bool $has - */ - public function testGetAnnotationFromEntity($entity, $annotation, $has): void - { - $class = get_class($entity); - $metadata = $this - ->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataInfo') - ->disableOriginalConstructor() - ->getMock(); - - $metadata - ->expects($this->once()) - ->method('getReflectionClass') - ->willReturn(new \ReflectionClass($entity)); - - $this->em - ->expects($this->once()) - ->method('getClassMetadata') - ->with($class) - ->willReturn($metadata); - - $result = $this->provider->getAnnotationFromEntity($this->em, $entity, $annotation); - - if ($has) { - $this->assertEquals($annotation, $result); - } else { - $this->assertNull($result); - } - } - - /** - * @return array - */ - public function getAnnotationFromEntityProvider(): iterable - { - return [ - [new \stdClass(), null, false], - [new MockEntity(), new TrackedAnnotation(), true], - ]; - } - /** * @dataProvider getAttributeFromEntityProvider */ @@ -149,43 +65,8 @@ public function getAttributeFromEntityProvider(): iterable { return [ [new \stdClass(), false, null], - [new MockEntity(), false, null], [new TrackedAttributeEntity(), true, null], [new ProxiedTrackedAttributeEntity(), true, TrackedAttributeEntity::class], ]; } - - /** - * @param mixed $entity - * @param string[] $field_names - * @param string[] $assoc_names - */ - private function buildMetadata($entity, array $field_names, array $assoc_names): MockObject - { - $meta = $this - ->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata') - ->setMethods([ - 'getFieldNames', - 'getAssociationNames', - 'setFieldValue', - 'getFieldValue', - 'getAssociationTargetClass', - 'getIdentifierValues', - 'getReflectionClass', - ]) - ->setConstructorArgs([get_class($entity)]) - ->getMock(); - - $meta - ->expects($this->any()) - ->method('getFieldNames') - ->willReturn($field_names); - - $meta - ->expects($this->any()) - ->method('getAssociationNames') - ->willReturn($assoc_names); - - return $meta; - } } diff --git a/test/Provider/EntityMutationMetadataProviderTest.php b/test/Provider/EntityMutationMetadataProviderTest.php index 1693291..7589451 100644 --- a/test/Provider/EntityMutationMetadataProviderTest.php +++ b/test/Provider/EntityMutationMetadataProviderTest.php @@ -6,11 +6,11 @@ namespace Hostnet\Component\EntityTracker\Provider; -use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\DBAL\DriverManager; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\ORMSetup; use Doctrine\ORM\Tools\SchemaTool; -use Doctrine\ORM\Tools\Setup; use Hostnet\Component\DatabaseTest\MysqlPersistentConnection; use Hostnet\Component\EntityTracker\Provider\Entity\A; use Hostnet\Component\EntityTracker\Provider\Entity\B; @@ -47,15 +47,16 @@ public function setUp(): void $this->connection = new MysqlPersistentConnection(); $params = $this->connection->getConnectionParams(); - $config = Setup::createAnnotationMetadataConfiguration([__DIR__ . '/Entity'], true, null, null, false); - $this->em = EntityManager::create($params, $config); + $config = ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/Entity'], true); + $connection = DriverManager::getConnection($params, $config); + $this->em = new EntityManager($connection, $config); // create tables in the database $metadata = $this->em->getMetadataFactory()->getAllMetadata(); $schema_tool = new SchemaTool($this->em); $schema_tool->createSchema($metadata); - $this->provider = new EntityMutationMetadataProvider(new AnnotationReader()); + $this->provider = new EntityMutationMetadataProvider(); } public function testChanges(): void diff --git a/test/bootstrap.php b/test/bootstrap.php index 8d277ff..1e166f8 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -4,8 +4,4 @@ */ declare(strict_types=1); -$loader = include __DIR__ . '/../vendor/autoload.php'; - -use Doctrine\Common\Annotations\AnnotationRegistry; - -AnnotationRegistry::registerLoader([$loader, 'loadClass']); +require __DIR__ . '/../vendor/autoload.php';