Last active
May 4, 2023 20:27
-
-
Save pvolyntsev/c85847b888d71e89beb0 to your computer and use it in GitHub Desktop.
Yii Active Record instance with "ifModified then ..." logic and dependencies clearing
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
<?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; | |
} | |
} |
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
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 | | |
-- +---------+------------------------------------+-----------+-----------------+ |
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
<?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 | |
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
<?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