Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce the Revisionable extension #2825

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading
Loading