Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
closes #2215
- Loading branch information
Showing
2 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<?php | ||
/** | ||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) | ||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* | ||
* Licensed under The MIT License | ||
* For full copyright and license information, please see the LICENSE.txt | ||
* Redistributions of files must retain the above copyright notice. | ||
* | ||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* @link http://cakephp.org CakePHP(tm) Project | ||
* @since CakePHP(tm) v 3.0.0 | ||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php) | ||
*/ | ||
namespace Cake\Model\Behavior; | ||
|
||
use Cake\Event\Event; | ||
use Cake\ORM\Behavior; | ||
use Cake\ORM\Entity; | ||
use Cake\ORM\Table; | ||
|
||
class TimestampBehavior extends Behavior { | ||
|
||
/** | ||
* Default settings | ||
* | ||
* These are merged with user-provided settings when the behavior is used. | ||
* | ||
* refreshTimestamp - if true (the default) the timestamp used will be the current time when | ||
* the code is executed, to set to an explicit date time value - set refreshTimetamp to false | ||
* and call setTimestamp() on the behavior class before use. | ||
* | ||
* @var array | ||
*/ | ||
protected $_defaultSettings = [ | ||
'fields' => [ | ||
'created' => 'created', | ||
'updated' => 'modified' | ||
], | ||
'refreshTimestamp' => true | ||
]; | ||
|
||
/** | ||
* Current timestamp | ||
* | ||
* @var \DateTime | ||
*/ | ||
protected $_ts; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* Merge settings with the default and store in the settings property | ||
* | ||
* @param Table $table The table this behavior is attached to. | ||
* @param array $settings The settings for this behavior. | ||
*/ | ||
public function __construct(Table $table, array $settings = []) { | ||
$this->_settings = $settings + $this->_defaultSettings; | ||
} | ||
|
||
/** | ||
* beforeSave | ||
* | ||
* There is only one event handler, it can be configured to be called for any event | ||
* | ||
* @param Event $event | ||
* @param Entity $entity | ||
* @return true (irrespective of the behavior logic, the save will not be prevented) | ||
*/ | ||
public function beforeSave(Event $event, Entity $entity) { | ||
$settings = $this->settings(); | ||
$new = $entity->isNew() !== false; | ||
|
||
if ($new) { | ||
$this->_updateField($entity, $settings['fields']['created'], $settings['refreshTimestamp']); | ||
} | ||
$this->_updateField($entity, $settings['fields']['updated'], $settings['refreshTimestamp']); | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* getTimestamp | ||
* | ||
* Gets the current timestamp. If $refreshTimestamp is not truthy, the existing timestamp will be | ||
* returned | ||
* | ||
* @return \DateTime | ||
*/ | ||
public function getTimestamp($refreshTimestamp = null) { | ||
if ($this->_ts === null || $refreshTimestamp) { | ||
$this->setTimestamp(); | ||
} | ||
|
||
return $this->_ts; | ||
} | ||
|
||
/** | ||
* setTimestamp | ||
* | ||
* Set the timestamp to the given DateTime object, or if not passed a new DateTime object | ||
* | ||
* @param int $ts | ||
* @return void | ||
*/ | ||
public function setTimestamp(\DateTime $ts = null) { | ||
if ($ts === null) { | ||
$ts = new \DateTime(); | ||
} | ||
$this->_ts = $ts; | ||
} | ||
|
||
/** | ||
* Update a field, if it hasn't been updated already | ||
* | ||
* @param Entity $entity | ||
* @param string $field | ||
* @param bool $refreshTimestamp | ||
* @return void | ||
*/ | ||
protected function _updateField(Entity $entity, $field, $refreshTimestamp) { | ||
if ($entity->dirty($field)) { | ||
return; | ||
} | ||
$entity->set($field, $this->getTimestamp($refreshTimestamp)); | ||
} | ||
} |
263 changes: 263 additions & 0 deletions
263
Cake/Test/TestCase/Model/Behavior/TimestampBehaviorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
<?php | ||
/** | ||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) | ||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* | ||
* Licensed under The MIT License | ||
* For full copyright and license information, please see the LICENSE.txt | ||
* Redistributions of files must retain the above copyright notice. | ||
* | ||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* @link http://cakephp.org CakePHP(tm) Project | ||
* @since CakePHP(tm) v 3.0.0 | ||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php) | ||
*/ | ||
namespace Cake\Test\TestCase\Model\Behavior; | ||
|
||
use Cake\Event\Event; | ||
use Cake\Model\Behavior\TimestampBehavior; | ||
use Cake\ORM\Entity; | ||
use Cake\TestSuite\TestCase; | ||
|
||
/** | ||
* Behavior test case | ||
*/ | ||
class TimestampBehaviorTest extends TestCase { | ||
|
||
/** | ||
* Sanity check Implemented events | ||
* | ||
* @return void | ||
*/ | ||
public function testImplementedEvents() { | ||
$table = $this->getMock('Cake\ORM\Table'); | ||
$this->Behavior = new TimestampBehavior($table); | ||
|
||
$expected = [ | ||
'Model.beforeSave' => 'beforeSave' | ||
]; | ||
$this->assertEquals($expected, $this->Behavior->implementedEvents()); | ||
} | ||
|
||
/** | ||
* testCreatedAbsent | ||
* | ||
* @return void | ||
*/ | ||
public function testCreatedAbsent() { | ||
$table = $this->getMock('Cake\ORM\Table'); | ||
$this->Behavior = new TimestampBehavior($table, ['refreshTimestamp' => false]); | ||
$ts = new \DateTime('2000-01-01'); | ||
$this->Behavior->setTimestamp($ts); | ||
|
||
$event = new Event('Model.beforeSave'); | ||
$entity = new Entity(['name' => 'Foo']); | ||
|
||
$return = $this->Behavior->beforeSave($event, $entity); | ||
$this->assertTrue($return, 'Handle Event is expected to always return true'); | ||
$this->assertSame($ts, $entity->created, 'Created timestamp is expected to be the mocked value'); | ||
} | ||
|
||
/** | ||
* testCreatedPresent | ||
* | ||
* @return void | ||
*/ | ||
public function testCreatedPresent() { | ||
$table = $this->getMock('Cake\ORM\Table'); | ||
$this->Behavior = new TimestampBehavior($table, ['refreshTimestamp' => false]); | ||
$ts = new \DateTime('2000-01-01'); | ||
$this->Behavior->setTimestamp($ts); | ||
|
||
$event = new Event('Model.beforeSave'); | ||
$existingValue = new \DateTime('2011-11-11'); | ||
$entity = new Entity(['name' => 'Foo', 'created' => $existingValue]); | ||
|
||
$return = $this->Behavior->beforeSave($event, $entity); | ||
$this->assertTrue($return, 'Handle Event is expected to always return true'); | ||
$this->assertSame($existingValue, $entity->created, 'Created timestamp is expected to be unchanged'); | ||
} | ||
|
||
/** | ||
* testCreatedNotNew | ||
* | ||
* @return void | ||
*/ | ||
public function testCreatedNotNew() { | ||
$table = $this->getMock('Cake\ORM\Table'); | ||
$this->Behavior = new TimestampBehavior($table, ['refreshTimestamp' => false]); | ||
$ts = new \DateTime('2000-01-01'); | ||
$this->Behavior->setTimestamp($ts); | ||
|
||
$event = new Event('Model.beforeSave'); | ||
$entity = new Entity(['name' => 'Foo']); | ||
$entity->isNew(false); | ||
|
||
$return = $this->Behavior->beforeSave($event, $entity); | ||
$this->assertTrue($return, 'Handle Event is expected to always return true'); | ||
$this->assertNull($entity->created, 'Created timestamp is expected to be untouched if the entity is not new'); | ||
} | ||
|
||
/** | ||
* testModifiedAbsent | ||
* | ||
* @return void | ||
*/ | ||
public function testModifiedAbsent() { | ||
$table = $this->getMock('Cake\ORM\Table'); | ||
$this->Behavior = new TimestampBehavior($table, ['refreshTimestamp' => false]); | ||
$ts = new \DateTime('2000-01-01'); | ||
$this->Behavior->setTimestamp($ts); | ||
|
||
$event = new Event('Model.beforeSave'); | ||
$entity = new Entity(['name' => 'Foo']); | ||
$entity->isNew(false); | ||
|
||
$return = $this->Behavior->beforeSave($event, $entity); | ||
$this->assertTrue($return, 'Handle Event is expected to always return true'); | ||
$this->assertSame($ts, $entity->modified, 'Modified timestamp is expected to be the mocked value'); | ||
} | ||
|
||
/** | ||
* testModifiedPresent | ||
* | ||
* @return void | ||
*/ | ||
public function testModifiedPresent() { | ||
$table = $this->getMock('Cake\ORM\Table'); | ||
$this->Behavior = new TimestampBehavior($table, ['refreshTimestamp' => false]); | ||
$ts = new \DateTime('2000-01-01'); | ||
$this->Behavior->setTimestamp($ts); | ||
|
||
$event = new Event('Model.beforeSave'); | ||
$existingValue = new \DateTime('2011-11-11'); | ||
$entity = new Entity(['name' => 'Foo', 'modified' => $existingValue]); | ||
$entity->clean(); | ||
$entity->isNew(false); | ||
|
||
$return = $this->Behavior->beforeSave($event, $entity); | ||
$this->assertTrue($return, 'Handle Event is expected to always return true'); | ||
$this->assertSame($ts, $entity->modified, 'Modified timestamp is expected to be updated'); | ||
} | ||
|
||
/** | ||
* testGetTimestamp | ||
* | ||
* @return void | ||
*/ | ||
public function testGetTimestamp() { | ||
$table = $this->getMock('Cake\ORM\Table'); | ||
$this->Behavior = new TimestampBehavior($table); | ||
|
||
$property = new \ReflectionProperty('Cake\Model\Behavior\TimestampBehavior', '_ts'); | ||
$property->setAccessible(true); | ||
|
||
$this->assertNull($property->getValue($this->Behavior), 'Should be null be default'); | ||
|
||
$return = $this->Behavior->getTimestamp(); | ||
$this->assertInstanceOf( | ||
'DateTime', | ||
$return, | ||
'After calling for the first time, should be a date time object' | ||
); | ||
|
||
return $this->Behavior; | ||
} | ||
|
||
/** | ||
* testGetTimestampPersists | ||
* | ||
* @depends testGetTimestamp | ||
* @return void | ||
*/ | ||
public function testGetTimestampPersists($behavior) { | ||
$this->Behavior = $behavior; | ||
|
||
$property = new \ReflectionProperty('Cake\Model\Behavior\TimestampBehavior', '_ts'); | ||
$property->setAccessible(true); | ||
|
||
$initialValue = $property->getValue($this->Behavior); | ||
$this->Behavior->getTimestamp(); | ||
$postValue = $property->getValue($this->Behavior); | ||
|
||
$this->assertSame( | ||
$initialValue, | ||
$postValue, | ||
'The timestamp should be exactly the same value' | ||
); | ||
} | ||
|
||
/** | ||
* testGetTimestampRefreshes | ||
* | ||
* @depends testGetTimestamp | ||
* @return void | ||
*/ | ||
public function testGetTimestampRefreshes($behavior) { | ||
$this->Behavior = $behavior; | ||
|
||
$property = new \ReflectionProperty('Cake\Model\Behavior\TimestampBehavior', '_ts'); | ||
$property->setAccessible(true); | ||
|
||
$initialValue = $property->getValue($this->Behavior); | ||
$this->Behavior->getTimestamp(true); | ||
$postValue = $property->getValue($this->Behavior); | ||
|
||
$this->assertNotSame( | ||
$initialValue, | ||
$postValue, | ||
'The timestamp should be a different object if refreshTimestamp is truthy' | ||
); | ||
} | ||
|
||
/** | ||
* testSetTimestampDefault | ||
* | ||
* @return void | ||
*/ | ||
public function testSetTimestampDefault() { | ||
$table = $this->getMock('Cake\ORM\Table'); | ||
$this->Behavior = new TimestampBehavior($table); | ||
|
||
$this->Behavior->setTimestamp(); | ||
|
||
$property = new \ReflectionProperty('Cake\Model\Behavior\TimestampBehavior', '_ts'); | ||
$property->setAccessible(true); | ||
$set = $property->getValue($this->Behavior); | ||
|
||
$this->assertInstanceOf( | ||
'DateTime', | ||
$set, | ||
'After calling for the first time, should be a date time object' | ||
); | ||
} | ||
|
||
/** | ||
* testSetTimestampExplicit | ||
* | ||
* @return void | ||
*/ | ||
public function testSetTimestampExplicit() { | ||
$table = $this->getMock('Cake\ORM\Table'); | ||
$this->Behavior = new TimestampBehavior($table); | ||
|
||
$ts = new \DateTime(); | ||
$this->Behavior->setTimestamp($ts); | ||
|
||
$property = new \ReflectionProperty('Cake\Model\Behavior\TimestampBehavior', '_ts'); | ||
$property->setAccessible(true); | ||
$set = $property->getValue($this->Behavior); | ||
|
||
$this->assertInstanceOf( | ||
'DateTime', | ||
$set, | ||
'After calling for the first time, should be a date time object' | ||
); | ||
|
||
$this->assertSame( | ||
$ts, | ||
$set, | ||
'Should have set the same object passed in' | ||
); | ||
} | ||
} |