Skip to content

Instantly share code, notes, and snippets.

@pvolyntsev
Last active May 4, 2023 20:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pvolyntsev/c85847b888d71e89beb0 to your computer and use it in GitHub Desktop.
Save pvolyntsev/c85847b888d71e89beb0 to your computer and use it in GitHub Desktop.
Yii Active Record instance with "ifModified then ..." logic and dependencies clearing
<?php
/**
* Class ModActiveRecord is the base class for classes representing relational data.
* It provides methods to check if attributes were modified
*
* @author pavel.volyntsev@gmail.com
* @link http://copi.st/JtvJ
* @link http://www.yiiframework.com/extension/mod-active-record/
*
* @usage see sample_01_clear_relations.php and sample_02_beforeSave_ifModified.php
*/
class ModActiveRecord extends CActiveRecord
{
protected $_modified = array();
/**
* Sets the named attribute value.
* Also mark this attribute as modified
* @see CActiveRecord::setAttribute
*/
public function setAttribute($name,$value)
{
$oldValue = $this->getAttribute($name);
if ($retValue = parent::setAttribute($name,$value))
{
if (!isset($this->_modified[$name]))
$this->_modified[$name] = array('new' => $value, 'old' => $oldValue);
else
$this->_modified[$name]['new'] = $value;
if ($this->_modified[$name]['new'] == $this->_modified[$name]['old'])
unset($this->_modified[$name]);
// clear relations that are depends on modified attribute
/** @var \CActiveRelation $relation */
foreach($this->getMetaData()->relations as $relationName => $relation)
if ($name == $relation->foreignKey)
unset($this->$relationName);
}
return $retValue;
}
/**
* Returns true if attribute $name was modified. If $name not set, returns true if any attribute were modified
*/
public function getIsModified($name = null)
{
if (is_null($name))
return !empty($this->_modified);
else
return isset($this->_modified[$name]);
}
/**
* Resets modified marks of attribute named $name or all attributes
*/
public function resetModified($name = null)
{
if (is_null($name))
$this->_modified = array();
else
unset($this->_modified[$name]);
}
/**
* Returns pair of original and modified values for attribute named $name or list of pairs for all modified attributes
*
* @return array
*/
public function getModified($name = null)
{
if (is_null($name))
return $this->_modified;
elseif (isset($this->_modified[$name]))
return $this->_modified[$name];
else
return false;
}
/**
* Saves the current record. After that reset modified marks
*
* @see CActiveRecord::save
*/
public function save($runValidation=true, $attributes=null)
{
if ($retVal = parent::save($runValidation, $attributes))
{
$this->resetModified();
}
return $retVal;
}
}
DROP TABLE IF EXISTS `book`;
DROP TABLE IF EXISTS `author`;
CREATE TABLE `author` (
`author_id` INT(11) AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
PRIMARY KEY (`author_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `book` (
`book_id` INT(11) AUTO_INCREMENT,
`author_id` INT(11) NOT NULL,
`title` VARCHAR(255) NOT NULL,
PRIMARY KEY (`book_id`),
CONSTRAINT `fk_book_author` FOREIGN KEY (`author_id`) REFERENCES `author` (`author_id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `author` (`author_id`, `name`)
VALUES
(1,'Jack London'),
(2,'Herman Melville'),
(3,'Jane Austen'),
(4,'Jonathan Swift'),
(5,'Mark Twain');
INSERT INTO `book` (`book_id`, `author_id`, `title`)
VALUES
(1,1,'White Fang'),
(2,2,'Moby-Dick, or The Whale'),
(3,3,'Pride and Prejudice'),
(4,4,'Gulliver\'s Travels'),
(5,5,'The Adventures Of Huckleberry Finn');
-- mysql> SELECT b.book_id, b.title, b.author_id, a.name FROM book b JOIN author a ON a.author_id = b.author_id;
-- +---------+------------------------------------+-----------+-----------------+
-- | book_id | title | author_id | name |
-- +---------+------------------------------------+-----------+-----------------+
-- | 1 | White Fang | 1 | Jack London |
-- | 2 | Moby-Dick, or The Whale | 2 | Herman Melville |
-- | 3 | Pride and Prejudice | 3 | Jane Austen |
-- | 4 | Gulliver's Travels | 4 | Jonathan Swift |
-- | 5 | The Adventures Of Huckleberry Finn | 5 | Mark Twain |
-- +---------+------------------------------------+-----------+-----------------+
<?php
# In this sample two Active Record classes are used. Class Author is related with Book.
/**
* This is the model class for table "author".
*
* The followings are the available columns in table 'author':
* @property string $author_id
* @property string $name
*/
class Author extends CActiveRecord {
/**
* @return string the associated database table name
*/
public function tableName()
{
return 'author';
}
/**
* Returns the static model of the specified AR class.
* @return Author the static model class
*/
public static function model()
{
return parent::model(get_called_class());
}
}
/**
* This is the model class for table "book".
*
* The followings are the available columns in table 'book':
* @property string $book_id
* @property string $title
* @property string $author_id
*
* The followings are the available model relations:
* @property Author $author
*/
class Book extends ModActiveRecord {
/**
* @return string the associated database table name
*/
public function tableName()
{
return 'book';
}
/**
* @return array relational rules.
*/
public function relations()
{
return array(
'author' => array(self::BELONGS_TO, 'Author', 'author_id'),
);
}
/**
* Returns the static model of the specified AR class.
* @return Book the static model class
*/
public static function model()
{
return parent::model(get_called_class());
}
}
$book = Book::model()->findByPk(1);
echo '#', $book->book_id, '. "', $book->title, '" by #', $book->author_id, '. ', $book->author->name, "\n";
# change the author_id attribute
$book->author_id = 2;
echo '#', $book->book_id, '. "', $book->title, '" by #', $book->author_id, '. ', $book->author->name, "\n";
# wrong output if class Book extends CActiveRecord
> #1. "White Fang" by #1. Jack London
> #1. "White Fang" by #2. Jack London <-- attribute $book->author_id was changed
but $book->author is associated previously initialized instance
# correct output if class Book extends ModActiveRecord
> #1. "White Fang" by #1. Jack London
> #1. "White Fang" by #2. Herman Melville <-- author_id was changed cause the reinitialize of $book->author
<?php
# In this sample two Active Record classes are used. Class Book has beforeSave logic if author_id attribute is modified
/**
* This is the model class for table "author".
*
* The followings are the available columns in table 'author':
* @property string $author_id
* @property string $name
*/
class Author extends CActiveRecord {
/**
* @return string the associated database table name
*/
public function tableName()
{
return 'author';
}
/**
* Returns the static model of the specified AR class.
* @return Author the static model class
*/
public static function model()
{
return parent::model(get_called_class());
}
}
/**
* This is the model class for table "book".
*
* The followings are the available columns in table 'book':
* @property string $book_id
* @property string $title
* @property string $author_id
*
* The followings are the available model relations:
* @property Author $author
*/
class Book extends ModActiveRecord {
/**
* @return string the associated database table name
*/
public function tableName()
{
return 'book';
}
/**
* @return array relational rules.
*/
public function relations()
{
return array(
'author' => array(self::BELONGS_TO, 'Author', 'author_id'),
);
}
/**
* Do something before save() when author_id attribute is modified
*/
public function beforeSave()
{
$authorModified = $this->getModified('author_id');
if ($authorModified)
{
$booksCollection = new BooksCollectionCache;
if ($authorModified['old'])
echo "Say 'Goodbye!' to author #", $authorModified['old'], "\n";
if ($authorModified['new'])
echo "Say 'Hello!' to author #", $authorModified['new'], "\n";
}
return true;
}
/**
* Returns the static model of the specified AR class.
* @return Book the static model class
*/
public static function model()
{
return parent::model(get_called_class());
}
}
$book = Book::model()->findByPk(1);
echo '#', $book->book_id, '. "', $book->title, '" by author #', $book->author_id, "\n";
$book->author_id = 2; // change author and save
$book->save();
# wrong output if class Book extends CActiveRecord
> #1. "White Fang" by author #1
> Save... <-- attribute $book->author_id was changed but beforeSave() don't know about it
> ... saved
# correct output if class Book extends ModActiveRecord
> #1. "White Fang" by author #1
> Save...
> Say 'Goodbye!' to author #1
> Say 'Hello!' to author #2
> ... saved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment