{"id":43666080,"url":"https://github.com/scherersoftware/cake-model-history","last_synced_at":"2026-02-04T22:45:28.836Z","repository":{"id":26557163,"uuid":"30010957","full_name":"scherersoftware/cake-model-history","owner":"scherersoftware","description":"CakePHP 3 Historization for database records","archived":false,"fork":false,"pushed_at":"2017-10-17T08:30:32.000Z","size":286,"stargazers_count":6,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-22T22:48:11.605Z","etag":null,"topics":["cakephp","cakephp-plugin","cakephp3"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scherersoftware.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-01-29T08:29:30.000Z","updated_at":"2018-11-16T02:14:22.000Z","dependencies_parsed_at":"2022-08-21T04:10:57.182Z","dependency_job_id":null,"html_url":"https://github.com/scherersoftware/cake-model-history","commit_stats":null,"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/scherersoftware/cake-model-history","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scherersoftware%2Fcake-model-history","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scherersoftware%2Fcake-model-history/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scherersoftware%2Fcake-model-history/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scherersoftware%2Fcake-model-history/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scherersoftware","download_url":"https://codeload.github.com/scherersoftware/cake-model-history/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scherersoftware%2Fcake-model-history/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29098252,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-04T22:44:52.815Z","status":"ssl_error","status_checked_at":"2026-02-04T22:44:16.428Z","response_time":62,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cakephp","cakephp-plugin","cakephp3"],"created_at":"2026-02-04T22:45:26.385Z","updated_at":"2026-02-04T22:45:28.822Z","avatar_url":"https://github.com/scherersoftware.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"![CakePHP 3 Model History Plugin](https://raw.githubusercontent.com/scherersoftware/cake-model-history/master/model-history.png)\n\n[![Build Status](https://travis-ci.org/scherersoftware/cake-model-history.svg?branch=master)](https://travis-ci.org/scherersoftware/cake-model-history)\n[![codecov](https://codecov.io/gh/scherersoftware/cake-model-history/branch/master/graph/badge.svg)](https://codecov.io/gh/scherersoftware/cake-model-history)\n[![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.txt)\n\nCakePHP 3 Historization for database records. Keeps track of changes performed by users and provides a customizable view element for displaying them.\n\n## Requirements\n\n- [scherersoftware cake-cktools](https://github.com/scherersoftware/cake-cktools) for JSON to MEDIUMBLOB type mapping\n- [Font Awesome](https://fortawesome.github.io/Font-Awesome/) panel navigation buttons\n\n## Installation\n\n#### 1. require the plugin in your `composer.json`\n\n```\n\"require\": {\n    \"codekanzlei/cake-model-history\": \"2.0.*\",\n}\n```\n\nOpen a terminal in your project-folder and run these commands:\n\n    `$ composer update`\n\n#### 2. Configure `config/bootstrap.php`\n\n**Load** the Plugin:\n\n```\nPlugin::load('ModelHistory', ['bootstrap' =\u003e false, 'routes' =\u003e true]);\n```\n\nSince all changes to a record are saved to the field `data` (type `MEDIUMBLOB`) in the `ModelHistoryTable` in JSON format, you must use custom **Type Mapping**.\n\n```\nType::map('json', 'CkTools\\Database\\Type\\JsonType');\n```\n\n\n#### 3. Create a table `model_history` in your project database\nWe have to create the database schema with help of the migrations plugin.\n\n```\n    $ bin/cake migrations migrate -p ModelHistory\n```\n\n#### 4. AppController.php\n\n**$helpers**\n\n```\npublic $helpers =  [\n    'ModelHistory.ModelHistory'\n]\n```\n\n\n## Usage \u0026 Configuration:\n\n#### Table setup\nAdd the Historizable Behavior in the `initialize` function of the **Table** you want to use model-history.\n\n```\n$this-\u003eaddBehavior('ModelHistory.Historizable');\n```\n\n**Note:** By default, the model-history plugin attributes changes to a database record to the user that performed and saved them by comparing table-fields 'firstname' and 'lastname' in `UsersTable` (See `$_defaultConfig` in `HistorizableBehavior.php` for these default settings). If your fields are not called 'firstname' and 'lastname', you can easily customize these settings according to the fieldnames in your UsersTable, like so:\n\n```\n$this-\u003eaddBehavior('ModelHistory.Historizable', [\n    'userNameFields' =\u003e [\n        'firstname' =\u003e 'User.your_first_name_field',\n        'lastname' =\u003e 'Users.your_last_name_field',\n        'id' =\u003e 'Users.id'\n    ],\n    'userIdCallback' =\u003e null,\n    'entriesToShow' =\u003e 10,\n    'fields' =\u003e [],\n    'associations' =\u003e [],\n    'ignoreFields' =\u003e []\n]);\n```\n\nBy default, the ModelHistory monitors changes for every field it finds in the schema of the table.\nIt tries to deduct the type to use from the data type and obfuscates all fields containing \"password\".\nOtherwise, the default values are `'searchable' =\u003e true`, `'saveable' =\u003e true` and `'obfuscated' =\u003e false`.\n\nTo override specific settings, add an array for the field into the 'fields' array and list the values you want to override.\n\n```\n$this-\u003eaddBehavior('ModelHistory.Historizable', [\n    'fields' =\u003e [\n    // The field name\n        'firstname' =\u003e[\n            // Allowed translation forms: String, Closure returning string\n            // Its recommended to use the closure, so translations are made after core initialize when the correct language was set.\n            'translation' =\u003e function () {\n                return __('user.firstname');\n            },\n            // The searchable indicator is used to show the field in the filter box\n            // defaults to true\n            'searchable' =\u003e true,\n            // The savable indicator is used to decide whether the field is tracked\n            // defaults to true\n            'saveable' =\u003e true,\n            // obfuscate the values of the field in the history view.\n            // defaults to false except for fieldnames containing \"password\"\n            'obfuscated' =\u003e false,\n            // Allowed: string, bool, number, relation, date, hash, array, association, mass_association.\n            'type' =\u003e 'string',\n            // Optional display parser to modify the value before displaying it,\n            // if no displayParser is found, the \\ModelHistory\\Model\\Transform\\{$type}Transformer is used.\n            'displayParser' =\u003e function ($fieldname, $value, $entity) {\n                return $value;\n            },\n            // Optional save parser to modify the value before saving the history entry\n            'saveParser' =\u003e function ($fieldname, $value, $entity) {\n                return $value;\n            },\n        ],\n    ]\n]);\n```\n\nBecause the default monitored fields are limited to those in the table, n:m associations and associations\nwith foreign_keys in other tables always have to be configured, at least with the type.\nHere are three real world examples from a UsersTable:\n\n```\n$this-\u003eaddBehavior('ModelHistory.Historizable', [\n    'fields' =\u003e [\n        'customer_id' =\u003e [\n            'saveable' =\u003e false,\n            'type' =\u003e 'association'\n        ],\n        'regions' =\u003e [\n            'type' =\u003e 'mass_association',\n            'displayParser' =\u003e function ($fieldName, $value, $entity) {\n                if (is_array($value)) {\n                    return implode(', ', $value);\n                }\n\n                return $value;\n            }\n        ],\n        'contact' =\u003e [\n            'type' =\u003e 'string',\n            'saveParser' =\u003e function ($fieldName, $value, $entity) {\n                return __('user.associated_contact');\n            },\n            'displayParser' =\u003e function ($fieldName, $value, $entity) {\n                return __('user.associated_contact');\n            }\n        ],\n    ]\n]);\n```\n\n**Types:**\n\n- `string`: for string values.\n- `bool`: for bool values.\n- `number`: for integer values.\n- `date`: for date values.\n- `hash`: for associative arrays.\n- `array`: for sequential (indexed) arrays.\n- `relation`: for 1 to n relations.\n- `association`: for n to m relations.\n- `mass_association`: for n to m relations which shall be condensed into one history entry.\n\nThe three types `relation`, `association` and `mass_association` are able to link to associated entities. The url defaults to:\n\n```\n'url' =\u003e [\n    'plugin' =\u003e null,\n    'controller' =\u003e $entityController // automatically set to the entities controller\n    'action' =\u003e 'view'\n]\n```\n\nThe default url can be overwritten: `url` in the behavior-array overwrites default and the defined url in the field-config has the highest priority.\n\n```\n$this-\u003eaddBehavior('ModelHistory.Historizable', [\n    'url' =\u003e [\n        'plugin' =\u003e 'Admin',\n        'action' =\u003e 'index'\n    ],\n    'fields' =\u003e [\n        'firstname' =\u003e [\n            'translation' =\u003e function () {\n                return __('user.firstname');\n            },\n            'searchable' =\u003e true,\n            'saveable' =\u003e true,\n            'obfuscated' =\u003e false,\n            'type' =\u003e 'relation',\n            'url' =\u003e [\n                'plugin' =\u003e 'Special',\n                'action' =\u003e 'show'\n            ]\n        ],\n    ]\n]);\n```\n\nIf saved association's entries shall be viewed within the source entities entries, you can specify the `associations` key. It has to match the object property keys given in the\nsource entity. Furthermore the model history area has to get the option `includeAssociated` with value `true`.\n\n```\n$this-\u003eaddBehavior('ModelHistory.Historizable', [\n    'fields' =\u003e [\n        ...\n    ],\n    'associations' =\u003e [\n        'contact',\n        'contact.address'\n    ]\n]);\n```\n\nTo further specify the context in which the entity was saved and in order to gather additional information, you can implement `\\ModelHistory\\Model\\Entity\\HistoryContextTrait` inside your entity. You have to call `setHistoryContext` on the entity to add the context information.\nCurrently there are three context types: `ModelHistory::CONTEXT_TYPE_CONTROLLER`, `ModelHistory::CONTEXT_TYPE_SHELL` and `ModelHistory::CONTEXT_TYPE_SLUG`.\nThe optional slug gets translated automatically, when its defined in the entities `typeDescriptions`.\n\n```\n    /**\n     * Index action of a controller\n     */\n    public function index()\n    {\n        if ($this-\u003erequest-\u003eis(['post'])) {\n            $entity = $this-\u003eTable-\u003enewEntity($this-\u003erequest-\u003edata);\n            $entity-\u003esetHistoryContext(ModelHistory::CONTEXT_TYPE_CONTROLLER, $this-\u003erequest, 'optional_slug');\n            $this-\u003eTable-\u003esave($entity);\n        }\n    }\n\n```\n\nYou can also specify a context getter inside an entity to search for defined contexts. Please keep in mind that you have to use the `TypeAwareTrait` from the `CkTools`:\n\n```\n    use \\CkTools\\Utility\\TypeAwareTrait;\n\n    /**\n     * Retrieve defined contexts\n     *\n     * @return void\n     */\n    public static function getContexts()\n    {\n        return self::getTypeMap(\n            self::CONTEXT_TYPE_FORGOT_PASSWORD\n        );\n    }\n```\n\n\n**Ignoring fields**\n\nBy default, the fields `id`, `created` and `modified` are not tracked.\nIf you want to overwrite which fields are ignored or not, give `ignoreFields` in the config array and only those fields will be ignored:\n\n```\n$this-\u003eaddBehavior('ModelHistory.Historizable', [\n    'ignoreFields' =\u003e [\n        'secret_field',\n        'created'\n    ]\n]);\n```\n\n#### View setup\nUse `ModelHistoryHelper.php` to create neat view elements containing a record's change history with one call in your view:\n\n```\n\u003c?= $this-\u003eModelHistory-\u003emodelHistoryArea($user); ?\u003e\n```\n\n`modelHistoryArea` has the following **Options:**\n\n- `showCommentBox` (false)\n\n    Additionally renders an comment field (input type text). User input will be saved to the model_history table\n\n- `showFilterBox` (false)\n\n    Additionally renders a filter box which can be used to search for entries.\n\n- `includeAssociated` (false)\n\n    Additionally include all associations saved.\n\nFor the modelHistoryArea to fetch its data, add the 'ModelHistory' component to the baseComponents property in your Frontend.AppController under `/webroot/js/app/app_controller.js`.\nIf you haven't set up the FrontendBridge yet, follow [these steps](https://github.com/scherersoftware/cake-frontend-bridge). There you will also find a template for this file.\n\nMake sure `app_controller.js` is loaded on the page where you want to show the modelHistoryArea.\nThen the ModelHistory JS-Component will make AJAX requests to /model_history/ModelHistory/index/$modelName/$primaryKey according to the $entity you gave the helper method and populate the modelHistoryArea by itself.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscherersoftware%2Fcake-model-history","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscherersoftware%2Fcake-model-history","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscherersoftware%2Fcake-model-history/lists"}