Catch an exception inside ActiveRecord::EVENT_BEFORE_UPDATE

Hi there everybody,

I have a behavior class that basically adapt the date format typed by the users (‘dd/MM/yyyy’) with the expected mysql date format that is (‘yyyy-MM-dd’) the code is the following:




<?php


namespace app\behaviors;


use yii\db\ActiveRecord;

use yii\base\Behavior;

use yii\base\Exception;


/**

 * Convert date attribute with the expected mysql date format

 *

 * @author Raffaele Tosti <raffaele AT digisin DOT it>

 */

class DateFormatBehavior extends Behavior {

  

  /**

   * @var array Attributes list to convert 

   */

  public $attributes = [];

  

  /**

   * @inheritdoc

   */

  public function attach($owner) {

    parent::attach($owner);

    

    if(!$this->owner instanceof ActiveRecord) {

      throw new Exception("This behavior is applicable only on classes that belongs or extends ActiveRecord");

    }

  }


  /**

   * @inheritdoc

   */

  public function events() {

    return [

      ActiveRecord::EVENT_BEFORE_INSERT => 'InsertDateFormat',

      ActiveRecord::EVENT_BEFORE_UPDATE => 'UpdateDateFormat',

      ActiveRecord::EVENT_AFTER_FIND => 'FindDateFormat'

    ];

  }

  

  /**

   * Convert the format before insertion

   */

  public function InsertDateFormat() {

    foreach ($this->attributes as $attribute) {

      if(isset($this->owner->{$attribute})) {

        $this->owner->{$attribute} = str_replace('/', '-', $this->owner->{$attribute});

        $this->owner->{$attribute} = \Yii::$app->formatter->asDate($this->owner->{$attribute}, 'yyyy-MM-dd');

      }

    }

  }

  

  /**

   * Convert the format before update

   */

  public function UpdateDateFormat($event) {

    foreach ($this->attributes as $attribute) {

      if(isset($this->owner->{$attribute})) {

        $old_value = $this->owner->getOldAttribute($attribute);

        if($old_value != $this->owner->{$attribute}){

            $this->owner->{$attribute} = \Yii::$app->formatter->asDate(str_replace('/', '-', $this->owner->{$attribute}), 'yyyy-MM-dd');

          

        }

      }

    }

  }

  

  /**

   * Convert the format after the model is found

   */

  public function FindDateFormat() {

    foreach ($this->attributes as $attribute) {

      if(isset($this->owner->{$attribute})) {

        $this->owner->{$attribute} = \Yii::$app->formatter->asDate($this->owner->{$attribute});

      }

    }

  }

}




if I type and invalid date \Yii::$app->formatter->asDate() method throw two Exceptions that are the following:

[b]

Caused by: Invalid Parameter – yii\base\InvalidParamException

‘78/98/7987’ is not a valid date time value: DateTime::__construct(): Failed to parse time string (78/98/7987) at position 0 (7): Unexpected character

Array

(

[warning_count] => 0

[warnings] => Array

(

)

[error_count] => 5

[errors] => Array

(

[0] => Unexpected character

[4] => Double date specification

[7] => Unexpected character

[8] => Unexpected character

[9] => Unexpected character

)

)

in /var/www/apache/plurima.snake.lappi.virt/htdocs/vendor/yiisoft/yii2/i18n/Formatter.php at line 698

Caused by: Exception

DateTime::__construct(): Failed to parse time string (78/98/7987) at position 0 (7): Unexpected character

in /var/www/apache/plurima.snake.lappi.virt/htdocs/vendor/yiisoft/yii2/i18n/Formatter.php at line 687

[/b]

I would like to catch such exception and deny the insertion/update of the model, possibly showing a validation error to the user. Since the introduction of \Trowable PHP7 predefined class, I have tried to catch the aforementioned exceptions in this way:




public function UpdateDateFormat($event) {

    foreach ($this->attributes as $attribute) {

      if(isset($this->owner->{$attribute})) {

        $old_value = $this->owner->getOldAttribute($attribute);

        if($old_value != $this->owner->{$attribute}){

          try{

            $this->owner->{$attribute} = \Yii::$app->formatter->asDate(str_replace('/', '-', $this->owner->{$attribute}), 'yyyy-MM-dd');

          } catch (\Throwable $t) {

            $this->owner->addError($attribute, 'Invalid date format');

            $event->isValid = false;

          } catch (\Exception $e) {

            //this is for PHP5 compatibility

            $this->owner->addError($attribute, 'Invalid date format');

            $event->isValid = false;

          }

          

        }

      }

    }

  }



However the exception is not handled at all and the error is displayed to the user, if i comment $event->isValid = false; the exception is handled and not raised but a wrong date is stored on the database and the attribute validation error is not taken into account at all, my question is: is there a way to gracefully catch this exception and show a validation error to the user instead of having a wrong date stored on the db or an exception not catched?

Thanks in advance

I think that’s possible by using EVENT_AFTER_VALIDATE. BEFORE_INSERT or BEFORE_UPDATE are too late to display errors.

Thank you very much @samdark, using the validation event I am now able to display validation error to users B)