Handling Related Models in Yii Forms

August 10, 2010 — 94 Comments
The Yii Book If you like my writing on the Yii framework, you'll love "The Yii Book"!

Normally two Models in an MVC architecture are related to each other, such as Employees and Departments (to use the classic example), where each employee is in one department and each department has multiple employees. Although Yii does a great job of auto-generating most of the code you need, including the form used to create and update a Model, the Yii-generated form won’t properly represent the related Model. In this post I’ll walk you through what you need to do to make your forms work properly for related Models.

Looking at the employees example, you would likely have a departmentId value in the Employees table: a foreign key (FK) to store the id value from the Departments table, thereby representing the department that the employee is in. That’s perfect. The form generated by Yii, though, will create a text input for creating and updating the employee’s department:

 <?php echo CHtml::activeLabelEx($model,'departmentId'); ?>
 <?php echo CHtml::activeTextField($model,'departmentId'); ?>
 <?php echo CHtml::error($model,'departmentId'); ?>

That’s not appropriate, of course. The department value for the employee must be a number and you can’t expect the user to know the primary key numbers of the different departments. It’d be most appropriate to turn that text input into a drop-down menu. That’s not hard to do:

<?php echo $form->labelEx($model,'departmentId'); ?>
<?php echo $form->dropDownList($model, 'departmentId', CHtml::listData(
Departments::model()->findAll(), 'id', 'name'),
array('prompt' => 'Select a Department')
); ?>
<?php echo $form->error($model,'departmentId'); ?>

And that’s all you need to do to get this to work. The dropDownList() method creates a SELECT menu. It’s associated with the departmentId attribute of this Model. The drop-down menu’s data has to come from CHtml::listData(). That part of the code says to fetch every Department, and to use the department’s id value for the menu value and its name value for the visible label. The final argument indicates a default prompt to give the menu. Since the Employees Model has a departmentId attribute, the framework will be able to handle errors, pre-select the right value on an update, and so forth. If you change the attribute label for departmentId in protected/models/Employees.php, you can make the prompt say “Department” instead. No problem.

A more complicated situation exists when there’s a many-to-many relationship between two Models, such as Posts and Categories, in a blog site. Because of the many-to-many relationship between these two, neither Posts nor Categories would have a foreign key to the other. Instead, a junction table (and Model) would be used. Let’s call that PostsCategories. The table itself would only need two columns: postId and categoryId.For each category that a post is associated with, there would be a record in this table.

On the form for adding (or updating) a blog post, you’d need to be able to select multiple Categories:

<?php echo $form->labelEx($model,'categories'); ?>
<?php echo $form->dropDownList($model, 'categories', CHtml::listData(
Categories::model()->findAll(), 'id', 'category'), array('multiple'=>'multiple', 'size'=>5)
); ?>
<?php echo $form->error($model,'categories'); ?>

That dropDownList() method will create a drop-down of size 5 (five items will be shown), populated using the list of Categories, and the user will be able to select multiple options. If you were to run this code, though, you’d get an error as Posts doesn’t have a categories attribute. But that’s easy to change…

In the Model definition, you identify the relationship to other Models within the relations() method. The relationship between Posts and PostsCategories could be represented as:

