Skip to content

Instantly share code, notes, and snippets.

@deizel
Created March 29, 2011 17:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deizel/892829 to your computer and use it in GitHub Desktop.
Save deizel/892829 to your computer and use it in GitHub Desktop.
Handles saving only fields that have been changed in a record by the logged in user
<?php
/**
* SaveDiff component
*
* Handles saving only fields that have been changed in a record by the logged in user.
*
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
class SaveDiffComponent extends Object {
/**
* Enabled flag.
*
* Prevents component from running unless certain criteria are met.
*
* @var boolean
*/
public $enabled = null;
/**
* Other components used.
*
* @var array
*/
public $components = array(
'Session', # This component relies on session storage.
);
/**
* Default options array.
*
* Contains an array of actions the component is enabled during.
*
* @var array
* @todo Use in the real world and decide if other options are needed.
*/
public $options = array(
# list of controller action this component is enabled during
'actions' => array(), # default: none
# template for session path where original form data is stored (`String::insert`-compatible)
'sessionKey' => 'SaveDiff.:modelClass.:modelId', # eg. 'SaveDiff.User.123'
);
/**
* Component initialize method.
*
* Called before `Controller::beforeFilter()`.
*
* @param object $controller Calling controller.
* @param array $options Optional settings.
* @return void
*/
public function initialize(&$controller, $options = array()) {
# merge in user defined options from controller level
$this->options = array_merge($this->options, $options);
# detect if controller should be enabled for this action.
$this->enabled = in_array($controller->action, $this->options['actions']);
}
/**
* Component shotdown method.
*
* Called after `Controller::render()`. `$controller->data` should have outgoing data.
*
* @param object $controller Calling controller.
* @return void
*/
public function shutdown(&$controller) {
# check component is enabled
if (!$this->enabled) {
return;
}
# check for validation failure (in case this is newly submitted data)
$valid = empty($controller->{$controller->modelClass}->validationErrors);
# generate a session key for record in data
$sessionKey = $this->generateSessionKey($controller);
# save form data to session if key was determined
if ($valid && $sessionKey) {
$originalData = $this->Session->write($sessionKey, $controller->data);
}
}
/**
* Generate session key.
*
* @param object $controller Calling controller.
* @return string Session key if record ID was determined, otherwise false.
*/
public function generateSessionKey(&$controller) {
# get primary model name
$modelClass = $controller->modelClass;
# controller has no models
if (!$modelClass) {
return false;
}
# get name of model's primary key field (default is `id`)
$modelPrimaryKey = $controller->{$modelClass}->primaryKey;
# check data and record id exists
if (empty($controller->data[$modelClass][$modelPrimaryKey])) {
return false;
}
# get the id of the model we are editing
$modelId = $controller->data[$modelClass][$modelPrimaryKey];
# create session storage key
return String::insert($this->options['sessionKey'], compact('modelClass', 'modelId'));
}
/**
* Component startup method.
*
* Called after `Controller::beforeFilter()`, before action is called. `$controller->data` should have incoming data.
*
* @param object $controller Calling controller.
* @param array $options Optional settings.
* @return void
*/
public function startup(&$controller) {
# check component is enabled
if (!$this->enabled) {
return;
}
# generate a session key for record in data
$sessionKey = $this->generateSessionKey($controller);
# check if data has been submitted
if ($sessionKey) {
# get original stored data
$originalData = $this->Session->read($sessionKey);
# get newly submitted data
$submittedData = $controller->data;
# extract all new changes
$this->extractChanges($submittedData, $originalData);
}
}
/**
* Compare two arrays and extract only changed data.
*
* @param array $newArray New data to remove unchanged data from.
* @param array $oldArray Existing data.
*/
public function extractChanges(&$newArray, &$oldArray) {
# loop over array of new data
foreach ($newArray as $key => $val) {
# check item keys exist in other array before doing anything
if (!array_key_exists($key, $oldArray)) {
continue;
}
# if the value is an array, be recursive
if (is_array($val)) {
$this->extractChanges($newArray[$key], $oldArray[$key]);
}
# if the old data is the same as the new, delete the key/value pair
if ($newArray[$key] == $oldArray[$key]) {
unset($newArray[$key]);
}
}
}
/**
* Called before Controller::redirect().
*
* @param object $controller Controller with components to beforeRedirect
* @return void
*/
public function beforeRedirect(&$controller, $url, $status = null, $exit = true) {
return true;
# comment above line to prevent redirects and inspect data
debug($controller->data);
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment