Tabular Input, Validating and Saving related models

This tutorial shows how to use a model to save and validate related (MANY_MANY) models.

In this tutorial you will find

  • Validating model as a whole including its related models
  • Collecting data for the model and its related models (tabular input)
  • Displaying errors from the related models.
  • Saving the primary model and its related models.
  • Natural association ( $model->students = array($studentOne, $studentTwo); )

Usage:

./protected/yiic migrate

./protected/yiic createCourses

Run the site.

Details:

Validation of the Course model is done by over loading the validate method. This method can be used to validate the model as a whole, like prevent students with the same name.

public function validate($attributes = null, $clearErrors = true) {
        $isValid = parent::validate($attributes, $clearErrors);
        foreach ($this->students as $student) {
            if ($student->validate() == false) {
                $isValid = false;
                $this->addErrors($student->getErrors());
            }
        }
        return $isValid;
    }

Next we overload the save method to save all related models.

public function save($runValidation = true, $attributes = null) {
    if ($runValidation) {
        if ($this->validate($attributes) == false)
            return false;
    }
    foreach ($this->students as $student) {
        $student->save(false);
    }
    return parent::save(false, $attributes);
}

Now I can create a course model and its related students.

Snip from protected/tests/unit/CourseTest.php

$courseName = 'testCourse';
    $studentOne = new Student();
    $studentOne->first_name = 'Firsty';
    $studentOne->last_name = 'Test';
    $studentTwo = new Student();
    $studentTwo->first_name = 'Secondy';
    $studentTwo->last_name = 'Test';

    $model = new Course();
    $model->name = $courseName;
    $model->description = 'This course was created for testing purpose';
    $model->students = array($studentOne, $studentTwo);
    if ($model->save() == true) {
        echo 'Successfully created ' . $model->name . PHP_EOL;
    } else {
        echo 'Failed to create ' . $model->name . PHP_EOL;
        print_r($model->Errors);
    }

EAdvancedArBehavior is used to update the relationship table. http://www.yiiframework.com/extension/eadvancedarbehavior/

To collect tabular input of students for a course I go through the Student collection as

if (isset($_POST['Course'])) {
    $model->attributes = $_POST['Course'];
    $model->students = CourseController::assignStudents($model, $_POST['Student']);

CourseController::assignStudents is defined as

public static function assignStudents($model, $items_posted) {
        $students = array();
        foreach ($items_posted as $item_post) {
            $student = CourseController::findStudent($model, $item_post['id']);
            if (is_null($student)) {
                $student = new Student();
            }
            unset($item_post['id']); // Remove primary key
            $student->attributes = $item_post;
            array_push($students, $student);
        }
        return $students;
    }

    public static function findStudent($model, $id) {
        $student = null;
        foreach ($model->students as $s) {
            if ($s->id == $id) {
                $student = $s;
            }
        }
        return $student;
    }

I got the idea to solve the tabular input from http://www.yiiframework.com/extension/ztabularinputmanager/