Automatic cache tagging in Yii
Tag caching is a tool that allows you to update the cache point-by-point when certain dependencies change.
Unfortunately, the Yii developers did not consider it necessary to implement this tool in ActiveRecord, but would be worth it. However, they gave us the opportunity to do it ourselves.
Implementation of binding to the tag based on the specific model has been discussed Habré habrahabr.ru/post/159079 . Special thanks to the author. I will use it as a basis, and add functions for automatic tag generation.
Since the project has many different entities, and the connecting keys are formed without specific standards, we need a way to use one tag for columns whose names are different. For example, in one table we have the user_id field, and in the other we have the customer_id field, both columns refer to the user entity. It is logical that they should depend on the same tag - user_id.
Banally overload certain methods in CActiveRecord.
Tags should be generated for relationships when fetching several models at the same time and when fetching a model using the master key (the master key may be composite)
No matter how we would like to automate the process to the maximum, still, there are enough situations where you need to specify tags manually.
For example, when a model is sampled using a complex query (using CDbCriteria). To do this, we will expand the CDbCriteria class by adding the $ tags property to it, according to which the tag generator will understand that there are tags added “manually”.
To satisfy all the above requirements, we need functionality that allows us to define standards.
The next problem is the determination of which tag should be deleted when changing a specific model. When reading, we create / check all the tags on which the model depends. When recording, we must remove only individual tags. You cannot delete tags on which other models depend.
To solve this problem, unfortunately, I was forced to divide the rules into two groups corresponding to the "read" and "write" modes. This allows us to determine which tags will be used when reading and which need to be removed when writing. You will have to manually determine these points.
We will store the rules in the cacheTags model method ($ mode = 'read'). Which should return an array. Also, we determine that if cacheTags is not declared in the model or returns an empty array, then automatic caching will not be activated.
Each tag will have a prefix consisting of the model name in lower case, i.e. user_id is the tag for the id property in the User model.
Going forward I’ll say that, during implementation, I had to define several types of rules:
Static - a linear element of the array, the value of which corresponds to any column of the table. The tag will consist of a prefix and the current name of the rule.
Constant - a value with the leading character ':', which will be used instead of the tag name, after deleting the character ':'. In other words, a global prefix will not be added to the tag name.
A link is an associative element of an array whose key corresponds to a table column; the rule value will be used to name the tag. Moreover, the value of the rule can be represented in the form of other rules.
Composition is an array of rules. The corresponding composite tag will be created
Example:
In the above example, we see that the model depends on the tags id, user_id, composite, consisting of id and user_email, but only id and composite tags will be deleted during recording. Thus, the user_id tag will remain untouched and other models dependent on it will not be affected.
All of the above is implemented as a library and posted on github: github.com/yiix/Cache .
For installation, you can use the composer or just copy the repository. Models must inherit the \ Yiix \ Cache \ Tagging \ CActiveRecord class. Also, do not forget to add the cacheTags method to each model with a description of the rules for generating tags, since all the automation is in it.
I do not see much reason to clutter up the post with code from the library, I will give only some illustrative examples.
The codes in the examples should not be used directly. I quote them to describe the technique used in Yiix / Cache / Tagging / CActiveRecord.
Let's say we have the following models:
It is logical that it is enough to define rules for key properties.
The tag will be created by primary key.
result:
Tags are created by this array of parameters.
result:
As a result, there is a tag (array element with index 3) created by a composite rule.
Analogously to example 2.
result:
Adding tags manually.
result:
here we see that the “link” rule has worked and the tag names correspond to the User entity.
The library uses the namespace. Information on setting up the namespace is described here:
yiiframework.ru/doc/guide/ru/basics.namespace
Thank you for your attention.
Unfortunately, the Yii developers did not consider it necessary to implement this tool in ActiveRecord, but would be worth it. However, they gave us the opportunity to do it ourselves.
Implementation of binding to the tag based on the specific model has been discussed Habré habrahabr.ru/post/159079 . Special thanks to the author. I will use it as a basis, and add functions for automatic tag generation.
We single out the tasks that we will have to solve:
Tag generation according to predefined rules. (standardization)
Since the project has many different entities, and the connecting keys are formed without specific standards, we need a way to use one tag for columns whose names are different. For example, in one table we have the user_id field, and in the other we have the customer_id field, both columns refer to the user entity. It is logical that they should depend on the same tag - user_id.
Automatic tag generation
Banally overload certain methods in CActiveRecord.
Tags should be generated for relationships when fetching several models at the same time and when fetching a model using the master key (the master key may be composite)
Ability to add tags manually
No matter how we would like to automate the process to the maximum, still, there are enough situations where you need to specify tags manually.
For example, when a model is sampled using a complex query (using CDbCriteria). To do this, we will expand the CDbCriteria class by adding the $ tags property to it, according to which the tag generator will understand that there are tags added “manually”.
Implementation:
To satisfy all the above requirements, we need functionality that allows us to define standards.
The next problem is the determination of which tag should be deleted when changing a specific model. When reading, we create / check all the tags on which the model depends. When recording, we must remove only individual tags. You cannot delete tags on which other models depend.
To solve this problem, unfortunately, I was forced to divide the rules into two groups corresponding to the "read" and "write" modes. This allows us to determine which tags will be used when reading and which need to be removed when writing. You will have to manually determine these points.
We will store the rules in the cacheTags model method ($ mode = 'read'). Which should return an array. Also, we determine that if cacheTags is not declared in the model or returns an empty array, then automatic caching will not be activated.
Each tag will have a prefix consisting of the model name in lower case, i.e. user_id is the tag for the id property in the User model.
Going forward I’ll say that, during implementation, I had to define several types of rules:
- Static
- Constant
- Link
- Composite
Static - a linear element of the array, the value of which corresponds to any column of the table. The tag will consist of a prefix and the current name of the rule.
Constant - a value with the leading character ':', which will be used instead of the tag name, after deleting the character ':'. In other words, a global prefix will not be added to the tag name.
A link is an associative element of an array whose key corresponds to a table column; the rule value will be used to name the tag. Moreover, the value of the rule can be represented in the form of other rules.
Composition is an array of rules. The corresponding composite tag will be created
Example:
public function cacheTags($mode='read'){
switch ($mode) {
case 'read':
return array(
'id', // статический
'user_id'=>':user_id', // ссылка, при этом значение ссылки представлено в виде константы
array( 'id', 'email'=>'user_email' ) // композитный
);
break;
case 'write':
return array(
'id', // статический
array( 'id', 'email'=>'user_email' ) // композитный
);
break;
}
}
In the above example, we see that the model depends on the tags id, user_id, composite, consisting of id and user_email, but only id and composite tags will be deleted during recording. Thus, the user_id tag will remain untouched and other models dependent on it will not be affected.
All of the above is implemented as a library and posted on github: github.com/yiix/Cache .
For installation, you can use the composer or just copy the repository. Models must inherit the \ Yiix \ Cache \ Tagging \ CActiveRecord class. Also, do not forget to add the cacheTags method to each model with a description of the rules for generating tags, since all the automation is in it.
I do not see much reason to clutter up the post with code from the library, I will give only some illustrative examples.
The codes in the examples should not be used directly. I quote them to describe the technique used in Yiix / Cache / Tagging / CActiveRecord.
Let's say we have the following models:
class User extends \Yiix\Cache\Tagged\CActiveRecord
{
...
public function cacheTags($mode = 'read')
{
switch ($mode) {
case 'write':
return array(
'id','email','username'=>':user_name',
array('id','email')
);
break;
case 'read':
return array(
'id','email','username'=>':user_name',
array('id','email')
);
default:
break;
}
}
...
}
class Post extends \Yiix\Cache\Tagged\CActiveRecord
{
...
public function cacheTags($mode = 'read')
{
switch ($mode) {
case 'write':
return array(
'id',
);
break;
case 'read':
return array(
'id',
'authorId'=>':user_id',
);
default:
break;
}
}
...
}
class Tag extends \Yiix\Cache\Tagged\CActiveRecord
{
...
public function cacheTags($mode = 'read')
{
switch ($mode) {
case 'write':
return array(
'id',
'name',
);
break;
case 'read':
return array(
'id',
'name',
);
default:
break;
}
}
...
}
It is logical that it is enough to define rules for key properties.
Example 1
The tag will be created by primary key.
$model = User::model()->findByPk(1);
$tags = \Yiix\Cache\Tagged\Helper::generateTags($model);
dump($tags);
result:
array
(
0 => 'user_id=1'
)
Example 2
Tags are created by this array of parameters.
$tags = \Yiix\Cache\Tagged\Helper::generateTags(User::model(),array(
'id'=>'1',
'email'=>'webmaster@example.com',
'username'=>'demo'
));
dump($tags);
result:
array
(
0 => 'user_id=1'
1 => 'user_email=webmaster@example.com'
2 => 'user_name=demo',
3 => 'user:user_id=1,user_email=webmaster@example.com'
)
As a result, there is a tag (array element with index 3) created by a composite rule.
Example 3
Analogously to example 2.
$tags = \Yiix\Cache\Tagged\Helper::generateTags($Tag::model(),array('name'=>'blog'));
dump($tags);
result:
array
(
0 => 'tag_name=blog'
)
Example 4
Adding tags manually.
$criteria = new \Yiix\Cache\Tagged\CDbCriteria();
$criteria->addInCondition('authorId', array('1','2'));
$criteria->tags = array(
'authorId'=>array('1','2'),
);
$tags = \Yiix\Cache\Tagged\Helper::generateTags(Post::model(),$criteria);
dump($tags);
result:
array
(
0 => 'user_id=1'
1 => 'user_id=2'
)
here we see that the “link” rule has worked and the tag names correspond to the User entity.
The library uses the namespace. Information on setting up the namespace is described here:
yiiframework.ru/doc/guide/ru/basics.namespace
Thank you for your attention.