, (*1)
Version
A CakePHP 4.x plugin that facilitates versioned database entities, (*2)
Installation
Add the following lines to your application's composer.json
:, (*3)
"require": {
"josegonzalez/cakephp-version": "dev-master"
}
followed by the command:, (*4)
composer update
, (*5)
Or run the following command directly without changing your composer.json
:, (*6)
composer require josegonzalez/cakephp-version:dev-master
, (*7)
Usage
In your app's config/bootstrap.php
add:, (*8)
Plugin::load('Josegonzalez/Version', ['bootstrap' => true]);
Usage
Run the following schema migration:, (*9)
CREATE TABLE `version` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`version_id` int(11) DEFAULT NULL,
`model` varchar(255) NOT NULL,
`foreign_key` int(10) NOT NULL,
`field` varchar(255) NOT NULL,
`content` text NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Note that the content
field must be nullable if you want to be able to version any nullable fields in your application., (*10)
You may optionally add a version_id
field of type integer
to the table which is being versioned. This will store the latest version number of a given page., (*11)
If you wish to create the table using cakephp/migrations
then you will need to use a migration that looks something like this:, (*12)
<?php
use Phinx\Migration\AbstractMigration;
class CreateVersions extends AbstractMigration
{
public function change()
{
$this->table('version')
->addColumn('version_id', 'integer', ['null' => true])
->addColumn('model', 'string')
->addColumn('foreign_key', 'integer')
->addColumn('field', 'string')
->addColumn('content', 'text', ['null' => true])
->addColumn('created', 'datetime')
->create();
}
}
Add the following line to your entities:, (*13)
use \Josegonzalez\Version\Model\Behavior\Version\VersionTrait;
And then include the trait in the entity class:, (*14)
class PostEntity extends Entity {
use VersionTrait;
}
Attach the behavior in the models you want with:, (*15)
public function initialize(array $config) {
$this->addBehavior('Josegonzalez/Version.Version');
}
Whenever an entity is persisted - whether via insert or update - that entity is also persisted to the version
table. You can access a given revision by executing the following code:, (*16)
// Will contain a generic `Entity` populated with data from the specified version.
$version = $entity->version(1);
You can optionally retrieve all the versions:, (*17)
$versions = $entity->versions();
cakephp-version
dispatches an event Model.Version.beforeSave
which you can optionally handle to attach additional meta-data about the version., (*18)
Add the necessary additional fields to your migration, for example:, (*19)
CREATE TABLE `version` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`version_id` int(11) DEFAULT NULL,
`model` varchar(255) NOT NULL,
`foreign_key` int(10) NOT NULL,
`field` varchar(255) NOT NULL,
`content` text,
`created` datetime NOT NULL,
`custom_field1` varchar(255) NOT NULL, /* column to store our metadata */
`custom_field2` varchar(255) NOT NULL, /* column to store our metadata */
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Then define an event listener to handle the event and pass in additional metadata, for example:, (*20)
use Cake\Event\Event;
use Cake\Event\EventListenerInterface;
class VersionListener implements EventListenerInterface {
public function implementedEvents() {
return array(
'Model.Version.beforeSave' => 'insertAdditionalData',
);
}
public function insertAdditionalData(Event $event) {
return [
'custom_field1' => 'foo',
'custom_field2' => 'bar'
];
}
}
Your event listener can then be attached in your project, for example:, (*21)
use App\Event\VersionListener;
use Cake\Event\EventManager;
$VersionListener = new VersionListener();
EventManager::instance()->on($VersionListener);
Note that handling this event also allows you to modify/overwrite values generated by the plugin.
This can provide useful functionality, but ensure that if your event listener returns array keys called
version_id
, model
, foreign_key
, field
, content
or created
that this is the intended behavior., (*22)
To store the user_id
as additional meta data is easiest in combination with Muffin/Footprint.
The above insertAdditionalData()
method could then look like this:, (*23)
/**
* @param \Cake\Event\Event $event
*
* @return array
*/
public function insertAdditionalData(Event $event)
{
$data = [
...
];
if ($event->data('_footprint')) {
$user = $event->data('_footprint');
$data += [
'user_id' => $user->id,
];
}
return $data;
}
Any controller with the FootprintAwareTrait
used will then provide the _footprint
data into the model layer for this event callback to use., (*24)
Bake Integration
If you load the plugin using 'bootstrap' => true
, this plugin can be used to autodetect usage via the properly named database table. To do so, simply create a table with the version
schema above named after the table you'd like to revision plus the suffix _versions
. For instance, to version the following table:, (*25)
CREATE TABLE `posts` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`category_id` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`status` varchar(255) NOT NULL DEFAULT 'published',
`visibility` varchar(255) NOT NULL DEFAULT 'public',
`title` varchar(255) NOT NULL DEFAULT '',
`route` varchar(255) DEFAULT NULL,
`content` text,
`published_date` datetime DEFAULT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Create the following table:, (*26)
CREATE TABLE `posts_versions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`version_id` int(11) NOT NULL,
`model` varchar(255) NOT NULL,
`foreign_key` int(11) NOT NULL,
`field` varchar(255) NOT NULL,
`content` text,
`created` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
You can create a migration for this with the following bake command:, (*27)
bin/cake bake migration create_posts_versions version_id:integer model foreign_key:integer field content:text created
You'll also want to set the content
field in this migration to nullable, otherwise you won't be able to version fields that can be nulled., (*28)
To track the current version in the posts
table, you can create a migration to add the version_id
field to the table:, (*29)
bin/cake bake migration add_version_id_to_posts version_id:integer
Configuration
There are five behavior configurations that may be used:, (*30)
-
versionTable
: (Default: version
) The name of the table to be used to store versioned data. It may be useful to use a different table when versioning multiple types of entities.
-
versionField
: (Default: version_id
) The name of the field in the versioned table that will store the current version. If missing, the plugin will continue to work as normal.
-
additionalVersionFields
: (Default ['created']
) The additional or custom fields of the versioned table to be exposed as well. By default prefixed with version_
, e.g. 'version_user_id'
for 'user_id'
.
-
referenceName
: (Default: db table name) Discriminator used to identify records in the version table.
-
onlyDirty
: (Default: false) Set to true to version only dirty properties.