Skip to content

Commit

Permalink
Introduce the Revisionable extension
Browse files Browse the repository at this point in the history
  • Loading branch information
mbabker committed Oct 15, 2024
1 parent c5798aa commit 591d002
Show file tree
Hide file tree
Showing 90 changed files with 6,113 additions and 44 deletions.
74 changes: 74 additions & 0 deletions doc/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ extension, refer to the extension's documentation page.
- [Loggable Extension](#loggable-extension)
- [Reference Integrity Extension](#reference-integrity-extension)
- [References Extension](#references-extension)
- [Revisionable Extension](#revisionable-extension)
- [Sluggable Extension](#sluggable-extension)
- [Soft Deleteable Extension](#soft-deleteable-extension)
- [Sortable Extension](#sortable-extension)
Expand Down Expand Up @@ -495,6 +496,79 @@ class Article
}
```

### Revisionable Extension

The below annotations are used to configure the [Revisionable extension](./revisionable.md).

#### `@Gedmo\Mapping\Annotation\Revisionable`

The `Revisionable` annotation is a class annotation used to identify objects which can have changes logged,
all revisionable objects **MUST** have this annotation.

Required Attributes:

- **revisionClass** - A custom model class implementing `Gedmo\Revisionable\RevisionInterface` to use for logging changes;
defaults to `Gedmo\Revisionable\Entity\Revision` for Doctrine ORM users or
`Gedmo\Revisionable\Document\Revision` for Doctrine MongoDB ODM users

Example:

```php
<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

/**
* @ORM\Entity
* @Gedmo\Revisionable(revisionClass="App\Entity\ArticleRevision")
*/
class Article {}
```

#### `@Gedmo\Mapping\Annotation\Versioned`

The `Versioned` annotation is a property annotation used to identify properties whose changes should be logged.
This annotation can be set for properties with a single value (i.e. a scalar type or an object such as
`DateTimeInterface`), but not for collections. Versioned fields can be restored to an earlier version.

Example:

```php
<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

/**
* @ORM\Entity
* @Gedmo\Revisionable
*/
class Comment
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
public ?int $id = null;

/**
* @ORM\ManyToOne(targetEntity="App\Entity\Article", inversedBy="comments")
* @Gedmo\Versioned
*/
public ?Article $article = null;

/**
* @ORM\Column(type="string")
* @Gedmo\Versioned
*/
public ?string $body = null;
}
```

### Sluggable Extension

The below annotations are used to configure the [Sluggable extension](./sluggable.md).
Expand Down
65 changes: 65 additions & 0 deletions doc/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ For more detailed usage of each extension, refer to the extension's documentatio
- [Loggable Extension](#loggable-extension)
- [Reference Integrity Extension](#reference-integrity-extension)
- [References Extension](#references-extension)
- [Revisionable Extension](#revisionable-extension)
- [Sluggable Extension](#sluggable-extension)
- [Soft Deleteable Extension](#soft-deleteable-extension)
- [Sortable Extension](#sortable-extension)
Expand Down Expand Up @@ -440,6 +441,70 @@ class Article
}
```

### Revisionable Extension

The below attributes are used to configure the [Revisionable extension](./revisionable.md).

#### `#[Gedmo\Mapping\Annotation\Revisionable]`

The `Revisionable` attribute is a class attribute used to identify objects which can have changes logged,
all revisionable objects **MUST** have this attribute.

Required Parameters:

- **revisionClass** - A custom model class implementing `Gedmo\Revisionable\RevisionInterface` to use for logging changes;
defaults to `Gedmo\Revisionable\Entity\Revision` for Doctrine ORM users or
`Gedmo\Revisionable\Document\Revision` for Doctrine MongoDB ODM users

Example:

```php
<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

#[ORM\Entity]
#[Gedmo\Revisionable(revisionClass: ArticleRevision::class)]
class Article {}
```

#### `#[Gedmo\Mapping\Annotation\Versioned]`

The `Versioned` attribute is a property attribute used to identify properties whose changes should be logged.
This attribute can be set for properties with a single value (i.e. a scalar type or an object such as
`DateTimeInterface`), but not for collections. Versioned fields can be restored to an earlier version.

Example:

```php
<?php
namespace App\Entity;

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

#[ORM\Entity]
#[Gedmo\Revisionable]
class Comment
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: Types::INTEGER)]
public ?int $id = null;

#[ORM\ManyToOne(targetEntity: Article::class, inversedBy: 'comments')]
#[Gedmo\Versioned]
public ?Article $article = null;

#[ORM\Column(type: Types::STRING)]
#[Gedmo\Versioned]
public ?string $body = null;
}
```

### Sluggable Extension

The below attributes are used to configure the [Sluggable extension](./sluggable.md).
Expand Down
24 changes: 19 additions & 5 deletions doc/frameworks/laminas.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use Gedmo\Blameable\BlameableListener;
use Gedmo\IpTraceable\IpTraceableListener;
use Gedmo\Loggable\LoggableListener;
use Gedmo\Mapping\Driver\AttributeReader;
use Gedmo\Revisionable\RevisionableListener;
use Gedmo\Sluggable\SluggableListener;
use Gedmo\SoftDeleteable\SoftDeleteableListener;
use Gedmo\Sortable\SortableListener;
Expand Down Expand Up @@ -83,6 +84,14 @@ return [

return $listener;
},
'gedmo.listener.revisionable' => function (ContainerInterface $container, string $requestedName): RevisionableListener {
$listener = new RevisionableListener();

// 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
$listener->setAnnotationReader($container->get('gedmo.mapping.driver.attribute'));

return $listener;
},
'gedmo.listener.sluggable' => function (ContainerInterface $container, string $requestedName): SluggableListener {
$listener = new SluggableListener();

Expand Down Expand Up @@ -141,6 +150,7 @@ return [
'gedmo.listener.blameable',
'gedmo.listener.ip_traceable',
'gedmo.listener.loggable',
'gedmo.listener.revisionable',
'gedmo.listener.sluggable',
'gedmo.listener.soft_deleteable',
'gedmo.listener.sortable',
Expand Down Expand Up @@ -232,8 +242,8 @@ return [

## Registering Mapping Configuration

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

> [!NOTE]
> 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.
Expand All @@ -258,6 +268,7 @@ return [
'class' => AttributeDriver::class, // If your application is using annotations, use the AnnotationDriver class instead
'paths' => [
'/path/to/vendor/gedmo/doctrine-extensions/src/Loggable/Document',
'/path/to/vendor/gedmo/doctrine-extensions/src/Revisionable/Document',
'/path/to/vendor/gedmo/doctrine-extensions/src/Translatable/Document',
],
],
Expand Down Expand Up @@ -288,6 +299,7 @@ return [
'class' => AttributeDriver::class, // If your application is using annotations, use the AnnotationDriver class instead
'paths' => [
'/path/to/vendor/gedmo/doctrine-extensions/src/Loggable/Entity',
'/path/to/vendor/gedmo/doctrine-extensions/src/Revisionable/Entity',
'/path/to/vendor/gedmo/doctrine-extensions/src/Translatable/Entity',
'/path/to/vendor/gedmo/doctrine-extensions/src/Tree/Entity',
],
Expand All @@ -310,6 +322,8 @@ $ vendor/bin/doctrine-module orm:info

[OK] Gedmo\Loggable\Entity\LogEntry
[OK] Gedmo\Loggable\Entity\MappedSuperclass\AbstractLogEntry
[OK] Gedmo\Revisionable\Entity\Revision
[OK] Gedmo\Revisionable\Entity\MappedSuperclass\AbstractRevision
[OK] Gedmo\Translatable\Entity\Translation
[OK] Gedmo\Translatable\Entity\MappedSuperclass\AbstractTranslation
[OK] Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation
Expand Down Expand Up @@ -374,9 +388,9 @@ return [

## Configuring Extensions via Event Listeners

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

**Help Improve This Documentation**

Expand Down
67 changes: 61 additions & 6 deletions doc/frameworks/symfony.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ services:
# The `annotation_reader` service was deprecated in Symfony 6.4 and removed in Symfony 7.0
- [ setAnnotationReader, [ '@annotation_reader' ] ]

# Gedmo Revisionable Extension Listener
gedmo.listener.revisionable:
class: Gedmo\Revisionable\RevisionableListener
tags:
- { name: doctrine.event_listener, event: 'onFlush' }
- { name: doctrine.event_listener, event: 'loadClassMetadata' }
- { name: doctrine.event_listener, event: 'postPersist' }
calls:
# Uncomment the below call if using attributes, and comment the call for the annotation reader
# - [ setAnnotationReader, [ '@gedmo.mapping.driver.attribute' ] ]
# The `annotation_reader` service was deprecated in Symfony 6.4 and removed in Symfony 7.0
- [ setAnnotationReader, [ '@annotation_reader' ] ]

# Gedmo Sluggable Extension Listener
gedmo.listener.sluggable:
class: Gedmo\Sluggable\SluggableListener
Expand Down Expand Up @@ -238,8 +251,8 @@ services:
## Registering Mapping Configuration
When using the [Loggable](../loggable.md), [Translatable](../translatable.md), or [Tree](../tree.md) extensions, you will
need to register the mappings for these extensions to your object managers.
When using the [Loggable](../loggable.md), [Revisionable](../revisionable.md), [Translatable](../translatable.md),
or [Tree](../tree.md) extensions, you will need to register the mappings for these extensions to your object managers.
> [!NOTE]
> 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.
Expand All @@ -262,6 +275,12 @@ doctrine_mongodb:
prefix: Gedmo\Loggable\Document
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Loggable/Document"
is_bundle: false
revisionable:
type: attribute # or annotation
alias: GedmoRevisionable
prefix: Gedmo\Revisionable\Document
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Revisionable/Document"
is_bundle: false
translatable:
type: attribute # or annotation
alias: GedmoTranslatable
Expand All @@ -277,6 +296,8 @@ $ bin/console doctrine:mongodb:mapping:info
Found X documents mapped in document manager default:
[OK] Gedmo\Loggable\Document\LogEntry
[OK] Gedmo\Loggable\Document\MappedSuperclass\AbstractLogEntry
[OK] Gedmo\Revisionable\Document\Revision
[OK] Gedmo\Revisionable\Document\MappedSuperclass\AbstractRevision
[OK] Gedmo\Translatable\Document\MappedSuperclass\AbstractPersonalTranslation
[OK] Gedmo\Translatable\Document\MappedSuperclass\AbstractTranslation
[OK] Gedmo\Translatable\Document\Translation
Expand All @@ -297,6 +318,12 @@ doctrine:
prefix: Gedmo\Loggable\Entity
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Loggable/Entity"
is_bundle: false
revisionable:
type: attribute # or annotation
alias: GedmoRevisionable
prefix: Gedmo\Revisionable\Entity
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Revisionable/Entity"
is_bundle: false
translatable:
type: attribute # or annotation
alias: GedmoTranslatable
Expand All @@ -318,6 +345,8 @@ $ bin/console doctrine:mapping:info
Found X mapped entities:
[OK] Gedmo\Loggable\Entity\LogEntry
[OK] Gedmo\Loggable\Entity\MappedSuperclass\AbstractLogEntry
[OK] Gedmo\Revisionable\Entity\Revision
[OK] Gedmo\Revisionable\Entity\MappedSuperclass\AbstractRevision
[OK] Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation
[OK] Gedmo\Translatable\Entity\MappedSuperclass\AbstractTranslation
[OK] Gedmo\Translatable\Entity\Translation
Expand Down Expand Up @@ -364,10 +393,10 @@ doctrine:

## Configuring Extensions via Event Subscribers

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

```php
<?php
Expand All @@ -376,6 +405,7 @@ namespace App\EventListener;
use Gedmo\Blameable\BlameableListener;
use Gedmo\IpTraceable\IpTraceableListener;
use Gedmo\Loggable\LoggableListener;
use Gedmo\Revisionable\RevisionableListener;
use Gedmo\Translatable\TranslatableListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
Expand All @@ -389,6 +419,7 @@ final class GedmoExtensionsEventSubscriber implements EventSubscriberInterface
private BlameableListener $blameableListener,
private IpTraceableListener $ipTraceableListener,
private LoggableListener $loggableListener,
private RevisionableListener $revisionableListener,
private TranslatableListener $translatableListener,
private ?AuthorizationCheckerInterface $authorizationChecker = null,
private ?TokenStorageInterface $tokenStorage = null,
Expand All @@ -401,6 +432,7 @@ final class GedmoExtensionsEventSubscriber implements EventSubscriberInterface
['configureBlameableListener'], // Must run after the user is authenticated
['configureIpTraceableListener', 512], // Runs early since this only requires the Request object
['configureLoggableListener'], // Must run after the user is authenticated
['configureRevisionableListener'], // Must run after the user is authenticated
['configureTranslatableListener'], // Must run after the locale is configured
],
];
Expand Down Expand Up @@ -470,6 +502,29 @@ final class GedmoExtensionsEventSubscriber implements EventSubscriberInterface
}
}
/**
* Configures the revisionable listener using the currently authenticated user
*/
public function configureRevisionableListener(RequestEvent $event): void
{
// Only applies to the main request
if (!$event->isMainRequest()) {
return;
}
// If the required security component services weren't provided, there's nothing we can do
if (null === $this->authorizationChecker || null === $this->tokenStorage) {
return;
}
$token = $this->tokenStorage->getToken();
// Only set the user information if there is a token in storage and it represents an authenticated user
if (null !== $token && $this->authorizationChecker->isGranted('IS_AUTHENTICATED')) {
$this->revisionableListener->setUsername($token->getUser());
}
}
/**
* Configures the translatable listener using the request locale
*/
Expand Down
3 changes: 3 additions & 0 deletions doc/loggable.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Loggable behavioral extension for Doctrine

> [!IMPORTANT]
> The Loggable extension is **NOT** compatible with `doctrine/dbal` >=4.0. If your project needs this extension, you will need to use the latest `doctrine/dbal` 3.x release.
**Loggable** behavior tracks your record changes and is able to
manage versions.

Expand Down
Loading

0 comments on commit 591d002

Please sign in to comment.