The ActiveRecord class in Yii provides an object oriented interface (aka ORM) for accessing database stored data. Similar structures can be found in most modern frameworks like Laravel, CodeIgniter, Smyfony and Ruby. Today, we’ll go over the implementation in Yii 2.0 and I’ll show you some of the more advanced features of it.
Model class intro
The Yii ActiveRecord is an advanced version of the base yii\base\Modelwhich is the foundation of the Model-View-Controller architecture. I’ll quickly explain the most important functionality that ActiveRecord inherits from the Model class:
The business data is held in attributes. These are publicly available properties of the model instance.
All the attributes can conveniently be assigned massively by assigning any array to the
attributes property of a model. This works because the base Component class (the base of almost everything in Yii 2.0) implements the
__set() method which in turn calls the
setAttributes() method in the Model class. The same goes for retrieving; all attributes can be retrieved by getting the
attributes property. Again, built upon the Component class which implements
__get() which calls the
getAttributes() in the Model class.
Models also supply attribute labels which are used for display purposes which makes using them in forms on pages easier.
Data passed in the model from user input should be checked to see that they satisfy your business logic. This is done by specifying
rules() which would normally hold one or more validators for each attribute.
By default, only attributes which are considered ‘safe’, meaning they have a validation rule defined for them, can be assigned massively.
The scenarios allow you to to define different ‘scenarios’ in which you’ll use a model allowing you to change the way it validates and handles its data. The example in the documentation describes how you can use it in a FormModel (which also extends the Model class) by specifying different validation rules for both user registration and login simply by defining two different scenarios in one Model.
Creating an ActiveRecord model
An ActiveRecord instance represents a row in a database table, therefore we need a database. In this article I’ll use the database design in the picture below as as an example. It’s a simple structure for blog articles. Authors can have multiple articles which can have multiple tags. The articles are related through an N:M relation to the tags because we want to be able to show related articles based on the tags. The ‘articles’ table will get our focus because it has the most interesting set of relations.
I’ve used the Gii extension to generate the model based on the table and it’s relations. Here’s what is generated for the articles table from the database structure just by clicking a few buttons:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
The properties listed in the comment before the class definition show which attributes are available on every instance. It is good to notice that because of the relation definitions (defined in this class), you also get properties for the related data; one
Authors $authors and multiple
tableName() function defines which database table is related to this Model. This allows a decoupling of the class name from the actual table name.
rules() define the validation rules for the model. There are no scenarios defined so there is only one set of rules. It’s quite readable; showing which fields are required and which require an integer or string. There are quite a few core validators available which suit most people’s needs.
attributeLabels() function supplies labels to be shown for each attribute should it be used in views. I chose to make mine i18n compatible from Gii which added all the calls to
Yii::t(). This basically means that translation of the labels, which end up in the rendered pages, will be much easier later on should we need it.
getArticlestags() functions define the relation of this table to other tables.
Note: I was quite surprised to find the ‘Format’ attribute was missing completely from the properties, validators and labels. Turns out that Gii doesn’t support ENUMs. Besides MySQL (and its forks) only PostgreSQL supports it and therefore it wasn’t implemented. I had to add them manually.
Completing the model
You can probably see that the generated Articles class only has relations defined for the foreign key tables. The N:M relation from Articles to Tags won’t be generated automatically so we’ll have to define that by hand.
The relations are all returned as instances of yii\db\ActiveQuery. To define a relation between Articles and Tags we’ll need to use the ArticlesTags as a junction table. In ActiveQuery, this is done by defining a via table. ActiveQuery has two methods you can use for this:
via()allows you to use an already defined relation in the class to define the relation.
viaTable()alternatively allows you to define a table to use for a relation.
via() method allows you to use an already defined relation as via table. In our example, however, the ArticlesTags table holds no information that we care for so I’ll use the
1 2 3 4 5 6 7 8
Now that we’ve defined all the relations, we can start using the model.
Using the model
I’ll quickly create some database content by hand.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
It should be noted that the code above has some assumptions which I’ll explain;
- The result of save(), a boolean, isn’t evaluated. This isn’t wise normally, because Yii will actually call validate() on the class before actually saving it in the database. The database INSERT won’t be executed should any of the validation rules fail.
- You might notice that the ID attributes of the various instances are used while they are not set. This can be done safely because the save() call will INSERT the data and get assigned the primary key back from the database and make the ID property value valid.
- The $article->LastEdited is a DateTime value in the database. I want to insert the current datetime by calling the NOW() SQL function on it. You can do this by using the Expression class which allows the usage of various SQL expressions with ActiveRecord instances.
You can then retrieve the article again from the database;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
New and advanced usages
The Yii ActiveRecord, as I’ve described it so far, is straight forward. Let’s make it interesting and go into the new and changed functionality in Yii 2.0 a bit more.
Yii 2.0 introduced the ability to detect changed attributes. For ActiveRecord, these are called dirty attributes because they require a database update. This ability now by default allows you to see which attributes changed in a model and to act on that. When, for example, you’ve massively assigned all the attributes from a form POST you might want to get only the changed attributes:
1 2 3 4 5 6 7 8 9 10 11 12 13
The ActiveRecord, being extended from
Model, now implements the
\yii\base\Arrayable trait with it’s
toArray() method. This allows you to convert the model with attributes to an array quickly. It also allows for some nice additions.
Normally a call to
toArray() would call the
fields() function and convert those to an array. The optional expand parameter of
toArray() will additionally call
extraFields() which dictates which fields will also be included.
These two fields methods are implemented by
BaseActiveRecord and you can implement them in your own model to customize the output of the
I’d like, in my example, to have the extended array contain all the tags of an article available as a comma separated string in my array output as well;
1 2 3 4 5 6 7 8 9 10 11 12 13 14
And then get an array of all the fields and this extra field from the model;
Yii 1.1 already implemented various events on the CActiveRecord and they’re still there in Yii 2.0. The ActiveRecord life cycle in the Yii 2.0 guide shows very nicely how all these events are fired when using an ActiveRecord. All the events are fired surrounding the normal actions of your ActiveRecord instance. The naming of the events is quite obvious so you should be able to figure out when they are fired;
In my example, the
LastEdited attribute is a nice way to demonstrate the use of an event. I want to make sure
LastEdited always reflects the last time the article was edited. I could set this on two events;
beforeValidate(). My model rules define
LastEdited as a required attribute so we need to use the
beforeValidate() event to make sure it is also set on new instances of the model;
1 2 3 4 5 6 7 8
Note that with all of these events, you should call the parent event handler. Returning false (or nothing!) from a before event in these functions stops the action from happening.
Behavior can be used to enhance the functionality of an existing component without modifying its code. It can also respond to the events in the component that it was attached to. They behave similar to the traits introduced in PHP 5.4. Yii 2.0 comes with a number of available behaviors;
yii\behaviors\AttributeBehaviorallows you to specify attributes which need to be updated on a specified event. You can, for example, set an attribute to a value based on an unnamed function on a
yii\behaviors\BlameableBehaviordoes what you’d expect; blame someone. You can set two attributes; a
updatedByAttributewhich will be set to the current user ID when the object is created or updated.
yii\behaviors\SluggableBehaviorallows you to automatically create a URL slug based on one of the attributes to another attribute in the model.
yii\behaviors\TimestampBehaviorwill allow you to automatically create and update the time stamp in a
updatedAtAttributein your model.
You can probably see that these have some practical applications in my example as well. Assuming that the person currently logged in to the application is the actual author of an article, I could use the
BlameableBehavior to make them the author and I can also use the
TimestampBehaviour to make sure the
LastEdited attribute stays up to date. This would replace my previous implementation of the
beforeValidate() event in my model. This is how I attached the behaviors to my Articles model:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
I assume here of course that the creator and the editor of the article is the same person. Since I don’t have a created timestamp field, I chose not to use it by setting the
false. I could of course also set this to ‘
The last feature I want to touch is the possibility to automatically force the usage of transactions in a model. With the enforcement of foreign keys also comes the possibility for database queries to fail because of that. This can be handled more gracefully by wrapping them in a transaction. Yii allows you to specify operations that should be transactional by implementing a
transactions() function in your model that specifies which operations in which scenarios should be enclosed in a transaction. Note that you should return a rule for the
SCENARIO_DEFAULT if you want this to be done by default on operations.
1 2 3 4 5 6 7 8 9
The Yii ActiveRecord class already made ORM handling very simple, Yii 2.0 builds upon this great base and extends it even further. The flexibility is huge due to the possibility to define different usage scenarios, attach behaviors and use events.
These are some features in the ActiveRecord that I’ve found most useful over time and most welcome with the arrival of Yii 2.0. Did you miss a feature of ActiveRecord, or perhaps feel that Yii ActiveRecord is missing a great feature from another framework? Please let us know in the comments!
作者: Arno Slatius