// protected/models/Posts
public function relations() {
    return array(
        'categories' => array(self::HAS_MANY, 'Categories', 'postId')

Now Posts has a categories attribute, meaning that the above form code will work. In fact, you can also add categories to the attributeLabels() array to give this relationship a new label that would be used in the form. You can even add a rule to make sure this part of the form validates. This is an important enough concept that I want to repeat it: when you create a relation, that relation becomes an attribute of the Model.

In order for the creation of a new Post to work, you’ll need to update the Controller to handle the PostsCategories. Within the actionCreate() method (of PostsController), after the Post has been saved, loop through each category and add that record to PostsCategories. The beginning of actionCreate() would look like:

public function actionCreate() {
    $model=new Posts;
    if(isset($_POST['Posts'])) {
        if($model->save()) {
            foreach ($_POST['Posts']['categories'] as $categoryId) {
                $postCategory = new PostsCategories;
                $postCategory->postId = $model->id;
                $postCategory->categoryId = $categoryId;
                if (!$postCategory->save()) print_r($postCategory->errors);

Now, when a new Post is created, one new record is created in PostsCategories for each selected category.

We’re almost done getting this working, except we now need to think about the update process. There are two problems with what we’ve got so far: getting the form to select existing PostsCategories and handling updates of PostsCategories. The form has already been created and populated, but to get it to indicate existing selections, we need to pass along the PostsCategories values. You might think that you can just fetch all the PostsCategories records where postId equals the Model’s id (in other words, perform a with(‘PostsCategories’)-> retrieval), but you can’t. For the drop-down menu in the form to preselect the right values, the form needs to access an array of values, not an array of objects. To achieve that, add some code to the loadModel() method of the PostsController. This method is called for the update, delete, and view actions and just returns a single Model. The Model is loaded using:


After that, within the loadModel() method, you can add this code to fetch the associated PostsCategories:

$criteria=new CDbCriteria;
$criteria->select = 'categoryId';
$postCategories = PostsCategories::model()->findAll($criteria);

That code selects all of the categoryId values from PostsCategories where the postId value equals this post’s id. Next, we need to turn those results into an array:

$categories = array();
foreach ($postCategories as $category) {
    $categories[] = $category->categoryId;

Now, $categories is an array of numeric values, one for each category associated with the post. Just add this to the loaded Model:

$this->_model->categories = $categories;

And now the form will automatically select the existing categories for this post.

The final step is to update the PostsCategories when the form is submitted (and the post is updated). This could be tricky, because the user could add or remove categories, or make no changes to the categories at all. The easiest way to handle all possibilities is to clear out the existing values (for this post) in the PostsCategories table, and then add them in anew. To do that, in actionUpdate() of the PostsController, you would have:

if($model->save()) {
    $criteria=new CDbCriteria;

Then you use the same foreach loop as in actionCreate() to repopulate the table.

And that’s it. It takes a little thought getting all this going but these changes seem to work fine for me and I haven’t come up with a simpler solution yet. I hope this helps you with your next Yii-based project. As always, thanks for reading and let me know if you have any questions or comments.

If you enjoyed this post, then please consider following me using your favorite social media, the RSS feed, and/or by subscribing to my newsletter. Or go crazy, and buy one or more of my books . Thanks!

94 responses to Handling Related Models in Yii Forms

  1. Stoimen Stoimenov August 10, 2010 at 1:19 pm

    Thank you very much for this information. I was just wondering how to accomplish that in my project.

  2. Stoimen Stoimenov August 11, 2010 at 4:10 am

    I did everything as you have described and it worked but I have a little problem.

    The problem is that I am building a multi language website and in this script
    dropDownList($model, ‘departmentId’, CHtml::listData( Departments::model()->findAll(), ‘id’, ‘name’),
    array(‘prompt’ => ‘Select a Department’)
    ); ?>
    The departments are displayed in english (because they are saved like that in the db) but I need to call the names in Yii::t(‘departments’, ‘name’) so that they can be shown in any language. But I can’t achieve that… It looks like the ‘name’ is hard coded in the CHtml::listData…

    I wonder if you could help me.

  3. Hi Larry,

    how do I update a model’s data?

  4. Thank you very much for your tutorial! All of your tutorials are very clear and well-made!

    I am trying to implement a project form where I would like to have a sub-form where one can change the users related to the project. The user and the project table are in a many-many relationship connected by a user_project-table. But in this junction table – a part from the two foreign keys, there is a field for project_role (the users role in the specific project):

    CREATE TABLE `users_projects` (
    `project_id` int(10) NOT NULL,
    `user_id` int(10) NOT NULL,
    `project_role` enum(‘Participant’,’Coordinator’,’Client’,’Other’) NOT NULL,
    PRIMARY KEY (`project_id`,`user_id`),
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    I would like to make a sub-form with tabular-data for user and project_role, where one can add, edit or remove users, connected to the project as well as assign project roles.

    I have made such things before in php/mysql but in a very unstructured way (not object-oriented and not using the MVC-design) This is relatively new to me as well as Yii. Do you have any hints on how to begin?

    I think that junction tables with more fields are a common in many db data structures and would like to make a tutorial later on this, but by now I am afraid that I am only able to find very messy solutions;-)

    Thanks again! Sune

    • Thanks for the nice words and my apologies for the delayed reply. As for your specific issue, I don’t think it’ll be that hard to implement. I guess I’d create a series of checkboxes, one for each possible user. Then you’d need to create a select menu, one for each user, in which the admin could identify the user’s role.

  5. You could take a look at http://www.yiiframework.com/extension/save-relations-ar-behavior

    It is a helpful extension that I am using!

  6. Larry, one thing that is very common and which I’d love for you to give a Yii example of (like the above many to many drop down list) is a dependent/cascading/chained set of dropdown lists. Although I have seen two yii extensions on this in the yii extensions repository, I have not been able to get any to work as they I wasn’t sure where to put the code (controller, view, model).

    • Hello Oscar. Thanks for the suggestion. I’ve been doing more Ajax stuff with Yii lately and I think you’re right that this is a topic that merits attention. I’ll see what I can do. Thanks!

  7. Thanks for sharing this code!
    It saved me a lot of time!

  8. THANK YOU LARRY!!! I thought I am going to spend another day solving this and you help me did it in 30 mins!

  9. Hey Larry!

    Awesome tutorial! You really helped me a lot.

    But there is one little problem I can’t get a grip on.

    You say:
    “Just add this to the loaded Model:

    $this->_model->categories = $categories;”

    Where exactly would that be? What do you mean by “loaded Model”?

    I constantly keep getting this Error Message:
    “Property “xController._model” is not defined. ”
    This is related to the codeline in the loadModel() function, where (in your case) you typed
    “$this->_model->categories = $categories;”

    I think knowing where to put that little piece of code exactly “into the loaded model” would solve the problem.

    It would be great if you could help me out here.

    Thank you!

    • Thanks for the nice words. In answer to your question, as I say just above that, I’m taking about the loadModel() method of the PostsController. The “loaded Model” is represented by the variable $this->_model, where $this refers to the current object and _model is a private variable (i.e., attribute) of that object.

      • Thanks a lot! That just solved he problem!

      • Thank you very much, Larry!

      • Hey Larry!
        Great tutorial.
        Like Patrick I keep getting the same error message.
        Where I must declare _model variable.
        Thanks in advance.

        • Thanks for the nice words. You’d declare that as a private variable just inside the controller definition.

          • Hi Larry.
            Forse cercavi: Ho seguito il tuo consiglio e ho definito $model in PostController
            I followed your advice and I defined in $ _model PostController:

            class PostController extends Controller
            * @var string the default layout for the views. Defaults to ‘//layouts/column2′, meaning
            * using two-column layout. See ‘protected/views/layouts/column2.php’.
            public $layout=’//layouts/column2′;
            private $_model;
            but I keep getting this Warning Message:
            Creating default object from empty value.

            What’s wrong?

            Thanks in advance.

          • Hard to say, given the information provided. If you need assistance, please use my support forums or the Yii support forums, and provide more details at the time.

      • Larry,

        I seem to be running in to the same issue that Patrick was, I have successfully managed to get this working except for the update and delete process every time I try to edit or delete I get the following CException – Property “PostsController._model” is not defined.

        I have included the code I have used in the in the loadModel function.

        public function loadModel($id)
        $criteria=new CDbCriteria;
        $criteria->select = ‘categoryId';
        $postCategories = PostsCategories::model()->findAll($criteria);

        $categories = array();
        foreach ($postCategories as $category) {
        $categories[] = $category->categoryId;

        $this->_model->categories = $categories;

        throw new CHttpException(404,’The requested page does not exist.’);
        return $model;

        Thanks for your Help!

  10. Larry, how come you don’t use the MANY_MANY option for the many to many relationships?

    • Because there’s not a many-to-many relationship between employees and departments. Each department can have many employees but each employee is in only one department. Hence, it’s a one-to-many relationship.

      • I wonder, too, why you don’t use the MANY_MANY option between categories and posts.
        Is there still no way to save all models in one query (when having many it is a performane problem), for example all comments like INSERT INTO VALUES(),(),()
        I added for my model a function like saveAll(), it is working, but not really nice.

        • I am also wondering?
          What is the best simple way to handle MANY MANY relation right now 2012 Aug 09?
          I mean based on latest framework.yii ver 1.1.10/1.1.11

          I am a newbie. You tutorial is very clear. If I could find any other methods, I will use your Posts Category samples.

          One small request:
          could you list separately how you define relationship for Class (Posts, Categories, PostsCategories)? The following was copied from your tutorial, and I believe it is for Class Posts.
          Thanks a lot.

          The relationship between Posts and PostsCategories could be represented as:

          // protected/models/Posts
          public function relations() {
          return array(
          ‘categories’ => array(self::HAS_MANY, ‘Categories’, ‘postId’)

  11. Hi Larry. How can I print the categories of a post in index.php?r=posts/ (i mean, on _view.php)????
    Thanks for the tutorials!!

    • Thanks for the nice words and you’re welcome. To print the categories associated with a post, you’d want to select the categories with the posts (using the relation) in the Controller. Then, in the View, you can loop through the categories associated with the specific Model instance. There may be an example of this in the Yii Blog demo.

  12. Hi Larry
    Thanks for the tutorial ! but how do I retain the checked values when the form is reloaded upon hitting validation error? All my other input fields values are retained but not these “categories” checkboxes ..

    Please help!

    • If setup properly, it should happen automatically. I suspect your code is missing something. If you need help with this, please turn to my forums or the Yii forms.

  13. I follow your example exactly except that I use $form->checkBoxList instead of dropDownList. Can you please help me at Yii Forum? Thanks!

  14. Hi, This post seem near to what I need but still not getting where I need. I have three models, AutoListings, AutoMakes and AutoModels. Now in the AutoListings which has the foreign keys for AutoMakes(make_id) and AutoModels(model_id). Now am in the AutoListing view file and I want to get the make and the model names for a specific AutoListing. How can I do this if I do echo $model->make_id and echo $model->model_id I get the IDs value but I want to get the really names.
    In my AutoListings we have this relation
    public function relations()
    // NOTE: you may need to adjust the relation name and the related
    // class name for the relations automatically generated below.
    return array(
    ‘model’ => array(self::BELONGS_TO, ‘AutoModels’, ‘model_id’),
    ‘make’ => array(self::BELONGS_TO, ‘AutoMakes’, ‘makes_id’),

  15. Thank you very much for this post !

  16. Hi, I see on the actionCreate you have $postCategory = new PostsCategories which mean you have a model for your third table PostsCategories. What is the relationship inside it and what is the relationship in your categories model.

  17. Hi Larry,

    What’s the philosophy behind handling the Many-to-Many DB create in the actionCreate() instead of, say, within a afterSave() ? Or even possibly creating a filter?

    It makes sense to me for it to be in the actionCreate() part of the controller, I just want to know why we wouldn’t want to place the code in those others areas.

    Thank you! I found this post very informative. Thanks again.


    Aaron Levin

    • Hello Aaron. Thanks for the nice words and for the question. It’s a good one. Here’s the problem with putting this code in an afterSave() method: you’d be having one Model directly interact with another Model, which I consider to be a no-no. The implication would be, for example, that if you later change Model X, then you’d also have to change the afterSave() method in Model Y, which doesn’t make sense from a design perspective. Plus I feel like that’s a bit too much activity for a Model method, and you’d have to handle errors within the Model somehow. It’s a reasonable question, but that’s my thinking on it.

  18. Hi Larry,
    Realy happ to find your site, it’s so useful .
    But I have a little trouble:
    if PostCategories has one more field named ‘type'( or anything like that), and we want to add field into the view so we can add the value of field ‘type’ into database, can you help me?
    thanks you very much

    • Thanks for the nice words. Much appreciated. I’m not following your question, though. If you need help, please use my support forums or the Yii support forums.

  19. Thanks, just what I was looking for!

  20. Larry, thanks for the explanation. I used these ideas in my code.
    However I believe the PostCategory part could be improved.
    You don’t really use the ‘categories’ from the relations(). The code would work equally well if you just defined
    “public $categories”
    in the model instead of defining it in the relations.

    However it is even nicer to use setter and getter functions in the model, like this:
    public function getCategories(){
    $criteria=new CDbCriteria;
    $criteria->select = ‘categoryId';
    $postCategories = PostsCategories::model()->findAll($criteria);
    $categories = array();
    foreach ($postCategories as $category) {
    $categories[] = $category->categoryId;
    return $categories;


    public function setCategories($categories) {
    $criteria=new CDbCriteria;
    foreach ($categories as $categoryId) {
    $postCategory = new PostsCategories;
    $postCategory->postId = $this->id;
    $postCategory->categoryId = $categoryId;
    if (!$postCategory->save()) print_r($postCategory->errors);

    In this case you don’t need to do anything special in the loadModel part of the controller, since $categories will be there automatically. When you save the model, you have to call
    $model->categories = $_POST[‘Posts’][‘categories’];
    instead of the code mentioned in the article.

    Although I used this solution in a similar environment, (so the principle is correct), but the above code was not tested, so few modifications may be needed.

    There is an alternative solution to the getter part:
    if you define the relation
    ‘my_category’ => array(self::MANY_MANY,’Categories’,’PostCategories(postId,categoryId)’),

    then you can simply have:
    public function getCategories(){
    $categories = array();
    foreach ($this->my_category as $category)
    $categories[] = $category->categoryId;

    • Thanks for your input. Your solution may work, but I would not be inclined to do it that way. First, you’re burying quite a bit of logic, referencing other Models, within a Model, which I would avoid. Second, you’re essentially replicating what Yii will do for you through relations and ActiveRecord. But to each their own, of course.

  21. thank you very much . it’s make my mood is good.

  22. Hey, very good post, thank you very much!

  23. Thanks for the nice information .. I was indeed working on a similar problem. However, in my case I am developing a registration form for a student, where in along with the details of the student (model named PersonalInfo) I accept education details (model named EducationInfo). A student may have many qualifications, hence it is a one-many relationship with educational details being stored in another table with the student id. My doubt is in case of educational details I am accepting 3 fields : degree, passing year and branch, so how can I now handle these two models with a single form .

  24. Hi Larry,
    Just wanna thank you for sharing this, well written and clear, saved me tons of time.

  25. HI, thank you for your information. But I have a question: what if the table recording the MANY-MANY relationship has another attributes except postID & categoryID, i.g the popularity of a post in a specific category denominated pop. It appears hard for me to let my client fill all necessary information, including pop, in just one form? Any suggestion?

  26. Hy Larry, thank you very much for this very useful tutorial..but i have a problem..i got an error caused by the fact that the tabel has a composite primary key…have you got any idea about this?

  27. Hi Larry,
    Thanks for the article. It’s pretty clear. I just have a problem displaying the option labels in the drop list derived from other related models. Here is my code:

    echo $form->dropDownList($model,’businessId’,
    CHtml::listData(Business::model()->findAll(), ‘id’, ‘name’),
    array(‘empty’=>’select business’));

    This works. But I need the labels to show “Business Name, City Name, Country Name” based on the relations in my Business model. As shown in the code below, something like ‘name, city.name, country.name’ does not work. I could not find an answer so far.

    echo $form->dropDownList($model,’businessId’,
    CHtml::listData(Business::model()->with(‘city’,’city.country’)->findAll(), ‘id’, ‘name, city.name, city.country.name’),
    array(‘empty’=>’select business’));


  28. Larry,
    I have modeled my many-many based on the above code. Following the above and applying a new rule.
    – if one Category is related to one Post, it cannot be again related to another Post again.
    Once a category is added to the tbl_PostCategory, I update that tbl_Category row as used_status (with a 1), so it does not appear in the dropdown in the next create.
    The problem is with the update, because the selected values from the join table, now disappears.

    dropDownList($model, 'categories', CHtml::listData(
    Categories::model()->findAll(array('condition'=>'used_status'=>1)), 'id', 'category'), array('multiple'=>'multiple', 'size'=>5)
    ); ?>

    Can you please advise how to ‘merge’ the selected categories from the join table and all categories with used_status=1, in the dropDownList() above.

  29. Thanks Larry, this code really helped me a lot.

  30. Hello, Larry.
    Thanks for this good the article. I’m new with Yii and i need a little help.. i want to print the categories of a post in the view .. can u help me to manage it.

  31. MIchael Kambenga April 12, 2012 at 5:07 am

    A lot of thanks for this nice tutorial of your…
    Here comes my problem,
    When it comes to update the Post,suppose i have another table that relates to the PostCategories table,But on updating the Post,all the contents of PostCategories are to be deleted first before saving the new ones!!
    This makes the foreign key constraints failure between the PostCategories and that new table…

  32. 7 hours searching on the web, and you got the answer !
    Thank you !

  33. Hi! Larry, thanks for such a great article. Hey, you said: “You can even add a rule to make sure this part of the form validates.”. How can I achieve that validation rule?

  34. Hi Larry, thanx again for your great work. Just a question:

    You state:
    The relationship between Posts and PostsCategories could be represented as:
    ‘categories’ => array(self::HAS_MANY, ‘Categories’, ‘postId’)

    Shouldn’t it be:
    ‘categories’ => array(self::HAS_MANY, ‘PostsCategories’, ‘postId’)


  35. Sir Larry
    Could you help me on how to add, edti, and delete data from three different tables using only one controller,model, and view??tnx

  36. Thank you very much, you are the best. This code is really useful. If you put the complete example would be amazing.

  37. Fantastic article. Works 100%.

  38. This article cleared up my understanding (or rather misunderstanding) of relations in about 30 minutes. The book is great too and really enjoy reading through it. Keep up the great work!

  39. One comment: I suggest moving the loading of related models from loadModel() method in the controller to the afterFind() method in the model class itself. Its more appropriate there and can save problems later when a model is not being loaded via loadModel() but in some other way.

  40. Sir thanks for a wonderful tutorial I followed your tutorial but whenever I submit my data. It does not submit. why is that? I did not change my actionCreate. thanks in advance

  41. Hi Larry,

    I have a form where I am getting inputs from multi Selection-dropdown Box, and want to store only this dropdown selected items in another relational table by creating a model object for that for that table, how could I do that, any suggestions are welcome

  42. First of all – a big thanks – your article was a great help!

    Working through it however, I noticed that the part for the update process (where you add code to the loadModel action) was not needed. For me, it works fine without that whole bit… I just had to update the actionCreate and actionUpdate functions as you show and that was it,
    and my relations is defined differently to the one you point out:

    ‘meetingMembers’=>array(self::MANY_MANY, ‘Member’, ‘meeting_member(meetingID, memberID)’),

    Just sharing…

  43. Hi Larry
    thanks for the great tutorial, I learnt yii via your tutorials. I followed the steps on creating a drop down list from a related model, all works fine. I have two related models, pip and project. my only problem is the pip view displays the project id and not the project name when I create a new pip?

Trackbacks and Pingbacks:

  1. Creating Forms with the Yii Framework – Larry Ullman - January 20, 2011

    […] Handling Related Models in Yii Forms […]

  2. Easy steps to saving relational data through a form in Yii Framework | stefandoorn.nl - August 19, 2011

    […] the relations to the database. A good guide for beginners to the Yii Framework. Find out more here: http://www.larryullman.com/2010/08/10/handling-related-models-in-yii-forms/ Share and […]

  3. Yii framework - заметки на полях | Заметки Лёвика - March 29, 2012

    […] http://www.larryullman.com/2010/08/10/handling-related-models-in-yii-forms/ – Yii MAnY MANY save Реклама для "поддержания штанов": […]

Comments are great, but I'd strongly prefer any requests for assistance get made in the support forums. Thanks!