Часто при работе над проектами встает вопрос в хранении и отображении дат, при работе с датами разработчик имеет выбор:
Особой разницы между INTEGER, DATETIME и DATE нету, все эти типы данных хранят дату без привязки к временной зоне, в случае использования INTEGER база данных вообще не знает, что вы храните дату и все операции по конвертации числа в дату вы выполняете самостоятельно, в случае использования DATETIME и DATE и выводе данных, БД конвертирует даты в человекочитаемый вид.
С другой стороны, есть тип данных TIMESTAMP, главный плюс которого это то, что при получении значений из базы данных, они отображаются с учетом часового пояса (на который настроена база данных), так же TIMESTAMP по-умолчанию NOT NULL, а его значение по-умолчанию равно NOW().
В случае, когда проекту не нужно учитывать часовые пояса, вполне нормальным является использование первого варианта, как более простого и понятного, этот вариант и хотелось бы рассмотреть, в контексте Yii2.
Когда вы создаете модель и один из атрибутов представляет из себя тип INTEGER, сложность состоит в том, что при выводе его нужно конвертировать в нужный вам формат (например: дд.мм.гггг), а при записи конвертировать обратно в число, при этом не забывая проверять установлено ли значение или нет, чтобы не записать в дату "0" или же подключать валидаторы, добавляя в них "кастыльные" правила: не равно нулю, больше нуля и т.п.
Для решения таких ситуаций в Yii очень хорошо подходят поведения (behaviors). Давайте посмотрим, как можно решить данную ситуацию на примере шаблона Yii2 Advanced.
Давайте представим, что у нас есть некая модель Human, которая имеет атрибуты id, имя и дату рождения:
namespace frontend\models;
use Yii;
use yii\db\ActiveRecord;
/**
*
* @property integer $id
* @property string $name
* @property integer $birthday
*/
class Human extends \yii\db\ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'human';
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['name'], 'required'],
[['birthday'], 'integer']
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'name' => 'Имя',
'birthday' => 'День рождения',
];
}
}
В итоге, для того, чтобы получить человекочитаемую дату рождения нам каждый раз нужно переводить число в дату и наоборот, для того, чтобы этого избежать давайте сделаем так:
1) Добавим поведение DateToTimeBehavior к себе, создадим папку behaviors в директории frontend и добавим в нее файл DateToTimeBehavior.php (не забываем про открытие и закрытие тегов php):
namespace frontend\behaviors;
use yii\behaviors\AttributeBehavior;
use yii\base\InvalidConfigException;
class DateToTimeBehavior extends AttributeBehavior {
public $timeAttribute;
public function getValue($event) {
if (empty($this->timeAttribute)) {
throw new InvalidConfigException(
'Can`t find "fromAttribute" property in ' . $this->owner->className()
);
}
if (!empty($this->owner->{$this->attributes[$event->name]})) {
$this->owner->{$this->timeAttribute} = strtotime(
$this->owner->{$this->attributes[$event->name]}
);
return date('d.m.Y', $this->owner->{$this->timeAttribute});
} else if (!empty($this->owner->{$this->timeAttribute})
&& is_numeric($this->owner->{$this->timeAttribute})
) {
$this->owner->{$this->attributes[$event->name]} = date(
'd.m.Y',
$this->owner->{$this->timeAttribute}
);
return $this->owner->{$this->attributes[$event->name]};
}
return null;
}
}
2) Изменим код в модели Human:
namespace frontend\models;
use frontend\behaviors\DateToTimeBehavior;
use Yii;
use yii\db\ActiveRecord;
/**
*
* @property integer $id
* @property string $name
* @property integer $birthday
*/
class Client extends \yii\db\ActiveRecord
{
public $birthday_formatted;
/**
* @inheritdoc
*/
public static function tableName()
{
return 'human';
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['name'], 'required'],
[['birthday'], 'integer'],
['birthday_formatted', 'date', 'format' => 'php:d.m.Y']
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'name' => 'Имя',
'birthday' => 'День рождение',
'birthday_formatted' => 'День рождение'
];
}
/**
* @inheritdoc
*/
public function behaviors()
{
return [
[
'class' => DateToTimeBehavior::className(),
'attributes' => [
ActiveRecord::EVENT_BEFORE_VALIDATE => 'birthday_formatted',
ActiveRecord::EVENT_AFTER_FIND => 'birthday_formatted',
],
'timeAttribute' => 'birthday'
]
];
}
}
Мы добавили публичное свойство birthday_formatted, в валидации добавили для него проверку, что это дата в формате php:d.m.Y, добавили для него описание (для вывода в виджетах) и самое главное - подключили поведение, в котором указали, что числовое поле у нас birthday и при действиях EVENT_BEFORE_VALIDATE, EVENT_AFTER_FIND, т.е. "до валидации" и "после получения данных модели из базы данных" перевести числовую дату в нормальную дату и добавить к свойству birthday_formatted.
Теперь при выводе данных в формы нам нужно использовать не поле birthday, а поле birthday_formatted, данные в которое будут добавляться автоматически при получении модели из базы данных и наоборот, в случае сохранения модели, данные из поля birthday_formatted будут переводиться в число и помещаться в поле birthday.