diff --git a/doc/timestampable.md b/doc/timestampable.md index b20a903577..79b3638233 100644 --- a/doc/timestampable.md +++ b/doc/timestampable.md @@ -1,674 +1,273 @@ -# Timestampable behavior extension for Doctrine +# Timestampable Behavior Extension for Doctrine -**Timestampable** behavior will automate the update of date fields -on your Entities or Documents. It works through annotations and can update -fields on creation, update, property subset update, or even on specific property value change. +The **Timestampable** behavior automates the update of timestamps on your Doctrine objects. -Features: +## Index -- Automatic predefined date field update on creation, update, property subset update, and even on record property changes -- ORM and ODM support using same listener -- Specific annotations for properties, and no interface required -- Can react to specific property or relation changes to specific value -- Can be nested with other behaviors -- Attribute, Annotation and Xml mapping support for extensions +- [Getting Started](#getting-started) +- [Configuring Timestampable Objects](#configuring-timestampable-objects) +- [Using Traits](#using-traits) +- [Logging Changes For Specific Actions](#logging-changes-for-specific-actions) -This article will cover the basic installation and functionality of **Timestampable** behavior +## Getting Started -Content: +The timestampable behavior can be added to a supported Doctrine object manager by registering its event subscriber +when creating the manager. -- [Including](#including-extension) the extension -- Entity [example](#entity-mapping) -- Document [example](#document-mapping) -- [Xml](#xml-mapping) mapping example -- Advanced usage [examples](#advanced-examples) -- Using [Traits](#traits) - - +```php +use Gedmo\Timestampable\TimestampableListener; -## Setup and autoloading +$listener = new TimestampableListener(); -Read the [documentation](./annotations.md#em-setup) -or check the [example code](../example) -on how to setup and use the extensions in most optimized way. +// The $om is either an instance of the ORM's entity manager or the MongoDB ODM's document manager +$om->getEventManager()->addEventSubscriber($listener); +``` - +### Using a Clock -## Timestampable Entity example: +The timestampable extension supports using a [PSR-20 Clock](https://www.php-fig.org/psr/psr-20/) as the provider for its +timestamps, falling back to creating a new `DateTime` instance when not available. -### Timestampable annotations: -- **@Gedmo\Mapping\Annotation\Timestampable** this annotation tells that this column is timestampable. -By default it updates this column on update. If column is not date, datetime or time -type it will trigger an exception. +To use a clock in the timestampable extension, you can provide one by calling the listener's `setClock` method. -### Timestampable attributes: -- **#[Gedmo\Mapping\Annotation\Timestampable]** this attribute tells that this column is timestampable. - By default it updates this column on update. If column is not date, datetime or time - type it will trigger an exception. +```php +$listener->setClock($clock); +``` -Available configuration options: +## Configuring Timestampable Objects -- **on** - is main option and can be **create, update, change** this tells when it -should be updated -- **field** - only valid if **on="change"** is specified, tracks property or a list of properties for changes -- **value** - only valid if **on="change"** is specified and the tracked field is a single field (not an array), if the tracked field has this **value** +The Itimestampable extension can be configured with [annotations](./annotations.md#timestampable-extension), +[attributes](./attributes.md#timestampable-extension), or XML configuration (matching the mapping of +your domain models). The full configuration for annotations and attributes can be reviewed in +the linked documentation. -**Note:** that Timestampable interface is not necessary, except in cases where -you need to identify entity as being Timestampable. The metadata is loaded only once then -cache is activated +The below examples show the simplest and default configuration for the extension, setting a field +when the model is updated. -### Annotations +### Attribute Configuration ```php id; - } - - public function setTitle($title) - { - $this->title = $title; - } - - public function getTitle() - { - return $this->title; - } - - public function setBody($body) - { - $this->body = $body; - } - - public function getBody() - { - return $this->body; - } - - public function getCreated() - { - return $this->created; - } - - public function getUpdated() - { - return $this->updated; - } - - public function getContentChanged() - { - return $this->contentChanged; - } -} -``` - -### Attributes - -```php #[ORM\Entity] class Article { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: Types::INTEGER)] - private $id; - - #[ORM\Column(name: 'title', type: Types::STRING, length: 128)] - private $title; - - #[ORM\Column(name: 'body', type: Types::STRING)] - private $body; + public ?int $id = null; - /** - * @var \DateTime - */ - #[Gedmo\Timestampable(on: 'create')] - #[ORM\Column(name: 'created', type: Types::DATE_MUTABLE)] - private $created; - - /** - * @var \DateTime - */ - #[ORM\Column(name: 'updated', type: Types::DATETIME_MUTABLE)] + #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] #[Gedmo\Timestampable] - private $updated; - - /** - * @var \DateTime - */ - #[ORM\Column(name: 'content_changed', type: Types::DATETIME_MUTABLE, nullable: true)] - #[Gedmo\Timestampable(on: 'change', field: ['title', 'body'])] - private $contentChanged; - - public function getId() - { - return $this->id; - } - - public function setTitle($title) - { - $this->title = $title; - } - - public function getTitle() - { - return $this->title; - } - - public function setBody($body) - { - $this->body = $body; - } - - public function getBody() - { - return $this->body; - } - - public function getCreated() - { - return $this->created; - } - - public function getUpdated() - { - return $this->updated; - } - - public function getContentChanged() - { - return $this->contentChanged; - } + public ?\DateTimeImmutable $updatedAt = null; } ``` - - -## Timestampable Document example: - -**Note:** this example is using annotations and attributes for mapping, you should use -one of them, not both. - -```php -id; - } - - public function setTitle($title) - { - $this->title = $title; - } - - public function getTitle() - { - return $this->title; - } - - public function setBody($body) - { - $this->body = $body; - } - - public function getBody() - { - return $this->body; - } - - public function getCreated() - { - return $this->created; - } - - public function getUpdated() - { - return $this->updated; - } - - public function getContentChanged() - { - return $this->contentChanged; - } -} -``` - -Now on update and creation these annotated fields will be automatically updated - - - -## Xml mapping example +### XML Configuration ```xml - + - - - - + - - - - - - - - ``` - - -## Advanced examples: - -### Using dependency of property changes +### Annotation Configuration -Add another entity which would represent Article Type: +> [!NOTE] +> Support for annotations is deprecated and will be removed in 4.0. ```php id; - } - - public function setTitle($title) - { - $this->title = $title; - } - - public function getTitle() - { - return $this->title; - } -} -``` - -Now update the Article Entity to reflect published date on Type change: - -```php -type = $type; - } - - public function getId() - { - return $this->id; - } - - public function setTitle($title) - { - $this->title = $title; - } - - public function getTitle() - { - return $this->title; - } - - public function getCreated() - { - return $this->created; - } - - public function getUpdated() - { - return $this->updated; - } - - public function getPublished() - { - return $this->published; - } + public ?\DateTimeImmutable $updatedAt = null; } ``` -Now few operations to get it all done: +### Supported Field Types -```php -setTitle('My Article'); +The timestampable extension supports the following field types for the timestamp field: -$em->persist($article); -$em->flush(); -// article: $created, $updated were set +- Date (`date` and `date_immutable`) +- Time (`time` and `time_immutable`) +- Date/Time (`datetime` and `datetime_immutable`) +- Date/Time with timezone (`datetimetz` and `datetimetz_immutable`) +- Timestamp (`timestamp`) +- Variable Date/Time (`vardatetime`) (Supported by the ORM and DBAL only) +- Integer (`integer` only) -$type = new Type; -$type->setTitle('Published'); +## Using Traits -$article = $em->getRepository('Entity\Article')->findByTitle('My Article'); -$article->setType($type); +The timestampable extension provides traits which can be used to quickly add fields, and optionally the mapping configuration, +for a created at and updated at timestamp to be updated for the **create** and **update** actions. These traits are +provided as a convenience for a common configuration, for other use cases it is suggested you add your own fields and configurations. -$em->persist($article); -$em->persist($type); -$em->flush(); -// article: $published, $updated were set +- `Gedmo\Timestampable\Traits\Timestampable` adds a `$createdAt` and `$updatedAt` property, with getters and setters +- `Gedmo\Timestampable\Traits\TimestampableDocument` adds a `$createdAt` and `$updatedAt` property, with getters and setters + and mapping annotations and attributes for the MongoDB ODM +- `Gedmo\Timestampable\Traits\TimestampableEntity` adds a `$createdAt` and `$updatedAt` property, with getters and setters + and mapping annotations and attributes for the ORM -$article->getPublished()->format('Y-m-d'); // the date article type changed to published -``` +## Logging Changes For Specific Actions -Easy like that, any suggestions on improvements are very welcome +In addition to supporting logging the timestamp for general create and update actions, the extension can also be configured to +log the timestamp for a change for specific fields or values. -### Creating a UTC DateTime type that stores your datetimes in UTC +### Single Field Changed To Specific Value -First, we define our custom data type (note the type name is datetime and the type extends DateTimeType which simply overrides the default Doctrine type): +For example, we want to record the timestamp of when an article is published on a news site. To do this, we add a field to our object +and configure it using the **change** action, specifying the field and value we want it to match. ```php setTimeZone(self::$utc); - - return $value->format($platform->getDateTimeFormatString()); - } - - public function convertToPHPValue($value, AbstractPlatform $platform) - { - if ($value === null) { - return null; - } - - if (is_null(self::$utc)) { - self::$utc = new \DateTimeZone('UTC'); - } + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: Types::INTEGER)] + public ?int $id = null; - $val = \DateTime::createFromFormat($platform->getDateTimeFormatString(), $value, self::$utc); + #[ORM\Column(type: Types::BOOLEAN)] + public bool $published = false; - if (!$val) { - throw ConversionException::conversionFailed($value, $this->getName()); - } + /** + * Field to track the timestamp for the last change made to this article. + */ + #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] + #[Gedmo\Timestampable] + public ?\DateTimeImmutable $updatedAt = null; - return $val; - } + /** + * Field to track the timestamp for when this article was published. + */ + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + #[Gedmo\Timestampable(on: 'change', field: 'published', value: true)] + public ?\DateTimeImmutable $updatedAt = null; } ``` -Now in Symfony, we register and override the **datetime** type. **WARNING:** this will override the **datetime** type for all your entities and for all entities in external bundles or extensions, so if you have some entities that require the standard **datetime** type from Doctrine, you must modify the above type and use a different name (such as **utcdatetime**). Additionally, you'll need to modify **Timestampable** so that it includes **utcdatetime** as a valid type. - -```yaml -doctrine: - dbal: - types: - datetime: Acme\DoctrineExtensions\DBAL\Types\UTCDateTimeType -``` - -And our Entity properties look as expected: +The change action can also be configured to watch for changes on related objects using a dot notation path. In this example, +we log the timestamp for when the article was moved into an archived category. ```php + /** + * Field to track the timestamp for the last change made to this article. + */ + #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] + #[Gedmo\Timestampable] + public ?\DateTimeImmutable $updatedAt = null; -## Traits + /** + * Field to track the timestamp for when this article was archived. + */ + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + #[Gedmo\Timestampable(on: 'change', field: 'category.archived', value: true)] + public ?\DateTimeImmutable $updatedAt = null; +} +``` -You can use timestampable traits for quick **createdAt** **updatedAt** timestamp definitions -when using annotation mapping. -There is also a trait without annotations for easy integration purposes. +### One of Many Fields Changed -**Note:** this feature is only available since php **5.4.0**. And you are not required -to use the Traits provided by extensions. +The extension can also update a timestampable field when using the **change** action by specifying a list of fields to watch. +This also supports the dotted path notation, allowing you to watch changes on the model itself as well as related data. ```php