вторник, 5 апреля 2016 г.

Работа с моделями в Yii

Подход, который я вывел для себя, работает и с первой и со второй версии yii. И так Yii для работы с данными использует паттерн ActiveRecord - одна запись в таблице - один экземпляр класса. Но в жизни (реальных проектах) все сложнее. Редко когда одна форма работает только с одной таблицей. Из последних проектов простой пример - есть форма редактирования клиента, у клиента есть множество телефонов. Пользователю удобнее сразу указать все телефоны клиента в форме. В такой ситуации нам на помощь приходит сервисный класс. В Yii1 это будет наследник от CFormModel, Yii2 - Model. "Ручками" добавляем ему все свойства - тут удобнее добавить getter/setter для каждого свойства и внутри обращаться к ActiveRecord объектам. Незабываем добавлять правила валидации. При вызове метода save у сервисного класса "раскладываем" все по таблицам используя ActiveRecord модели и транзакцию. Вместо тысячи слов несколько строк кода

class Client extends CActiveRecord
{
    public function tableName()
    {
        return 'client';
    }

    public function rules()
    {
        return [['name', 'required'],];
    }
}

class Phone extends CActiveRecord
{
    public function tableName()
    {
        return 'phone';
    }

    public function rules()
    {
        return [
            ['number, client_id', 'required'],
            ['client_id', 'exist', 'className' => 'Client', 'attributeName' => 'id'],
        ];
    }
}

class ClientForm extends CFormModel
{
    /** @var Client */
    private $client;
    /** @var Phone */
    private $phones;

    public function rules()
    {
        return [['name', 'required'], ['phones', 'validatePhones']];
    }

    public function getName()
    {
        return $this->client->name;
    }

    public function setName($value)
    {
        $this->client->name = $value;
    }

    public function save()
    {
        if (!$this->validate()) {
            return false;
        }
        $tr = Yii::app()->db->beginTransaction();
        try {
            if (!$this->client->save(false)) {
                $this->addErrors($this->client->getErrors());
                throw new Exception;
            }
            $tr->commit();
            return true;
        } catch (Exception $e) {
            $tr->rollback();
        }
        return false;
    }
}

Тут реализованы не все методы, но с этого скелета уже можно начать. В одном проекте (который я сейчас делаю) у меня только getter/setter уже 20 шт и форма будет управлять 6 классами и их массивами. До того как я нашел такой "кашерный" вариант на просторах сети, я бы все засунул в контроллер. Больше я так не делаю.

Еще один довольно часто встречающийся кейс - хранение даты или любого форматированного поля (например номер телефона). Тут вариантов миллион и все они неправильные :-) А правильный мой. В базе дату можно хранить или в виде целого числа секунд, прошедших с 1 января 1970 года (вариант быстрый и удобный, если только не нужно потом делать расследование прямо в базе) или в виде типа date/datetime (удобно смотреть прямо в базе, но затратнее при хранении и выводе). Но когда мы начинаем работать через форму, пользователь не очень хочет видеть 2016-04-21 или 1459880043 в поле ввода, им подавай 21.04.2016. И так вместо тысячи слов код
Была моделька
class Client extends CActiveRecord
{
    public function tableName()
    {
        return 'client';
    }

    public function rules()
    {
        return [['name, birthday', 'required'],];
    }
}
добавляем фиктивное поле через набор getter/setter, специально для формы
/** * Class Client * @property string $birthday */
class Client extends CActiveRecord
{
    public function tableName()
    {
        return 'client';
    }

    public function rules()
    {
        return [['name,birthdayDate', 'required'], ['birthdayDate', 'date', 'format' => 'dd.MM.yyyy'],];
    }

    public function getBirthdayDate()
    {
        return Yii::app()->format->formatDate($this->birthday);
    }

    public function setBirthdayDate($value)
    {
        $this->birthday = date('Y-m-d', CDateTimeParser::parse($value, 'dd.MM.yyyy'));
    }
}
и в форме работаем с birthday через getter/setter

<div class="form-group">    
    <?= $form->labelEx($model, 'birthdayDate'); ?>
    <?= $form->dateField($model, 'birthdayDate', ['class' => 'input-sm']); ?>
    <?= $form->error($model, 'birthdayDate'); ?>
</div>

При правильной организации проекта - делается наследник от базовой модельки в отдельном модуле и эти getter/setter нигде больше не будут нам мешаться.

Комментариев нет:

Отправить комментарий