4

I have a Controller and an updateAction generated with giiant:

public function actionUpdate($id) {
    $model = $this->findModel($id);

    if ($model->load($_POST) && $model->save()) {
        return $this->redirect(['view', 'id' => $model->id]);
    } else {
        return $this->render('update', [
            'model' => $model,
        ]);
    }
}

and I would like to rename a file by overriding beforeSave in Model:

public function beforeSave($insert) {
    if ($this->isAttributeChanged('name')) {
        rename($this->getOldAttribute('name') . '.pdf', $this->name . '.pdf');
    }

    parent::beforeSave($insert);
}

it seems like the model is saved, still the form is rendered after saving, what can not really be. I'm sure that it's because of beforeSave, because if I comment it out, everything works normal. How can the use of beforeSave result in this inconsequent behavior? What am I missing? Thanks a lot!

user2511599
  • 796
  • 1
  • 13
  • 38

3 Answers3

10

If you review the source code of beforeSave, you would find that the insertion or updating process will be cancelled if your beforeSave does not return true. "it seems like the model is saved", actually it is not.

So adjust your code to this:

public function beforeSave($insert) {
    if ($this->isAttributeChanged('name')) {
        rename($this->getOldAttribute('name') . '.pdf', $this->name . '.pdf');
    }

    return parent::beforeSave($insert);
}
Paul
  • 1,630
  • 1
  • 16
  • 23
  • @user2511599 Your `beforeSave` does not return anything. You must return `true` or `false` to indicate whether the saving process should be cancelled or not. – Paul Jun 21 '18 at 10:52
  • I get it now, it's working, many thanks for your help! Have a nice day buddy! – user2511599 Jun 21 '18 at 10:56
2

Yii and other MVC frameworks have those kind of functions.

While you can write your "before save" code in the controller, prior to the save() function - it's more recommended and useful to use the beforeSave() function.

Reason 1: The M in the MVC

The beforeSave relates to the model, so it would be more logical to have a code that handles the model's properties (fields) in the model's file rather than having that code in the controller.

Reason 2: Saving is for insert & update

You use save() when you insert a new record and also when you update an existing record. Without using the beforeSave built-in function, you'll have to have 2 instances of your "manual" before save code. ("Waste" of code lines)

Reason 3: Saving a model from another controller

What if you'll be asked to expand your application and now you'd have to face a new controller that need to save that same model (from some reason - just a possible scenario) - you'll have to copy your "before save" code to that controller. While if you're using the built-in beforeSave function - you don't.

In conclusion, the main purpose of frameworks is to reduce the code you need to write while keeping anything logical (MVC separation). While you can do things differently, why not using what's already exists?

A simple example:

I have a table with two date fields. Every time I try to perform insert or update I need to get current system date and make my operation depending on operation type.

    public function beforeSave() {

        if ($this->isNewRecord) {
            $this->insertDate = new CDbExpression('NOW()');
        } else {
            $this->updateDate = new CDbExpression('NOW()');
        }

        return parent::beforeSave();
    }

I wrote this once so I dont have to write everytime I call save() on that object.

Also some databases prefer different time formats so you can handle them here:

    public function beforeSave() {
        $this->date = date('Y-m-d', $this->date);
        return parent::beforeSave();
    }
Muthusamy
  • 306
  • 1
  • 11
0

From the github repo of BaseActiveRecord:

......
/**
 * This method is called at the beginning of inserting or updating a record.
 *
 * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is `true`,
 * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is `false`.
 * When overriding this method, make sure you call the parent implementation like the following:
 *
 * ```php
 * public function beforeSave($insert)
 * {
 *     if (!parent::beforeSave($insert)) {
 *         return false;
 *     }
 *
 *     // ...custom code here...
 *     return true;
 * }
....
  • The parent function called before your customization
Luis Chanferoni
  • 336
  • 2
  • 4