Skip to content

Commit 13607b9

Browse files
committed
Introduce the Revisionable extension
1 parent eb53dfc commit 13607b9

File tree

90 files changed

+6703
-23
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+6703
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ a release.
4242
- SoftDeleteable: Resolved a bug where a soft-deleted object isn't remove from the ObjectManager (#2930)
4343

4444
### Added
45+
- Introduced the `Revisionable` extension as a modern replacement to the `Loggable` extension (#2825)
4546
- IP address provider for use with extensions with IP address references (#2928)
4647

4748
## [3.19.0] - 2025-02-24

doc/annotations.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ extension, refer to the extension's documentation page.
1414
- [Loggable Extension](#loggable-extension)
1515
- [Reference Integrity Extension](#reference-integrity-extension)
1616
- [References Extension](#references-extension)
17+
- [Revisionable Extension](#revisionable-extension)
1718
- [Sluggable Extension](#sluggable-extension)
1819
- [Soft Deleteable Extension](#soft-deleteable-extension)
1920
- [Sortable Extension](#sortable-extension)
@@ -495,6 +496,79 @@ class Article
495496
}
496497
```
497498

499+
### Revisionable Extension
500+
501+
The below annotations are used to configure the [Revisionable extension](./revisionable.md).
502+
503+
#### `@Gedmo\Mapping\Annotation\Revisionable`
504+
505+
The `Revisionable` annotation is a class annotation used to identify objects which can have changes logged,
506+
all revisionable objects **MUST** have this annotation.
507+
508+
Required Attributes:
509+
510+
- **revisionClass** - A custom model class implementing `Gedmo\Revisionable\RevisionInterface` to use for logging changes;
511+
defaults to `Gedmo\Revisionable\Entity\Revision` for Doctrine ORM users or
512+
`Gedmo\Revisionable\Document\Revision` for Doctrine MongoDB ODM users
513+
514+
Example:
515+
516+
```php
517+
<?php
518+
namespace App\Entity;
519+
520+
use Doctrine\ORM\Mapping as ORM;
521+
use Gedmo\Mapping\Annotation as Gedmo;
522+
523+
/**
524+
* @ORM\Entity
525+
* @Gedmo\Revisionable(revisionClass="App\Entity\ArticleRevision")
526+
*/
527+
class Article {}
528+
```
529+
530+
#### `@Gedmo\Mapping\Annotation\Versioned`
531+
532+
The `Versioned` annotation is a property annotation used to identify properties whose changes should be logged.
533+
This annotation can be set for properties with a single value (i.e. a scalar type or an object such as
534+
`DateTimeInterface`), but not for collections. Versioned fields can be restored to an earlier version.
535+
536+
Example:
537+
538+
```php
539+
<?php
540+
namespace App\Entity;
541+
542+
use Doctrine\ORM\Mapping as ORM;
543+
use Gedmo\Mapping\Annotation as Gedmo;
544+
545+
/**
546+
* @ORM\Entity
547+
* @Gedmo\Revisionable
548+
*/
549+
class Comment
550+
{
551+
/**
552+
* @ORM\Id
553+
* @ORM\GeneratedValue
554+
* @ORM\Column(type="integer")
555+
*/
556+
public ?int $id = null;
557+
558+
/**
559+
* @ORM\ManyToOne(targetEntity="App\Entity\Article", inversedBy="comments")
560+
* @Gedmo\Versioned
561+
*/
562+
public ?Article $article = null;
563+
564+
/**
565+
* @ORM\Column(type="string")
566+
* @Gedmo\Versioned
567+
*/
568+
public ?string $body = null;
569+
}
570+
```
571+
498572
### Sluggable Extension
499573

500574
The below annotations are used to configure the [Sluggable extension](./sluggable.md).

doc/attributes.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ For more detailed usage of each extension, refer to the extension's documentatio
1414
- [Loggable Extension](#loggable-extension)
1515
- [Reference Integrity Extension](#reference-integrity-extension)
1616
- [References Extension](#references-extension)
17+
- [Revisionable Extension](#revisionable-extension)
1718
- [Sluggable Extension](#sluggable-extension)
1819
- [Soft Deleteable Extension](#soft-deleteable-extension)
1920
- [Sortable Extension](#sortable-extension)
@@ -440,6 +441,70 @@ class Article
440441
}
441442
```
442443

444+
### Revisionable Extension
445+
446+
The below attributes are used to configure the [Revisionable extension](./revisionable.md).
447+
448+
#### `#[Gedmo\Mapping\Annotation\Revisionable]`
449+
450+
The `Revisionable` attribute is a class attribute used to identify objects which can have changes logged,
451+
all revisionable objects **MUST** have this attribute.
452+
453+
Required Parameters:
454+
455+
- **revisionClass** - A custom model class implementing `Gedmo\Revisionable\RevisionInterface` to use for logging changes;
456+
defaults to `Gedmo\Revisionable\Entity\Revision` for Doctrine ORM users or
457+
`Gedmo\Revisionable\Document\Revision` for Doctrine MongoDB ODM users
458+
459+
Example:
460+
461+
```php
462+
<?php
463+
namespace App\Entity;
464+
465+
use Doctrine\ORM\Mapping as ORM;
466+
use Gedmo\Mapping\Annotation as Gedmo;
467+
468+
#[ORM\Entity]
469+
#[Gedmo\Revisionable(revisionClass: ArticleRevision::class)]
470+
class Article {}
471+
```
472+
473+
#### `#[Gedmo\Mapping\Annotation\Versioned]`
474+
475+
The `Versioned` attribute is a property attribute used to identify properties whose changes should be logged.
476+
This attribute can be set for properties with a single value (i.e. a scalar type or an object such as
477+
`DateTimeInterface`), but not for collections. Versioned fields can be restored to an earlier version.
478+
479+
Example:
480+
481+
```php
482+
<?php
483+
namespace App\Entity;
484+
485+
use Doctrine\DBAL\Types\Types;
486+
use Doctrine\ORM\Mapping as ORM;
487+
use Gedmo\Mapping\Annotation as Gedmo;
488+
489+
#[ORM\Entity]
490+
#[Gedmo\Revisionable]
491+
class Comment
492+
{
493+
#[ORM\Id]
494+
#[ORM\GeneratedValue]
495+
#[ORM\Column(type: Types::INTEGER)]
496+
public ?int $id = null;
497+
498+
#[ORM\ManyToOne(targetEntity: Article::class, inversedBy: 'comments')]
499+
#[Gedmo\Versioned]
500+
public ?Article $article = null;
501+
502+
#[ORM\Column(type: Types::STRING)]
503+
#[Gedmo\Versioned]
504+
public ?string $body = null;
505+
}
506+
```
507+
443508
### Sluggable Extension
444509

445510
The below attributes are used to configure the [Sluggable extension](./sluggable.md).

doc/frameworks/laminas.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use Gedmo\Blameable\BlameableListener;
4444
use Gedmo\IpTraceable\IpTraceableListener;
4545
use Gedmo\Loggable\LoggableListener;
4646
use Gedmo\Mapping\Driver\AttributeReader;
47+
use Gedmo\Revisionable\RevisionableListener;
4748
use Gedmo\Sluggable\SluggableListener;
4849
use Gedmo\SoftDeleteable\SoftDeleteableListener;
4950
use Gedmo\Sortable\SortableListener;
@@ -83,6 +84,14 @@ return [
8384

8485
return $listener;
8586
},
87+
'gedmo.listener.revisionable' => function (ContainerInterface $container, string $requestedName): RevisionableListener {
88+
$listener = new RevisionableListener();
89+
90+
// This call configures the listener to use the attribute driver service created above; if using annotations, you will need to provide the appropriate service instead
91+
$listener->setAnnotationReader($container->get('gedmo.mapping.driver.attribute'));
92+
93+
return $listener;
94+
},
8695
'gedmo.listener.sluggable' => function (ContainerInterface $container, string $requestedName): SluggableListener {
8796
$listener = new SluggableListener();
8897

@@ -141,6 +150,7 @@ return [
141150
'gedmo.listener.blameable',
142151
'gedmo.listener.ip_traceable',
143152
'gedmo.listener.loggable',
153+
'gedmo.listener.revisionable',
144154
'gedmo.listener.sluggable',
145155
'gedmo.listener.soft_deleteable',
146156
'gedmo.listener.sortable',
@@ -232,8 +242,8 @@ return [
232242

233243
## Registering Mapping Configuration
234244

235-
When using the [Loggable](../loggable.md), [Translatable](../translatable.md), or [Tree](../tree.md) extensions, you will
236-
need to register the mappings for these extensions to your object managers.
245+
When using the [Loggable](../loggable.md), [Revisionable](../revisionable.md), [Translatable](../translatable.md),
246+
or [Tree](../tree.md) extensions, you will need to register the mappings for these extensions to your object managers.
237247

238248
> [!NOTE]
239249
> These extensions only provide mappings through annotations or attributes, with support for annotations being deprecated. If using annotations, you will need to ensure the [`doctrine/annotations`](https://www.doctrine-project.org/projects/annotations.html) library is installed and configured.
@@ -258,6 +268,7 @@ return [
258268
'class' => AttributeDriver::class, // If your application is using annotations, use the AnnotationDriver class instead
259269
'paths' => [
260270
'/path/to/vendor/gedmo/doctrine-extensions/src/Loggable/Document',
271+
'/path/to/vendor/gedmo/doctrine-extensions/src/Revisionable/Document',
261272
'/path/to/vendor/gedmo/doctrine-extensions/src/Translatable/Document',
262273
],
263274
],
@@ -288,6 +299,7 @@ return [
288299
'class' => AttributeDriver::class, // If your application is using annotations, use the AnnotationDriver class instead
289300
'paths' => [
290301
'/path/to/vendor/gedmo/doctrine-extensions/src/Loggable/Entity',
302+
'/path/to/vendor/gedmo/doctrine-extensions/src/Revisionable/Entity',
291303
'/path/to/vendor/gedmo/doctrine-extensions/src/Translatable/Entity',
292304
'/path/to/vendor/gedmo/doctrine-extensions/src/Tree/Entity',
293305
],
@@ -310,6 +322,8 @@ $ vendor/bin/doctrine-module orm:info
310322

311323
[OK] Gedmo\Loggable\Entity\LogEntry
312324
[OK] Gedmo\Loggable\Entity\MappedSuperclass\AbstractLogEntry
325+
[OK] Gedmo\Revisionable\Entity\Revision
326+
[OK] Gedmo\Revisionable\Entity\MappedSuperclass\AbstractRevision
313327
[OK] Gedmo\Translatable\Entity\Translation
314328
[OK] Gedmo\Translatable\Entity\MappedSuperclass\AbstractTranslation
315329
[OK] Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation
@@ -374,9 +388,9 @@ return [
374388

375389
## Configuring Extensions via Event Listeners
376390

377-
When using the [Blameable](../blameable.md), [IP Traceable](../ip_traceable.md), [Loggable](../loggable.md), or
378-
[Translatable](../translatable.md) extensions, to work correctly, they require extra information that must be set
379-
at runtime.
391+
When using the [Blameable](../blameable.md), [IP Traceable](../ip_traceable.md), [Loggable](../loggable.md),
392+
[Revisionable](../revisionable.md), or [Translatable](../translatable.md) extensions, to work correctly,
393+
they require extra information that must be set at runtime.
380394

381395
**Help Improve This Documentation**
382396

doc/frameworks/symfony.md

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,19 @@ services:
8585
# The `annotation_reader` service was deprecated in Symfony 6.4 and removed in Symfony 7.0
8686
- [ setAnnotationReader, [ '@annotation_reader' ] ]
8787

88+
# Gedmo Revisionable Extension Listener
89+
gedmo.listener.revisionable:
90+
class: Gedmo\Revisionable\RevisionableListener
91+
tags:
92+
- { name: doctrine.event_listener, event: 'onFlush' }
93+
- { name: doctrine.event_listener, event: 'loadClassMetadata' }
94+
- { name: doctrine.event_listener, event: 'postPersist' }
95+
calls:
96+
# Uncomment the below call if using attributes, and comment the call for the annotation reader
97+
# - [ setAnnotationReader, [ '@gedmo.mapping.driver.attribute' ] ]
98+
# The `annotation_reader` service was deprecated in Symfony 6.4 and removed in Symfony 7.0
99+
- [ setAnnotationReader, [ '@annotation_reader' ] ]
100+
88101
# Gedmo Sluggable Extension Listener
89102
gedmo.listener.sluggable:
90103
class: Gedmo\Sluggable\SluggableListener
@@ -238,8 +251,8 @@ services:
238251
239252
## Registering Mapping Configuration
240253
241-
When using the [Loggable](../loggable.md), [Translatable](../translatable.md), or [Tree](../tree.md) extensions, you will
242-
need to register the mappings for these extensions to your object managers.
254+
When using the [Loggable](../loggable.md), [Revisionable](../revisionable.md), [Translatable](../translatable.md),
255+
or [Tree](../tree.md) extensions, you will need to register the mappings for these extensions to your object managers.
243256
244257
> [!NOTE]
245258
> These extensions only provide mappings through annotations or attributes, with support for annotations being deprecated. If using annotations, you will need to ensure the [`doctrine/annotations`](https://www.doctrine-project.org/projects/annotations.html) library is installed and configured.
@@ -262,6 +275,12 @@ doctrine_mongodb:
262275
prefix: Gedmo\Loggable\Document
263276
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Loggable/Document"
264277
is_bundle: false
278+
revisionable:
279+
type: attribute # or annotation
280+
alias: GedmoRevisionable
281+
prefix: Gedmo\Revisionable\Document
282+
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Revisionable/Document"
283+
is_bundle: false
265284
translatable:
266285
type: attribute # or annotation
267286
alias: GedmoTranslatable
@@ -277,6 +296,8 @@ $ bin/console doctrine:mongodb:mapping:info
277296
Found X documents mapped in document manager default:
278297
[OK] Gedmo\Loggable\Document\LogEntry
279298
[OK] Gedmo\Loggable\Document\MappedSuperclass\AbstractLogEntry
299+
[OK] Gedmo\Revisionable\Document\Revision
300+
[OK] Gedmo\Revisionable\Document\MappedSuperclass\AbstractRevision
280301
[OK] Gedmo\Translatable\Document\MappedSuperclass\AbstractPersonalTranslation
281302
[OK] Gedmo\Translatable\Document\MappedSuperclass\AbstractTranslation
282303
[OK] Gedmo\Translatable\Document\Translation
@@ -297,6 +318,12 @@ doctrine:
297318
prefix: Gedmo\Loggable\Entity
298319
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Loggable/Entity"
299320
is_bundle: false
321+
revisionable:
322+
type: attribute # or annotation
323+
alias: GedmoRevisionable
324+
prefix: Gedmo\Revisionable\Entity
325+
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Revisionable/Entity"
326+
is_bundle: false
300327
translatable:
301328
type: attribute # or annotation
302329
alias: GedmoTranslatable
@@ -318,6 +345,8 @@ $ bin/console doctrine:mapping:info
318345
Found X mapped entities:
319346
[OK] Gedmo\Loggable\Entity\LogEntry
320347
[OK] Gedmo\Loggable\Entity\MappedSuperclass\AbstractLogEntry
348+
[OK] Gedmo\Revisionable\Entity\Revision
349+
[OK] Gedmo\Revisionable\Entity\MappedSuperclass\AbstractRevision
321350
[OK] Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation
322351
[OK] Gedmo\Translatable\Entity\MappedSuperclass\AbstractTranslation
323352
[OK] Gedmo\Translatable\Entity\Translation
@@ -364,10 +393,10 @@ doctrine:
364393

365394
## Configuring Extensions via Event Subscribers
366395

367-
When using the [Blameable](../blameable.md), [IP Traceable](../ip_traceable.md), [Loggable](../loggable.md), or
368-
[Translatable](../translatable.md) extensions, to work correctly, they require extra information that must be set
369-
at runtime, typically during the `kernel.request` event. The below example is an event subscriber class which configures
370-
all of these extensions.
396+
When using the [Blameable](../blameable.md), [IP Traceable](../ip_traceable.md), [Loggable](../loggable.md),
397+
[Revisionable](../revisionable.md), or [Translatable](../translatable.md) extensions, to work correctly,
398+
they require extra information that must be set at runtime, typically during the `kernel.request` event.
399+
The below example is an event subscriber class which configures all of these extensions.
371400

372401
```php
373402
<?php
@@ -376,6 +405,7 @@ namespace App\EventListener;
376405
use Gedmo\Blameable\BlameableListener;
377406
use Gedmo\IpTraceable\IpTraceableListener;
378407
use Gedmo\Loggable\LoggableListener;
408+
use Gedmo\Revisionable\RevisionableListener;
379409
use Gedmo\Translatable\TranslatableListener;
380410
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
381411
use Symfony\Component\HttpKernel\Event\RequestEvent;
@@ -389,6 +419,7 @@ final class GedmoExtensionsEventSubscriber implements EventSubscriberInterface
389419
private BlameableListener $blameableListener,
390420
private IpTraceableListener $ipTraceableListener,
391421
private LoggableListener $loggableListener,
422+
private RevisionableListener $revisionableListener,
392423
private TranslatableListener $translatableListener,
393424
private ?AuthorizationCheckerInterface $authorizationChecker = null,
394425
private ?TokenStorageInterface $tokenStorage = null,
@@ -401,6 +432,7 @@ final class GedmoExtensionsEventSubscriber implements EventSubscriberInterface
401432
['configureBlameableListener'], // Must run after the user is authenticated
402433
['configureIpTraceableListener', 512], // Runs early since this only requires the Request object
403434
['configureLoggableListener'], // Must run after the user is authenticated
435+
['configureRevisionableListener'], // Must run after the user is authenticated
404436
['configureTranslatableListener'], // Must run after the locale is configured
405437
],
406438
];
@@ -470,6 +502,29 @@ final class GedmoExtensionsEventSubscriber implements EventSubscriberInterface
470502
}
471503
}
472504
505+
/**
506+
* Configures the revisionable listener using the currently authenticated user
507+
*/
508+
public function configureRevisionableListener(RequestEvent $event): void
509+
{
510+
// Only applies to the main request
511+
if (!$event->isMainRequest()) {
512+
return;
513+
}
514+
515+
// If the required security component services weren't provided, there's nothing we can do
516+
if (null === $this->authorizationChecker || null === $this->tokenStorage) {
517+
return;
518+
}
519+
520+
$token = $this->tokenStorage->getToken();
521+
522+
// Only set the user information if there is a token in storage and it represents an authenticated user
523+
if (null !== $token && $this->authorizationChecker->isGranted('IS_AUTHENTICATED')) {
524+
$this->revisionableListener->setUsername($token->getUser());
525+
}
526+
}
527+
473528
/**
474529
* Configures the translatable listener using the request locale
475530
*/

0 commit comments

Comments
 (0)