11
votes

I've built a simple form model & view, a simple AR model, and a simple controller. The form model assigns the correct values to the AR instance, but when I call save(), none of those values are saved in the DB. Any ideas?

The form model:

<?php

namespace app\models;

use Yii;
use yii\base\Model;

class PromptForm extends Model
{
    public $name;
    public $intro;
    public $prompt;
    public $notes;
    public $questions;


    public function attributeLabels()
    {
        return [
            'name' => 'Prompt title',
            'intro' => 'Intro',
            'prompt' => 'Prompt body',
            'notes' => 'Closing notes',
            'questions' => 'Exploration questions',
        ];
    }

    /**
     * @return array the validation rules.
     */
    public function rules()
    {
        return [
            [['name', 'prompt'], 'required'],
            ['name', 'filter', 'filter' => 'trim'],
            ['name', 'string', 'max' => 255],
            [['intro', 'prompt', 'notes', 'questions'], 'default'],
        ];
    }

    public function post()
    {
        if ($this->validate()) {
            $prompt = new Prompt();
            $prompt->name = $this->name;
            $prompt->intro = $this->intro;
            $prompt->prompt = $this->prompt;
            $prompt->notes = $this->notes;
            $prompt->questions = $this->questions;

            $prompt->author = \Yii::$app->user->getId();

            //die(print_r($prompt, TRUE));

            $prompt->save();

            return $prompt;
        }

        return null;
    }
}

The AR model:

<?php

namespace app\models;

use Yii;
use yii\db\ActiveRecord;

/**
 * Prompt is the model behind the prompt item.
 */
class Prompt extends ActiveRecord
{
    public $name;
    public $intro;
    public $prompt;
    public $notes;
    public $questions;
    public $status;
    public $author;

    public $id;

    /**
     * @return string the name of the table associated with this ActiveRecord class.
     */
    public static function tableName()
    {
        return 'prompt';
    }

    /**
     * @return array the attribute labels.
     */
    public function attributeLabels()
    {
        return [
            'name' => 'Prompt title',
            'intro' => 'Intro',
            'prompt' => 'Prompt body',
            'notes' => 'Closing notes',
            'questions' => 'Exploration questions',
            'status' => 'Status',
            'author' => 'Author ID',
        ];
    }
}

The controller:

<?php

namespace app\controllers;

use Yii;
use yii\filters\AccessControl;
use yii\web\Controller;
use yii\filters\VerbFilter;
use app\models\PromptForm;
use app\models\Prompt;

class PromptsController extends Controller
{
    public function actionIndex()
    {
        // Return a list of all prompts:
        return $this->render('index');
    }

    public function actionNew()
    {
        if (\Yii::$app->user->isGuest) {
            return $this->goHome();
        }

        $model = new PromptForm();
        if ($model->load(Yii::$app->request->post())) {
            if ($prompt = $model->post()) {
                Yii::$app->getSession()->setFlash('success', 'Your prompt was created successfully!');
                return $this->goHome();
            } else {
                Yii::$app->getSession()->setFlash('error', 'Error while submitting your prompt.');
            }
        }

        return $this->render('create', [
            'model' => $model,
        ]);
    }
}
7

7 Answers

21
votes

Okay, I figured it out. Turns out that if you declare public attributes in your ActiveRecord model, they obscure the automatic attributes that are created by AR. Data gets assigned to your obscuring attributes but doesn't get sent into the database.

The correct AR model should have been simply this:

<?php

namespace app\models;

use Yii;
use yii\db\ActiveRecord;

class Prompt extends ActiveRecord
{
    /**
     * @return string the name of the table associated with this ActiveRecord class.
     */
    public static function tableName()
    {
        return 'prompt';
    }
}
2
votes

Use

$prompt->save(false);

If that works that means that some validation rule fails.

1
votes

Try

    if ($model->load(Yii::$app->request->post())) {
                if ($prompt = $model->post()) {
                    $model->save()
                    Yii::$app->getSession()->setFlash('success', 'Your prompt was created successfully!');
                    return $this->goHome();
                } else {
                    Yii::$app->getSession()->setFlash('error', 'Error while submitting your prompt.');
                }
            }
0
votes

In controller, change your if condition as follow :

if ($prompt = $model->post() !== null) {

This will validate that the value which is return is not null.
Your current validation condition is only validating where value is get assigned to variable $prompt or not. And that's why it's always returns true.

0
votes

I ran across the same problem recently, when I combine the Active Record class with The Model class. Cause I know that AR actually extends Model in Yii2. Why not write less code.So I move the code from the Model to the AR.

$model = new User();  
$model->load(Yii::$app->request->post())

But the AR's _attribute didn't get the post data in the form. the form data is actually in a Model object.

object(app\models\User)#39 (12) { ["password"]=> string(6) "google" ["newpass"]=> NULL ["name"]=> string(5) "Jane1" ["email"]=> string(16) "[email protected]" ["_attributes":"yii\db\BaseActiveRecord":private]=> array(2) { ["password_hash"]=> string(60) "$2y$13$.vNKpmosLjW/oYAhIezOZOj8rIG6QJvQj8tGHN2x78.75poXVn6Yi" ["auth_key"]=> string(32) "4XggNakVd-oeU28ny7obdw7gOmZJ-Rbu" }

simply delete the public attribute you want mass assign to the AR instance will make it work.

0
votes

For who is struggling with this problem, I would remember to check the beforeSave method, if present. I mistakenly commented out the return statement.

public function beforeSave($insert)
{
    // never toggle comment on this!!!
    return parent::beforeSave( $insert); 
}
0
votes

How to Troubleshoot

First thing you should add while developing to your _form.php is errorSummary():

<?php $form = ActiveForm::begin(); ?>
// Some input fields
...
<?= $form->errorSummary($model); ?> // <--- Add this 
...
<?php ActiveForm::end(); ?> 

Simplify

Why not use scenarios instead if there is some minimal variation form to form:

In your model:

    public function rules()
    {
        return [
            [['field_1'], 'required', 'on' => self::SCENARIO_ADD],  // only on add
            [['field_2'], 'required', 'on' => self::SCENARIO_UPDATE], // only on update
            [['field_3', 'field_4'], 'required'], // required all the time
        ];
    }

In your controller:

public function actionAdd()
{
    $model = new Model();
    $model->scenario = Model::SCENARIO_ADD;
    if ($model->load(Yii::$app->request->post())) {
        return $this->redirect(['view', 'id' => $model->id]);
    }

    return $this->render('add', ['model' => $model]);
}

Behaviors

Alternatively, rather than assign the user directly in your model, you could use a behavior like so:

https://www.yiiframework.com/doc/api/2.0/yii-behaviors-blameablebehavior

/**
 * {@inheritdoc}
 */
public function behaviors()
{
    return [
        [
            'class' => \yii\behaviors\BlameableBehavior::className(),
            'value' => Yii::$app->user->identity->username,
        ],
        [
            'class' => \yii\behaviors\TimestampBehavior::className(),
            'value' => new \yii\db\Expression('NOW()'),
        ],
        [
            'class' => 'sammaye\audittrail\LoggableBehavior',
            'userAttribute' => 'updated_by', //blameable attribute of the current model.
            'ignored' => ['updated_by', 'updated_at'], // This ignores fields from a selection of all fields, not needed with allowed
        ],
    ];
}