Yii2 model save error

Previously, I was not using $model->save() function for inserting or updating any data. I was simply using createCommand() to execute query and it was working successfully. But, my team members ...

Previously, I was not using $model->save() function for inserting or updating any data. I was simply using createCommand() to execute query and it was working successfully. But, my team members asked me to avoid createCommand() and use $model->save();

Now, I started cleaning my code and problem is $model->save(); not working for me. I don’t know where i did mistake.

UsersController.php (Controller)

<?php
namespace appmodulesuserscontrollers;
use Yii;
use yiiwebNotFoundHttpException;
use yiifiltersVerbFilter;
use yiiswiftmailerMailer;
use yiifiltersAccessControl;
use yiiwebResponse;
use yiiwidgetsActiveForm;
use appmodulesusersmodelsUsers;
use appcontrollersCommonController;

class UsersController extends CommonController 
{
    .
    .

    public function actionRegister() {
    $model = new Users();

        // For Ajax Email Exist Validation
   if(Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())){
     Yii::$app->response->format = Response::FORMAT_JSON;
     return ActiveForm::validate($model);
   } 

   else if ($model->load(Yii::$app->request->post())) {
      $post = Yii::$app->request->post('Users');
      $CheckExistingUser = $model->findOne(['email' => $post['email']]);

      // Ok. Email Doesn't Exist
      if(!$CheckExistingUser) {

        $auth_key = $model->getConfirmationLink();
        $password = md5($post['password']);
        $registration_ip = Yii::$app->getRequest()->getUserIP();
        $created_at = date('Y-m-d h:i:s');

        $model->auth_key = $auth_key;
        $model->password = $password;
        $model->registration_ip = $registration_ip;
        $model->created_at = $created_at;

        if($model->save()) {
          print_r("asd");
        }

      }

    } 
    }
    .
    .
}

Everything OK in this except $model->save(); Not printing ‘asd’ as i echoed it.

And, if i write

else if ($model->load(Yii::$app->request->post() && $model->validate()) {

}

It’s not entering to this if condition.

And, if i write

if($model->save(false)) {
    print_r("asd");
}

It insert NULL to all columns and print ‘asd’

Users.php (model)

<?php

namespace appmodulesusersmodels;

use Yii;
use yiibaseModel;
use yiidbActiveRecord;
use yiihelpersSecurity;
use yiiwebIdentityInterface;
use appmodulesusersmodelsUserType;

class Users extends ActiveRecord implements IdentityInterface 
{

  public $id;
  public $first_name;
  public $last_name;
  public $email;
  public $password;
  public $rememberMe;
  public $confirm_password;
  public $user_type;
  public $company_name;
  public $status;
  public $auth_key;
  public $confirmed_at;
  public $registration_ip;
  public $verify_code;
  public $created_at;
  public $updated_at;
  public $_user = false;

  public static function tableName() {
    return 'users';
  }

  public function rules() {
    return [
      //First Name
      'FirstNameLength' => ['first_name', 'string', 'min' => 3, 'max' => 255],
      'FirstNameTrim' => ['first_name', 'filter', 'filter' => 'trim'],
      'FirstNameRequired' => ['first_name', 'required'],
      //Last Name
      'LastNameLength' => ['last_name', 'string', 'min' => 3, 'max' => 255],
      'LastNameTrim' => ['last_name', 'filter', 'filter' => 'trim'],
      'LastNameRequired' => ['last_name', 'required'],
      //Email ID
      'emailTrim' => ['email', 'filter', 'filter' => 'trim'],
      'emailRequired' => ['email', 'required'],
      'emailPattern' => ['email', 'email'],
      'emailUnique' => ['email', 'unique', 'message' => 'Email already exists!'],
      //Password
      'passwordRequired' => ['password', 'required'],
      'passwordLength' => ['password', 'string', 'min' => 6],
      //Confirm Password
      'ConfirmPasswordRequired' => ['confirm_password', 'required'],
      'ConfirmPasswordLength' => ['confirm_password', 'string', 'min' => 6],
      ['confirm_password', 'compare', 'compareAttribute' => 'password'],
      //Admin Type
      ['user_type', 'required'],
      //company_name
      ['company_name', 'required', 'when' => function($model) {
          return ($model->user_type == 2 ? true : false);
        }, 'whenClient' => "function (attribute, value) {
          return $('input[type="radio"][name="Users[user_type]"]:checked').val() == 2;
      }"], #'enableClientValidation' => false
      //Captcha
      ['verify_code', 'captcha'],

      [['auth_key','registration_ip','created_at'],'safe'] 
    ];
  }

  public function attributeLabels() {
    return [
      'id' => 'ID',
      'first_name' => 'First Name',
      'last_name' => 'Last Name',
      'email' => 'Email',
      'password' => 'Password',
      'user_type' => 'User Type',
      'company_name' => 'Company Name',
      'status' => 'Status',
      'auth_key' => 'Auth Key',
      'confirmed_at' => 'Confirmed At',
      'registration_ip' => 'Registration Ip',
      'confirm_id' => 'Confirm ID',
      'created_at' => 'Created At',
      'updated_at' => 'Updated At',
      'verify_code' => 'Verification Code',
    ];
  }

  //custom methods
  public static function findIdentity($id) {
    return static::findOne($id);
  }

  public static function instantiate($row) {
    return new static($row);
  }

  public static function findIdentityByAccessToken($token, $type = null) {
    throw new NotSupportedException('Method "' . __CLASS__ . '::' . __METHOD__ . '" is not implemented.');
  }

  public function getId() {
    return $this->id;
  }

  public function getAuthKey() {
    return $this->auth_key;
  }

  public function validateAuthKey($authKey) {
    return $this->auth_key === $auth_key;
  }

  public function validatePassword($password) {
    return $this->password === $password;
  }

  public function getFirstName() {
    return $this->first_name;
  }

  public function getLastName() {
    return $this->last_name;
  }

  public function getEmail() {
    return $this->email;
  }

  public function getCompanyName() {
    return $this->company_name;
  }

  public function getUserType() {
    return $this->user_type;
  }

  public function getStatus() {
    return $this->status;
  }

  public function getUserTypeValue() {
    $UserType = $this->user_type;
    $UserTypeValue = UserType::find()->select(['type'])->where(['id' => $UserType])->one();
    return $UserTypeValue['type'];
  }

  public function getCreatedAtDate() {
    $CreatedAtDate = $this->created_at;
    $CreatedAtDate = date('d-m-Y h:i:s A', strtotime($CreatedAtDate));
    return $CreatedAtDate;
  }

  public function getLastUpdatedDate() {
    $UpdatedDate = $this->updated_at;
    if ($UpdatedDate != 0) {
      $UpdatedDate = date('d-m-Y h:i:s A', strtotime($UpdatedDate));
      return $UpdatedDate;
    } else {
      return '';
    }
  }

  public function register() {
    if ($this->validate()) {
      return true;
    }
    return false;
  }

  public static function findByEmailAndPassword($email, $password) {
    $password = md5($password);
    $model = Yii::$app->db->createCommand("SELECT * FROM users WHERE email ='{$email}' AND password='{$password}' AND status=1");
    $users = $model->queryOne();
    if (!empty($users)) {
      return new Users($users);
    } else {
      return false;
    }
  }

  public static function getConfirmationLink() {
    $characters = 'abcedefghijklmnopqrstuvwxyzzyxwvutsrqponmlk';
    $confirmLinkID = '';
    for ($i = 0; $i < 10; $i++) {
      $confirmLinkID .= $characters[rand(0, strlen($characters) - 1)];
    }
    return $confirmLinkID = md5($confirmLinkID);
  }

}

Any help is appreciable. Please Help me.

Содержание

  1. Validation is passed, but $model->errors returns an array of error messages #9273
  2. Comments
  3. Yii2 model save error
  4. Комментарии RSS по email OK
  5. Active Record ¶
  6. Объявление классов Active Record ¶
  7. Настройка имени таблицы ¶
  8. Классы Active record называются «моделями» ¶
  9. Подключение к базам данных ¶
  10. Получение данных ¶
  11. Доступ к данным ¶
  12. Преобразование данных ¶
  13. Получение данных в виде массива ¶
  14. Пакетное получение данных ¶
  15. Сохранение данных ¶
  16. Валидация данных ¶
  17. Массовое присваивание ¶
  18. Обновление счётчиков ¶
  19. Dirty-атрибуты ¶
  20. Значения атрибутов по умолчанию ¶
  21. Приведение типов атрибутов ¶
  22. JSON в MySQL и PostgreSQL ¶
  23. Массивы в PostgreSQL ¶
  24. Обновление нескольких строк данных ¶
  25. Удаление данных ¶
  26. Жизненные циклы Active Record ¶
  27. Жизненный цикл создания нового объекта ¶
  28. Жизненный цикл получения данных ¶
  29. Жизненный цикл сохранения данных ¶
  30. Жизненный цикл удаления данных ¶
  31. Работа с транзакциями ¶
  32. Оптимистическая блокировка ¶
  33. Работа со связными данными ¶
  34. Объявление связей ¶
  35. Доступ к связным данным ¶
  36. Динамические запросы связных данных ¶
  37. Связывание посредством промежуточной таблицы ¶
  38. Отложенная и жадная загрузка ¶
  39. Использование JOIN со связями ¶
  40. Псевдонимы связанных таблиц ¶
  41. Обратные связи ¶
  42. Сохранение связных данных ¶
  43. Связывание объектов из разных баз данных ¶
  44. Тонкая настройка классов Query ¶
  45. Получение дополнительных атрибутов ¶

Validation is passed, but $model->errors returns an array of error messages #9273

I encountered a tricky bug when did file uploading. The problem is that, when form validation is passed, $model->save() returns false, and $model->getErrors() returns an array with one error message regarding file input only.

And the scenario, where its used is the following:

There are 3 columns in a table (id, name, cover) , and this is a very basic photo-album. The validation rules for this are pretty simple: when a user uploads a new photo, he must fill both name ( input type=»text» ) and cover ( input type=»file» ) fields. But when editing some existing record, he’s allowed not to upload a file, since that’s optional.

Pretty simple, right?

Now consider the relevant parts:

After filling both name and file fields in the browser manually, Yii’s client-side validator highlights all fields green, which means that validation is passed successfully.

Here come the dragons now (after submitting successfully validated form):

This bug occurs only in «Add form», while it works in «Edit form». When I remove the file input everything works as expected, so that must be its own issue.

The environment itself, if that matters in this case:

  • PHP 5.6
  • Apache 2.2
  • Yii 2.0 (downloaded that version 2 months ago)
  • Tested on Windows/Linux, and all browsers

The text was updated successfully, but these errors were encountered:

Источник

Yii2 model save error

Те, кто раньше не работал с Yii, на первых порах часто совершают очень нехорошую ошибку:

Вот такой, казалось бы, простой код убить может немало нервов. Дело в том, что save запросто может не сработать, если не выполнится правило валидации. А т.к. мы этого не проверили, произойдёт редирект без каких-либо признаков ошибки.

У меня проще, много раз об этом писал, но всем показалось неудобным 🙂 :

За метод saved отвечает поведение — EFormModelBehavior

Тут вообще вариаций много. Я предпочитаю вызывать validate сперва, да и в общем-то Gii так генерит. Иногда до валидации по событию что-то сделать надо или после валидации, а потом уже и сам save делаю. Местами вообще всё в транзакции занесено.

Но основная ошибка типична и многие натыкаются.

Вообще, по хорошему, при не прохождении валидации должно кидаться исключение, жалко что изначально в Yii так не реализовано.

Serge, непрохождение валидации — это вполне штатная ситуация. В большинстве случаев необходимо выводить ошибки в форму, а не кидать исключение, поэтому try-catch вместо if сильно раздули бы код и сделали бы его не очень красивым.

2Sam: Я говорил о том моменте когда пользователь пытается сохранить модель не прошедшую валидацию в БД, именно тогда нужно кидать исключение, в других случаях я соглашусь что кидание исключения будет излишним. 🙂

2Serge, В корне не согласен. Никто не мешает подключить поведение типа:

Тем самым мы убираем еще один if и сворачиваем все в конструкцию:

Так что если не устраивает вариант по умолчанию, выше я описал альтернативу для избежания подобных ошибок.

2Ekstazi: Внесу немного ясности про моё высказывание про исключение. Оно должно кидаться только тогда, когда вызван метод save() и модель не валидна! Это позволит избежать ошибки описанной в данной статье. И действительно жаль что такое не реализованно по умолчанию в модели Yii

А вы разве не знаете что save еще и валидацию делает ?

Поддержу Serge. Мне должно быть абсолютно пофиг, делает save валидацию, или не делает — это же реализация метода, инкапсулированная логика. То, что save возвращает false — не говорит о характере ошибки, и в этом есть запах дурного кода. Код бизнес логики должен быть скрыт за интерфейсом. Обработка ошибок через исключения — часть этого интерфейса. Безусловно, паттерн Валидатор чрезвычайно удобен. Когда мы выполняет явную проверку типа isValid, то естественно ожидается boolean. Но когда я сохраняю объект, то я ничего не ожидаю. При любой ошибке сохранения желателен Exception, так как уровней вложенности сохранения может быть очень много при развитом домене с большим числом relations. В случае ifов будет настоящий говнокод, везде придется учитывать, что же вернул save. А мог бы быть один блок try catch на всю транзакцию. Exception объясняет, что может произойти при исполнении save, это хорошая документация.

Если выполняется проверка — ожидается bool как результат. Если произошла ошибка выполнения — exception. Работа save — не проверка, следовательно более правильным считаю exception

Я работаю над проектом legacy, все ошибки на if . Без слез не взглянешь на это.

Помогите в топике пожалуйста http://www.yiiframework.com/forum/index.php?/topic/19386-%d0%bf%d0%be%d0%bb%d1%83%d1%87%d0%b5%d0%bd%d0%b8%d0%b5-%d0%b4%d0%b0%d0%bd%d0%bd%d1%8b%d1%85-%d1%81%d0%b2%d1%8f%d0%b7%d0%b0%d0%bd%d0%bd%d0%be%d0%b9-%d0%bc%d0%be%d0%b4%d0%b5%d0%bb%d0%b8-many-many-%d0%b2-cactivedataprovider/

А как такое может быть, что была ошибка валидации, а $model->errors пустой? Это я про Yii 2 спрашиваю .

Виталий, лучше с вопросами на форум yiiframework.ru.

You have to use try-catch anyway, because PDO may throw exception.

Правильно ли я понимаю, проверять save() необходимо также и по причине «а вставились ли вообще данные в таблицу»?

Источник

Active Record ¶

Active Record обеспечивает объектно-ориентированный интерфейс для доступа и манипулирования данными, хранящимися в базах данных. Класс Active Record соответствует таблице в базе данных, объект Active Record соответствует строке этой таблицы, а атрибут объекта Active Record представляет собой значение отдельного столбца строки. Вместо непосредственного написания SQL-выражений вы сможете получать доступ к атрибутам Active Record и вызывать методы Active Record для доступа и манипулирования данными, хранящимися в таблицах базы данных.

Для примера предположим, что Customer — это класс Active Record, который сопоставлен с таблицей customer , а name — столбец в таблице customer . Тогда вы можете написать следующий код для вставки новой строки в таблицу customer :

Вышеприведённый код аналогичен использованию следующего SQL-выражения в MySQL, которое менее интуитивно, потенциально может вызвать ошибки и даже проблемы совместимости, если вы используете различные виды баз данных:

Yii поддерживает работу с Active Record для следующих реляционных баз данных:

  • MySQL 4.1 и выше: посредством yiidbActiveRecord
  • PostgreSQL 7.3 и выше: посредством yiidbActiveRecord
  • SQLite 2 и 3: посредством yiidbActiveRecord
  • Microsoft SQL Server 2008 и выше: посредством yiidbActiveRecord
  • Oracle: посредством yiidbActiveRecord
  • CUBRID 9.3 и выше: посредством yiidbActiveRecord (Имейте в виду, что вследствие бага в PDO-расширении для CUBRID, заключение значений в кавычки не работает, поэтому необходимо использовать CUBRID версии 9.3 как на клиентской стороне, так и на сервере)
  • Sphinx: посредством yiisphinxActiveRecord , потребуется расширение yii2-sphinx
  • ElasticSearch: посредством yiielasticsearchActiveRecord , потребуется расширение yii2-elasticsearch

Кроме того Yii поддерживает использование Active Record со следующими NoSQL базами данных:

  • Redis 2.6.12 и выше: посредством yiiredisActiveRecord , потребуется расширение yii2-redis
  • MongoDB 1.3.0 и выше: посредством yiimongodbActiveRecord , потребуется расширение yii2-mongodb

В этом руководстве мы в основном будем описывать использование Active Record для реляционных баз данных. Однако большая часть этого материала также применима при использовании Active Record с NoSQL базами данных.

Объявление классов Active Record ¶

Для начала объявите свой собственный класс, унаследовав класс yiidbActiveRecord.

Настройка имени таблицы ¶

По умолчанию каждый класс Active Record ассоциирован с таблицей в базе данных. Метод tableName() получает имя таблицы из имени класса с помощью yiihelpersInflector::camel2id(). Если таблица не названа соответственно, вы можете переопределить данный метод.

Также может быть применён tablePrefix по умолчанию. Например, если tablePrefix задан как tbl_ , Customer преобразуется в tbl_customer , а OrderItem в tbl_order_item .

Если имя таблицы указано в формате <<%TableName>> , символ % заменяется префиксом. Например <<%post>> становится <> . Фигуриные скобки используются для экранирования в SQL-запросах.

В нижеследующем примере мы объявляем класс Active Record с названием Customer для таблицы customer .

Классы Active record называются «моделями» ¶

Объекты Active Record являются моделями. Именно поэтому мы обычно задаём классам Active Record пространство имён appmodels (или другое пространство имён, предназначенное для моделей).

Т.к. класс yiidbActiveRecord наследует класс yiibaseModel, он обладает всеми возможностями моделей, такими как атрибуты, правила валидации, способы сериализации данных и т.д.

Подключение к базам данных ¶

По умолчанию Active Record для доступа и манипулирования данными БД использует компонент приложения db в качестве компонента DB connection. Как сказано в разделе Объекты доступа к данным (DAO), вы можете настраивать компонент db на уровне конфигурации приложения как показано ниже:

Если вы хотите использовать для подключения к базе данных другой компонент подключения, отличный от db , вам нужно переопределить метод getDb():

Получение данных ¶

После объявления класса Active Record вы можете использовать его для получения данных из соответствующей таблицы базы данных. Этот процесс, как правило, состоит из следующих трёх шагов:

  1. Создать новый объект запроса вызовом метода yiidbActiveRecord::find();
  2. Настроить объект запроса вызовом методов построения запросов;
  3. Вызвать один из методов получения данных для извлечения данных в виде объектов Active Record.

Как вы могли заметить, эти шаги очень похожи на работу с построителем запросов. Различие лишь в том, что для создания объекта запроса вместо оператора new используется метод yiidbActiveRecord::find(), возвращающий новый объект запроса, являющийся представителем класса yiidbActiveQuery.

Ниже приведено несколько примеров использования Active Query для получения данных:

В примерах выше $customer — это объект класса Customer , в то время как $customers — это массив таких объектов. Все эти объекты заполнены данными таблицы customer .

Информация: Т.к. класс yiidbActiveQuery наследует yiidbQuery, вы можете использовать в нём все методы построения запросов и все методы класса Query как описано в разделе Построитель запросов.

Т.к. извлечение данных по первичному ключу или значениям отдельных столбцов достаточно распространённая задача, Yii предоставляет два коротких метода для её решения:

  • yiidbActiveRecord::findOne(): возвращает один объект Active Record, заполненный первой строкой результата запроса.
  • yiidbActiveRecord::findAll(): возвращает массив объектов Active Record, заполненных всеми полученными результатами запроса.

Оба метода могут принимать параметры в одном из следующих форматов:

  • скалярное значение: значение интерпретируется как первичный ключ, по которому следует искать. Yii прочитает информацию о структуре базы данных и автоматически определит, какой столбец таблицы содержит первичные ключи.
  • массив скалярных значений: массив интерпретируется как набор первичных ключей, по которым следует искать.
  • ассоциативный массив: ключи массива интерпретируются как названия столбцов, а значения — как содержимое столбцов, которое следует искать. За подробностями вы можете обратиться к разделу Hash Format

Нижеследующий код демонстрирует, каким образом эти методы могут быть использованы:

Внимание: Если вам нужно передать в эти методы данные, полученные от пользователя, убедитесь что передаваемое значение – это скаляр, а если необходимо указать условия в формате массива – убедитесь, что пользовательские данные не могут изменить структуру этого массива.

Примечание: Ни метод yiidbActiveRecord::findOne(), ни yiidbActiveQuery::one() не добавляет условие LIMIT 1 к генерируемым SQL-запросам. Если ваш запрос может вернуть много строк данных, вы должны вызвать метод limit(1) явно в целях улучшения производительности, например: Customer::find()->limit(1)->one() .

Помимо использования методов построения запросов вы можете также писать запросы на «чистом» SQL для получения данных и заполнения ими объектов Active Record. Вы можете делать это посредством метода yiidbActiveRecord::findBySql():

Не используйте дополнительные методы построения запросов после вызова метода findBySql(), т.к. они будут проигнорированы.

Доступ к данным ¶

Как сказано выше, получаемые из базы данные заполняют объекты Active Record и каждая строка результата запроса соответствует одному объекту Active Record. Вы можете получить доступ к значениям столбцов с помощью атрибутов этих объектов. Например так:

Примечание: Атрибуты объекта Active Record названы в соответствии с названиями столбцов связной таблицы с учётом регистра. Yii автоматически объявляет для каждого столбца связной таблицы атрибут в Active Record. Вы НЕ должны переопределять какие-либо из этих атрибутов.

Атрибуты Active Record названы в соответствии с именами столбцов таблицы. Если столбцы вашей таблицы именуются через нижнее подчёркивание, то может оказаться, что вам придётся писать PHP-код вроде этого: $customer->first_name — в нём будет использоваться нижнее подчёркивание для разделения слов в названиях атрибутов. Если вы обеспокоены единообразием стиля кодирования, вам придётся переименовать столбцы вашей таблицы соответствующим образом (например, назвать столбцы в стиле camelCase).

Преобразование данных ¶

Часто бывает так, что данные вводятся и/или отображаются в формате, который отличается от формата их хранения в базе данных. Например, в базе данных вы храните дни рождения покупателей в формате UNIX timestamp (что, кстати говоря, не является хорошим дизайном), в то время как во многих случаях вы хотите манипулировать днями рождения в виде строк формата ‘ДД.ММ.ГГГГ’ . Для достижения этой цели, вы можете объявить методы преобразования данных в ActiveRecord-классе Customer как показано ниже:

Теперь в своём PHP коде вместо доступа к $customer->birthday , вы сможете получить доступ к $customer->birthdayText , что позволить вам вводить и отображать дни рождения покупателей в формате ‘ДД.ММ.ГГГГ’ .

Подсказка: Вышеприведённый пример демонстрирует общий способ преобразования данных в различные форматы. Если вы работаете с датами и временем, вы можете использовать DateValidator и yiijuiDatePicker , которые проще в использовании и являются более мощными инструментами.

Получение данных в виде массива ¶

Несмотря на то, что получение данных в виде Active Record объектов является удобным и гибким, этот способ не всегда подходит при получении большого количества данных из-за больших накладных расходов памяти. В этом случае вы можете получить данные в виде PHP-массива, используя перед выполнением запроса метод asArray():

Примечание: В то время как этот способ бережёт память и улучшает производительность, он ближе к низкому слою абстракции базы данных и вы потеряете многие возможности Active Record. Важное отличие заключается в типах данных значений столбцов. Когда вы получаете данные в виде объектов Active Record, значения столбцов автоматически приводятся к типам, соответствующим типам столбцов; с другой стороны, когда вы получаете данные в массивах, значения столбцов будут строковыми (до тех пор, пока они являются результатом работы PDO-слоя без какой-либо обработки), несмотря на настоящие типы данных соответствующих столбцов.

Пакетное получение данных ¶

В главе Построитель запросов мы объясняли, что вы можете использовать пакетную выборку для снижения расходов памяти при получении большого количества данных из базы. Вы можете использовать такой же подход при работе с Active Record. Например:

Сохранение данных ¶

Используя Active Record, вы легко можете сохранить данные в базу данных, осуществив следующие шаги:

  1. Подготовьте объект Active Record;
  2. Присвойте новые значения атрибутам Active Record;
  3. Вызовите метод yiidbActiveRecord::save() для сохранения данных в базу данных.

Метод save() может вставить или обновить строку данных в зависимости от состояния Active Record объекта. Если объект создан с помощью оператора new , вызов метода save() приведёт к вставке новой строки данных; если объект был получен с помощью запроса на получение данных, вызов save() обновит строку таблицы, соответствующую объекту Active Record.

Вы можете различать два состояния Active Record объекта с помощью проверки значения его свойства isNewRecord. Это свойство также используется внутри метода save() как показано ниже:

Подсказка: Вы можете вызвать insert() или update() непосредственно, чтобы вставить или обновить строку данных в таблице.

Валидация данных ¶

Т.к. класс yiidbActiveRecord наследует класс yiibaseModel, он обладает такими же возможностями валидации данных. Вы можете объявить правила валидации переопределив метод rules() и осуществлять валидацию данных посредством вызовов метода validate().

Когда вы вызываете метод save(), по умолчанию он автоматически вызывает метод validate(). Только после успешного прохождения валидации происходит сохранение данных; в ином случае метод save() просто возвращает false , и вы можете проверить свойство errors для получения сообщений об ошибках валидации.

Подсказка: Если вы уверены, что ваши данные не требуют валидации (например, данные пришли из доверенного источника), вы можете вызвать save(false) , чтобы пропустить валидацию.

Массовое присваивание ¶

Как и обычные модели, объекты Active Record тоже обладают возможностью массового присваивания. Как будет показано ниже, используя эту возможность, вы можете одним PHP выражением присвоить значения множества атрибутов Active Record объекту. Запомните однако, что только безопасные атрибуты могут быть массово присвоены.

Обновление счётчиков ¶

Распространённой задачей является инкремент или декремент столбца в таблице базы данных. Назовём такие столбцы столбцами-счётчиками. Вы можете использовать метод updateCounters() для обновления одного или нескольких столбцов-счётчиков. Например:

Примечание: Если вы используете метод yiidbActiveRecord::save() для обновления столбца-счётчика, вы можете прийти к некорректному результату, т.к. вполне вероятно, что этот же счётчик был сохранён сразу несколькими запросами, которые читают и записывают этот же столбец-счётчик.

Dirty-атрибуты ¶

Когда вы вызываете save() для сохранения Active Record объекта, сохраняются только dirty-атрибуты. Атрибут считается dirty-атрибутом, если его значение было изменено после чтения из базы данных или же он был сохранён в базу данных совсем недавно. Заметьте, что валидация данных осуществляется независимо от того, имеются ли dirty-атрибуты в объекте Active Record или нет.

Active Record автоматически поддерживает список dirty-атрибутов. Это достигается за счёт хранения старых значений атрибутов и сравнения их с новыми. Вы можете вызвать метод yiidbActiveRecord::getDirtyAttributes() для получения текущего списка dirty-атрибутов. Вы также можете вызвать yiidbActiveRecord::markAttributeDirty(), чтобы явно пометить атрибут в качестве dirty-атрибута.

Если вам нужны значения атрибутов, какими они были до их изменения, вы можете вызвать getOldAttributes() или getOldAttribute().

Примечание: Сравнение старых и новых значений будет осуществлено с помощью оператора === , так что значение будет считаться dirty-значением даже в том случае, если оно осталось таким же, но изменило свой тип. Это часто происходит, когда модель получает пользовательский ввод из HTML-форм, где каждое значение представлено строкой. Чтобы убедиться в корректности типа данных, например для целых значений, вы можете применить фильтрацию данных: [‘attributeName’, ‘filter’, ‘filter’ => ‘intval’] .

Значения атрибутов по умолчанию ¶

Некоторые столбцы ваших таблиц могут иметь значения по умолчанию, объявленные в базе данных. Иногда вы можете захотеть предварительно заполнить этими значениями вашу веб-форму, которая соответствует Active Record объекту. Чтобы избежать повторного указания этих значений, вы можете вызвать метод loadDefaultValues() для заполнения соответствующих Active Record атрибутов значениями по умолчанию, объявленными в базе данных:

Приведение типов атрибутов ¶

При заполнении результатами запроса yiidbActiveRecord производит автоматическое приведение типов для значений атрибутов на основе информации из схемы базы данны. Это позволяет данным, полученным из колонки таблицы объявленной как целое, заноситься в экземпляр ActiveRecord как значение целого типа PHP, булево как булево и т.д. Однако, механизм приведения типов имеет несколько ограничений:

  • Числа с плавающей точкой не будут обработаны, а будут представленны как строки, в противном случае они могут потерять точность.
  • Конвертация целых чисел зависит от разрядности используемой операционной системы. В частности: значения колонок, объявленных как ‘unsigned integer’ или ‘big integer’ будут приведены к целому типу PHP только на 64-х разрядных системах, в то время как на 32-х разрядных — они будут представленны как строки.

Имейте в виду, что преобразование типов производиться только в момент заполнения экземпляра ActiveRecord данными из результата запроса. При заполнении данных из HTTP запроса или непосредственно через механизм доступа к полям — автоматическая конвертация не производтся. Схема таблицы базы данных также используется при построении SQL запроса для сохранения данных ActiveRecord, обеспечивая соответсвие типов связываемых параметров в запросе. Однако, над атрибутами объекта ActiveRecord не будет производиться приведение типов в процессе сохранения.

Совет: вы можете использовать поведение yiibehaviorsAttributeTypecastBehavior для того, чтобы производить приведение типов для ActiveRecord во время валидации или сохранения.

Начиная с 2.0.14, Yii ActiveRecord поддерживает сложные типы данных, такие как JSON или многомерные массивы.

JSON в MySQL и PostgreSQL ¶

После заполнения данных, значение из столбца JSON будет автоматически декодировано из JSON в соответствии со стандартными правилами декодирования JSON.

Чтобы сохранить значение атрибута в столбец JSON, ActiveRecord автоматически создаст объект JsonExpression, который будет закодирован в строку JSON на уровне QueryBuilder.

Массивы в PostgreSQL ¶

После заполнения данных значение из столбца Array будет автоматически декодировано из нотации PgSQL в объект ArrayExpression. Он реализует интерфейс PHP ArrayAccess , так что вы можете использовать его в качестве массива, или вызвать ->getValue () , чтобы получить сам массив.

Чтобы сохранить значение атрибута в столбец массива, ActiveRecord автоматически создаст объект [[yiidbArray Expression|ArrayExpression]], который будет закодирован QueryBuilder в строковое представление массива PgSQL.

Можно также использовать условия для столбцов JSON:

Обновление нескольких строк данных ¶

Методы, представленные выше, работают с отдельными Active Record объектами, инициируя вставку или обновление данных для отдельной строки таблицы. Вместо них для обновления нескольких строк одновременно можно использовать метод updateAll(), который является статическим.

Подобным образом можно использовать метод updateAllCounters() для обновления значений столбцов-счётчиков в нескольких строках одновременно.

Удаление данных ¶

Для удаления одной отдельной строки данных сначала получите Active Record объект, соответствующий этой строке, а затем вызовите метод yiidbActiveRecord::delete().

Вы можете вызвать yiidbActiveRecord::deleteAll() для удаления всех или нескольких строк данных одновременно. Например:

Примечание: будьте очень осторожны, используя метод deleteAll(), потому что он может полностью удалить все данные из вашей таблицы, если вы сделаете ошибку при указании условий удаления.

Жизненные циклы Active Record ¶

Важно понимать как устроены жизненные циклы Active Record при использовании Active Record для различных целей. В течение каждого жизненного цикла вызывается определённая последовательность методов, которые вы можете переопределять, чтобы получить возможность тонкой настройки жизненного цикла. Для встраивания своего кода вы также можете отвечать на конкретные события Active Record, которые срабатывают в течение жизненного цикла. Эти события особенно полезны, когда вы разрабатываете поведения, которые требуют тонкой настройки жизненных циклов Active Record.

Ниже мы подробно опишем различные жизненные циклы Active Record и методы/события, которые участвуют в жизненных циклах.

Жизненный цикл создания нового объекта ¶

Когда создаётся новый объект Active Record с помощью оператора new , следующий жизненный цикл имеет место:

  1. Вызывается конструктор класса;
  2. Вызывается init(): инициируется событие EVENT_INIT.

Жизненный цикл получения данных ¶

Когда происходит получение данных посредством одного из методов получения данных, каждый вновь создаваемый объект Active Record при заполнении данными проходит следующий жизненный цикл:

  1. Вызывается конструктор класса.
  2. Вызывается init(): инициируется событие EVENT_INIT.
  3. Вызывается afterFind(): инициируется событие EVENT_AFTER_FIND.

Жизненный цикл сохранения данных ¶

Когда вызывается метод save() для вставки или обновления объекта Active Record, следующий жизненный цикл имеет место:

  1. Вызывается beforeValidate(): инициируется событие EVENT_BEFORE_VALIDATE. Если метод возвращает false или свойство события yiibaseModelEvent::$isValid равно false , оставшиеся шаги не выполняются.
  2. Осуществляется валидация данных. Если валидация закончилась неудачей, после 3-го шага остальные шаги не выполняются.
  3. Вызывается afterValidate(): инициируется событие EVENT_AFTER_VALIDATE.
  4. Вызывается beforeSave(): инициируется событие EVENT_BEFORE_INSERT или событие EVENT_BEFORE_UPDATE. Если метод возвращает false или свойство события yiibaseModelEvent::$isValid равно false , оставшиеся шаги не выполняются.
  5. Осуществляется фактическая вставка или обновление данных в базу данных;
  6. Вызывается afterSave(): инициируется событие EVENT_AFTER_INSERT или событие EVENT_AFTER_UPDATE.

Жизненный цикл удаления данных ¶

Когда вызывается метод delete() для удаления объекта Active Record, следующий жизненный цикл имеет место:

  1. Вызывается beforeDelete(): инициируется событие EVENT_BEFORE_DELETE. Если метод возвращает false или свойство события yiibaseModelEvent::$isValid равно false , остальные шаги не выполняются.
  2. Осуществляется фактическое удаление данных из базы данных.
  3. Вызывается afterDelete(): инициируется событие EVENT_AFTER_DELETE.

Примечание: Вызов следующих методов НЕ инициирует ни один из вышеприведённых жизненных циклов:

Работа с транзакциями ¶

Есть два способа использования транзакций при работе с Active Record.

Первый способ заключается в том, чтобы явно заключить все вызовы методов Active Record в блок транзакции как показано ниже:

Примечание: в коде выше ради совместимости с PHP 5.x и PHP 7.x использованы два блока catch. Exception реализует интерфейс Throwable interface начиная с PHP 7.0. Если вы используете только PHP 7 и новее, можете пропустить блок с Exception .

Второй способ заключается в том, чтобы перечислить операции с базой данных, которые требуют тразнакционного выполнения, в методе yiidbActiveRecord::transactions(). Например:

Метод yiidbActiveRecord::transactions() должен возвращать массив, ключи которого являются именами сценариев, а значения соответствуют операциям, которые должны быть выполнены с помощью транзакций. Вы должны использовать следующие константы для обозначения различных операций базы данных:

  • OP_INSERT: операция вставки, осуществляемая с помощью метода insert();
  • OP_UPDATE: операция обновления, осуществляемая с помощью метода update();
  • OP_DELETE: операция удаления, осуществляемая с помощью метода delete().

Используйте операторы | для объединения вышеприведённых констант при обозначении множества операций. Вы можете также использовать вспомогательную константу OP_ALL, чтобы обозначить одной константой все три вышеприведённые операции.

Оптимистическая блокировка ¶

Оптимистическая блокировка — это способ предотвращения конфликтов, которые могут возникать, когда одна и та же строка данных обновляется несколькими пользователями. Например, пользователь A и пользователь B одновременно редактируют одну и ту же wiki-статью. После того, как пользователь A сохранит свои изменения, пользователь B нажимает на кнопку «Сохранить» в попытке также сохранить свои изменения. Т.к. пользователь B работал с фактически-устаревшей версией статьи, было бы неплохо иметь способ предотвратить сохранение его варианта статьи и показать ему некоторое сообщение с подсказкой о том, что произошло.

Оптимистическая блокировка решает вышеприведённую проблему за счёт использования отдельного столбца для сохранения номера версии каждой строки данных. Когда строка данных сохраняется с использованием устаревшего номера версии, выбрасывается исключение yiidbStaleObjectException, которое предохраняет строку от сохранения. Оптимистическая блокировка поддерживается только тогда, когда вы обновляете или удаляете существующую строку данных, используя методы yiidbActiveRecord::update() или yiidbActiveRecord::delete() соответственно.

Для использования оптимистической блокировки:

  1. Создайте столбец в таблице базы данных, ассоциированной с классом Active Record, для сохранения номера версии каждой строки данных. Столбец должен быть типа big integer (в Mysql это будет BIGINT DEFAULT 0 ).
  2. Переопределите метод yiidbActiveRecord::optimisticLock() таким образом, чтобы он возвращал название этого столбца.
  3. В веб-форме, которая принимает пользовательский ввод, добавьте скрытое поле для сохранения текущей версии обновляемой строки. Убедитесь, что для вашего атрибута с версией объявлены правила валидации, и валидация проходит успешно.
  4. В действии контроллера, которое занимается обновлением строки данных с использованием Active Record, оберните в блок try. catch код и перехватывайте исключение yiidbStaleObjectException. Реализуйте необходимую бизнес-логику (например, возможность слияния изменений, подсказку о том, что данные устарели) для разрешения возникшего конфликта.

Например, предположим, что столбец с версией называется version . Вы можете реализовать оптимистическую блокировку с помощью подобного кода:

Работа со связными данными ¶

Помимо работы с отдельными таблицами баз данных, Active Record также имеет возможность объединять связные данные, что делает их легко-доступными для получения через основные объекты данных. Например, данные покупателя связаны с данными заказов, потому что один покупатель может осуществить один или несколько заказов. С помощью объявления этой связи вы можете получить возможность доступа к информации о заказе покупателя с помощью выражения $customer->orders , которое возвращает информацию о заказе покупателя в виде массива объектов класса Order , которые являются Active Record объектами.

Объявление связей ¶

Для работы со связными данными посредством Active Record вы прежде всего должны объявить связи в классе Active Record. Эта задача решается простым объявлением методов получения связных данных для каждой интересующей вас связи как показано ниже:

В вышеприведённом коде мы объявили связь orders для класса Customer и связь customer для класса Order .

Каждый метод получения связных данных должен быть назван в формате getXyz . Мы называем xyz (первая буква в нижнем регистре) именем связи. Помните, что имена связей чувствительны к регистру.

При объявлении связи, вы должны указать следующую информацию:

  • кратность связи: указывается с помощью вызова метода hasMany() или метода hasOne(). В вышеприведённом примере вы можете легко увидеть в объявлениях связей, что покупатель может иметь много заказов в то время, как заказ может быть сделан лишь одним покупателем.
  • название связного Active Record класса: указывается в качестве первого параметра для метода hasMany() или для метода hasOne(). Рекомендуется использовать код Xyz::class , чтобы получить строку с именем класса, при этом вы сможете воспользоваться возможностями авто-дополнения кода, встроенного в IDE, а также получите обработку ошибок на этапе компиляции.

связь между двумя типами данных: указываются столбцы с помощью которых два типа данных связаны. Значения массива — это столбцы основного объекта данных (представлен классом Active Record, в котором объявляется связь), в то время как ключи массива — столбцы связанных данных.

Есть простой способ запомнить это правило: как вы можете увидеть в примере выше, столбец связной Active Record указывается сразу после указания самого класса Active Record. Вы видите, что customer_id — это свойство класса Order , а id — свойство класса Customer .

Внимание: Имя связи relation зарезервировано. Его использование приведёт к ошибке ArgumentCountError .

Доступ к связным данным ¶

После объявления связей вы можете получать доступ к связным данным с помощью имён связей. Это происходит таким же образом, каким осуществляется доступ к свойству объекта объявленному с помощью метода получения связных данных. По этой причине, мы называем его свойством связи. Например:

Информация: когда вы объявляете связь с названием xyz посредством геттера getXyz() , у вас появляется возможность доступа к свойству xyz подобно свойству объекта. Помните, что название связи чувствительно к регистру.

Если связь объявлена с помощью метода hasMany(), доступ к свойству связи вернёт массив связных объектов Active Record; если связь объявлена с помощью метода hasOne(), доступ к свойству связи вернёт связный Active Record объект или null , если связные данные не найдены.

Когда вы запрашиваете свойство связи в первый раз, выполняется SQL-выражение как показано в примере выше. Если то же самое свойство запрашивается вновь, будет возвращён результат предыдущего SQL-запроса без повторного выполнения SQL-выражения. Для принудительного повторного выполнения SQL-запроса, вы можете удалить свойство связи с помощью операции: unset($customer->orders) .

Примечание: Несмотря на то, что эта концепция выглядит похожей на концепцию свойств объектов, между ними есть важное различие. Для обычных свойств объектов значения свойств имеют тот же тип, который возвращает геттер. Однако метод получения связных данных возвращает объект yiidbActiveQuery, в то время как доступ к свойству связи возвращает объект yiidbActiveRecord или массив таких объектов. ` php $customer->orders; // массив объектов Order $customer->getOrders(); // объект ActiveQuery ` Это полезно при тонкой настройке запросов к связным данным, что будет описано в следующем разделе.

Динамические запросы связных данных ¶

Т.к. метод получения связных данных возвращает объект запроса yiidbActiveQuery, вы можете в дальнейшем перед его отправкой в базу данных настроить этот запрос, используя методы построения запросов. Например:

В отличие от доступа к данным с помощью свойства связи, каждый раз при выполнении такого динамического запроса посредством метода получения связных данных будет выполняться SQL-запрос, даже если тот же самый динамический запрос был отправлен ранее.

Иногда вы можете даже захотеть настроить объявление связи таким образом, чтобы вы могли более просто осуществлять динамические запросы связных данных. Например, вы можете объявить связь bigOrders как показано ниже:

После этого вы сможете выполнять следующие запросы связных данных:

Связывание посредством промежуточной таблицы ¶

При проектировании баз данных, когда между двумя таблицами имеется кратность связи many-to-many, обычно вводится промежуточная таблица. Например, таблицы order и item могут быть связаны посредством промежуточной таблицы с названием order_item . Один заказ будет соотноситься с несколькими товарами, в то время как один товар будет также соотноситься с несколькими заказами.

При объявлении подобных связей вы можете пользоваться методом via() или методом viaTable() для указания промежуточной таблицы. Разница между методами via() и viaTable() заключается в том, что первый метод указывает промежуточную таблицу с помощью названия связи, в то время как второй метод непосредственно указывает промежуточную таблицу. Например:

Использовать связи, объявленные с помощью промежуточных таблиц, можно точно также, как и обычные связи. Например:

Отложенная и жадная загрузка ¶

В разделе Доступ к связным данным, мы показывали, что вы можете получать доступ к свойству связи объекта Active Record точно также, как получаете доступ к свойству обычного объекта. SQL-запрос будет выполнен только во время первого доступа к свойству связи. Мы называем подобный способ получения связных данных отложенной загрузкой. Например:

Отложенная загрузка очень удобна в использовании. Однако этот метод может вызвать проблемы производительности, когда вам понадобится получить доступ к тем же самым свойствам связей для нескольких объектов Active Record. Рассмотрите следующий пример кода. Сколько SQL-запросов будет выполнено?

Как вы могли заметить по вышеприведённым комментариям кода, будет выполнен 101 SQL-запрос! Это произойдёт из-за того, что каждый раз внутри цикла будет выполняться SQL-запрос при получении доступа к свойству связи orders каждого отдельного объекта Customer .

Для решения этой проблемы производительности вы можете, как показано ниже, использовать подход, который называется жадная загрузка:

Посредством вызова метода yiidbActiveQuery::with(), вы указываете объекту Active Record вернуть заказы первых 100 покупателей с помощью одного SQL-запроса. В результате снижаете количество выполняемых SQL-запросов от 101 до 2!

Вы можете жадно загружать одну или несколько связей. Вы можете даже жадно загружать вложенные связи. Вложенная связь — это связь, которая объявлена внутри связного Active Record класса. Например, Customer связан с Order посредством связи orders , а Order связан с Item посредством связи items . При формировании запроса для Customer , вы можете жадно загрузить items , используя нотацию вложенной связи orders.items .

Ниже представлен код, который показывает различные способы использования метода with(). Мы полагаем, что класс Customer имеет две связи: orders и country — в то время как класс Order имеет лишь одну связь items .

Вы можете жадно загрузить более глубокие вложенные связи, такие как a.b.c.d . Все родительские связи будут жадно загружены. Таким образом, когда вы вызываете метод with() с параметром a.b.c.d , вы жадно загрузите связи a , a.b , a.b.c и a.b.c.d .

Информация: В целом, когда жадно загружается N связей, среди которых M связей объявлено с помощью промежуточной таблицы, суммарное количество выполняемых SQL-запросов будет равно N+M+1 . Заметьте, что вложенная связь a.b.c.d насчитывает 4 связи.

Когда связь жадно загружается, вы можете настроить соответствующий запрос получения связных данных с использованием анонимной функции. Например:

Когда настраивается запрос на получение связных данных для какой-либо связи, вы можете указать название связи в виде ключа массива и использовать анонимную функцию в качестве соответствующего значения этого массива. Анонимная функция получит параметр $query , который представляет собой объект yiidbActiveQuery, используемый для выполнения запроса на получение связных данных для данной связи. В вышеприведённом примере кода мы изменили запрос на получение связных данных, наложив на него дополнительное условие выборки статуса заказов.

Примечание: Если вы вызываете метод select() в процессе жадной загрузки связей, вы должны убедиться, что будут выбраны столбцы, участвующие в объявлении связей. Иначе связные модели будут загружены неправильно. Например:

Использование JOIN со связями ¶

Примечание: Материал этого раздела применим только к реляционным базам данных, таким как MySQL, PostgreSQL, и т.д.

Запросы на получение связных данных, которые мы рассмотрели выше, ссылаются только на столбцы основной таблицы при извлечении основной информации. На самом же деле нам часто нужно ссылаться в запросах на столбцы связных таблиц. Например, мы можем захотеть получить покупателей, для которых имеется хотя бы один активный заказ. Для решения этой проблемы мы можем построить запрос с использованием JOIN как показано ниже:

Примечание: Важно однозначно указывать в SQL-выражениях имена столбцов при построении запросов на получение связных данных с участием оператора JOIN. Наиболее распространённая практика — предварять названия столбцов с помощью имён соответствующих им таблиц.

Однако лучшим подходом является использование имеющихся объявлений связей с помощью вызова метода yiidbActiveQuery::joinWith():

Оба подхода выполняют одинаковый набор SQL-запросов. Однако второй подход более прозрачен и прост.

По умолчанию, метод joinWith() будет использовать конструкцию LEFT JOIN для объединения основной таблицы со связной. Вы можете указать другой тип операции JOIN (например, RIGHT JOIN ) с помощью третьего параметра этого метода — $joinType . Если вам нужен INNER JOIN , вы можете вместо этого просто вызвать метод innerJoinWith().

Вызов метода joinWith() будет жадно загружать связные данные по умолчанию. Если вы не хотите получать связные данные, вы можете передать во втором параметре $eagerLoading значение false .

Подобно методу with() вы можете объединять данные с одной или несколькими связями; вы можете настроить запрос на получение связных данных «на лету»; вы можете объединять данные с вложенными связями; вы можете смешивать использование метода with() и метода joinWith(). Например:

Иногда во время объединения двух таблиц вам может потребоваться указать некоторые дополнительные условия рядом с оператором ON во время выполнения JOIN-запроса. Это можно сделать с помощью вызова метода yiidbActiveQuery::onCondition() как показано ниже:

Вышеприведённый запрос вернёт всех покупателей и для каждого покупателя вернёт все активные заказы. Заметьте, что это поведение отличается от нашего предыдущего примера, в котором возвращались только покупатели, у которых был как минимум один активный заказ.

Информация: Когда в объекте yiidbActiveQuery указано условие выборки с помощью метода onCondition(), это условие будет размещено в конструкции ON , если запрос содержит оператор JOIN. Если же запрос не содержит оператор JOIN, такое условие будет автоматически размещено в конструкции WHERE .

Псевдонимы связанных таблиц ¶

Как уже было отмечено, при использовании в запросе JOIN-ов, приходится явно решать конфликты имён. Поэтому часто таблицам дают псевдонимы. Задать псевдоним для реляционного запроса можно следующим образом:

Выглядит это довольно сложно. Либо приходится задавать явно имена таблиц, либо вызывать Order::tableName() . Начиная с версии 2.0.7 вы можете задать и использовать псевдоним для связанной таблицы следующим образом:

Этот синтаксис работает для простых связей. Если необходимо использовать связующую таблицу, например $query->joinWith([‘orders.product’]) , то вызовы joinWith вкладываются друг в друга:

Обратные связи ¶

Объявления связей часто взаимны между двумя Active Record классами. Например, Customer связан с Order посредством связи orders , а Order взаимно связан с Customer посредством связи customer .

Теперь рассмотрим следующий участок кода:

Мы думали, что $customer и $customer2 эквивалентны, но оказалось, что нет! Фактически они содержат одинаковые данные, но являются разными объектами. Когда мы получаем доступ к данным посредством $order->customer , выполняется дополнительный SQL-запрос для заполнения нового объекта $customer2 .

Чтобы избежать избыточного выполнения последнего SQL-запроса в вышеприведённом примере, мы должны подсказать Yii, что customer — обратная связь относительно orders , и сделаем это с помощью вызова метода inverseOf() как показано ниже:

Теперь, после этих изменений в объявлении связи, получим:

Примечание: обратные связи не могут быть объявлены для связей, использующих промежуточную таблицу. То есть, если связь объявлена с помощью методов via() или viaTable(), вы не должны вызывать после этого метод inverseOf().

Сохранение связных данных ¶

Во время работы со связными данными вам часто требуется установить связи между двумя разными видами данных или удалить существующие связи. Это требует установки правильных значений для столбцов, с помощью которых заданы связи. При использовании Active Record вам может понадобится завершить участок кода следующим образом:

Active Record предоставляет метод link(), который позволяет выполнить эту задачу более красивым способом:

Метод link() требует указать название связи и целевой объект Active Record, с которым должна быть установлена связь. Метод изменит значения атрибутов, которые связывают два объекта Active Record, и сохранит их в базу данных. В вышеприведённом примере, метод присвоит атрибуту customer_id объекта Order значение атрибута id объекта Customer и затем сохранит его в базу данных.

Примечание: Невозможно связать два свежесозданных объекта Active Record.

Преимущество метода link() становится ещё более очевидным, когда связь объявлена посредством промежуточной таблицы. Например, вы можете использовать следующий код, чтобы связать объект Order с объектом Item :

Вышеприведённый код автоматически вставит строку данных в промежуточную таблицу order_item , чтобы связать объект order с объектом item .

Информация: Метод link() не осуществляет какую-либо валидацию данных во время сохранения целевого объекта Active Record. На вас лежит ответственность за валидацию любых введённых данных перед вызовом этого метода.

Существует противоположная операция для link() — это операция unlink(), она снимает существующую связь с двух объектов Active Record. Например:

По умолчанию метод unlink() задаст вторичному ключу (или ключам), который определяет существующую связь, значение null . Однако вы можете запросить удаление строки таблицы, которая содержит значение вторичного ключа, передав значение true в параметре $delete для этого метода.

Если связь построена на основе промежуточной таблицы, вызов метода unlink() инициирует очистку вторичных ключей в промежуточной таблице, или же удаление соответствующей строки данных в промежуточной таблице, если параметр $delete равен true .

Связывание объектов из разных баз данных ¶

Active Record позволяет вам объявить связи между классами Active Record, которые относятся к разным базам данных. Базы данных могут быть разных типов (например, MySQL и PostgreSQL или MS SQL и MongoDB), и они могут быть запущены на разных серверах. Вы можете использовать тот же самый синтаксис для осуществления запросов выборки связных данных. Например:

Вы можете использовать большую часть возможностей запросов получения связных данных, которые были описаны в этой главе.

Примечание: Применимость метода joinWith() ограничена базами данных, которые позволяют выполнять запросы между разными базами с использованием оператора JOIN. По этой причине вы не можете использовать этот метод в вышеприведённом примере, т.к. MongoDB не поддерживает операцию JOIN.

Тонкая настройка классов Query ¶

По умолчанию все запросы данных для Active Record поддерживаются с помощью класса yiidbActiveQuery. Для использования собственного класса запроса вам необходимо переопределить метод yiidbActiveRecord::find() и возвращать из него объект вашего собственного класса запроса. Например:

Теперь, когда вы будете осуществлять получение данных (например, выполните find() , findOne() ) или объявите связь (например, hasOne() ) с объектом Comment , вы будете работать с объектом класса CommentQuery вместо ActiveQuery .

Подсказка: В больших проектах рекомендуется использовать собственные классы запросов, которые будут содержать в себе большую часть кода, связанного с настройкой запросов, таким образом классы Active Record удастся сохранить более чистыми.

Вы можете настроить класс запроса большим количеством различных способов для улучшения методик построения запросов. Например, можете объявить новые методы построения запросов в собственном классе запросов:

Примечание: Вместо вызова метода where() старайтесь во время объявления новых методов построения запросов использовать andWhere() или orWhere() для добавления дополнительных условий, в этом случае уже заданные условия выборок не будут перезаписаны.

Это позволит вам писать код построения запросов как показано ниже:

Вы также можете использовать новые методы построения запросов, когда объявляете связи для класса Comment или осуществляете запрос для выборки связных данных:

Информация: В Yii версии 1.1 была концепция с названием scope. Она больше не поддерживается в Yii версии 2.0, и вы можете использовать собственные классы запросов и собственные методы построения запросов, чтобы добиться той же самой цели.

Получение дополнительных атрибутов ¶

Когда объект Active Record заполнен результатами запроса, его атрибуты заполнены значениями соответствующих столбцов из полученного набора данных.

Вы можете получить дополнительные столбцы или значения с помощью запроса и сохранить их внутри объекта Active Record. Например, предположим, что у нас есть таблица ‘room’, которая содержит информацию о доступных в отеле комнатах. Каждая комната хранит информацию о её геометрических размерах с помощью атрибутов ‘length’, ‘width’, ‘height’. Представьте, что вам требуется получить список всех доступных комнат, отсортированных по их объёму в порядке убывания. В этом случае вы не можете вычислять объём с помощью PHP, потому что нам требуется сортировать записи по объёму, но вы также хотите отображать объем в списке. Для достижения этой цели, вам необходимо объявить дополнительный атрибут в вашем Active Record классе ‘Room’, который будет хранить значение ‘volume’:

Далее вам необходимо составить запрос, который вычисляет объём комнаты и выполняет сортировку:

Возможность выбирать дополнительные атрибуты может быть особенно полезной для агрегирующих запросов. Представьте, что вам необходимо отображать список покупателей с количеством их заказов. Прежде всего вам потребуется объявить класс Customer со связью ‘orders’ и дополнительным атрибутом для хранения расчётов:

После этого вы сможете составить запрос, который объединяет заказы и вычисляет их количество:

Недостаток этого подхода заключается в том, что если данные для поля не загружены по результатам SQL запроса, то они должны быть вычисленны отдельно. Это означает, что запись, полученная посредством обычного запроса без дополнительных полей в разделе ‘select’, не может вернуть реальное значения для дополнительного поля. Это же касается и только что сохранненой записи.

Использование магических методов __get() и __set() позволяет эмулировать поведение обычного поля:

Если результат запроса на выборку данных не содержит поле ‘volume’, то модель сможет расчитать его автоматически используя имеющиеся атрибуты.

Вы также можете вычислять агрегируемые поля используя объявленные отношения:

При такой реализации, в случае когда ‘ordersCount’ присутсвует в разделе ‘select’ — значение ‘Customer::ordersCount’ будет заполнено из результатов запроса, в противном случае — оно будет вычислено по первому требованию на основании отношения Customer::orders .

Этот подход также можно использовать для быстрого доступа к некоторым данным отношений, в особенности для агрегации. Например:

Источник

Не могу понять почему не сохраняются данные в БД
При вызове метода save(false) у заполненной модели, создаются пустые поля в БД (хотя вроде как все правила для полей прописал):
Уж сижу несколько часов.

Контроллер (точнее один метод из него)

Код: Выделить всё

public function actionAddDriver(){

        $modelDriver = new Driver();

        $postRequest = Yii::$app->request->post();

        $isValidate = false;
        if($modelDriver->load($postRequest)){
            if($modelDriver->validate()){
                var_dump($modelDriver, true);
                $modelDriver->save(false);
                $isValidate = true;
            }
        }
        return $this->render('addDriver',['modelDriver'=>$modelDriver, 'isValidate'=>$isValidate]);
    }
 

Модель

Код: Выделить всё

<?php
namespace appmodels;

use yiidbActiveRecord;

class Driver extends ActiveRecord{

    public $name;
    public $lastname;
    public $patronymic;
    public $percent;

    public static function tableName(){
        return 'drivers';
    }

    public function getOrder(){
        return $this->hasMany(Order::className(),['driver_id'=>'id']);
    }

    public function attributeLabels(){
        return [
            'name'=>'Имя таксиста',
            'lastname'=>'Фамилия таксиста',
            'patronymic'=>'Отчество таксиста',
            'percent' => 'Процент'
        ];
    }

    public function rules(){
        return [
            [['name','percent'],'required'],
            [['name','lastname','patronymic'], 'string', 'length'=>[3,50]],
            ['percent','number'],
        ];
    }
}
 

Вид:

Код: Выделить всё

<?php
use yiiwidgetsActiveForm;
use yiihelpersHtml;
?>
    <?php if($isValidate):?>
        <div class="alert alert-success alert-dismissable">
            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
            Запись добавлена!
        </div>
    <?php else:?>
        <div class="alert alert-warning alert-dismissable">
            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
            ОШИБКА добавления!
        </div>
    <?php endif?>



<?php
$form = ActiveForm::begin(['id'=>'form-edit-driver','method'=>'post']);
    echo $form->field($modelDriver,'name')->textInput();
    echo $form->field($modelDriver,'lastname')->textInput();
    echo $form->field($modelDriver,'patronymic')->textInput();
    echo $form->field($modelDriver,'percent')->textInput();
    echo Html::submitButton('Добавить',['class'=>'btn btn-success']);
ActiveForm::end();

Даже не знаю с чем может быть проблема) знаю что из-за rules может не добавлять в БД, но вроде верно написал в правилах модели.

Раньше я не использовал функцию $model->save() для вставки или обновления любых данных. Я просто использовал createCommand() для выполнения запроса, и он успешно работал. Но члены моей команды попросили меня избегать createCommand() и использовать $model->save();

Теперь я начал очищать свой код, а проблема — $model->save(); не работает для меня. Я не знаю, где я ошибся.

UsersController.php (контроллер)

<?php
namespace appmodulesuserscontrollers;
use Yii;
use yiiwebNotFoundHttpException;
use yiifiltersVerbFilter;
use yiiswiftmailerMailer;
use yiifiltersAccessControl;
use yiiwebResponse;
use yiiwidgetsActiveForm;
use appmodulesusersmodelsUsers;
use appcontrollersCommonController;

class UsersController extends CommonController 
{
    .
    .

    public function actionRegister() {
    $model = new Users();

        // For Ajax Email Exist Validation
   if(Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())){
     Yii::$app->response->format = Response::FORMAT_JSON;
     return ActiveForm::validate($model);
   } 

   else if ($model->load(Yii::$app->request->post())) {
      $post = Yii::$app->request->post('Users');
      $CheckExistingUser = $model->findOne(['email' => $post['email']]);

      // Ok. Email Doesn't Exist
      if(!$CheckExistingUser) {

        $auth_key = $model->getConfirmationLink();
        $password = md5($post['password']);
        $registration_ip = Yii::$app->getRequest()->getUserIP();
        $created_at = date('Y-m-d h:i:s');

        $model->auth_key = $auth_key;
        $model->password = $password;
        $model->registration_ip = $registration_ip;
        $model->created_at = $created_at;

        if($model->save()) {
          print_r("asd");
        }

      }

    } 
    }
    .
    .
}

Все в порядке, кроме $model->save(); Не печатайте ‘asd’, как я его повторил.

И, если я пишу

else if ($model->load(Yii::$app->request->post() && $model->validate()) {

}

Он не входит в это условие if.

И, если я пишу

if($model->save(false)) {
    print_r("asd");
}

Вставляет NULL во все столбцы и печатает ‘asd’

Пользователи .php (модель)

<?php

namespace appmodulesusersmodels;

use Yii;
use yiibaseModel;
use yiidbActiveRecord;
use yiihelpersSecurity;
use yiiwebIdentityInterface;
use appmodulesusersmodelsUserType;

class Users extends ActiveRecord implements IdentityInterface 
{

  public $id;
  public $first_name;
  public $last_name;
  public $email;
  public $password;
  public $rememberMe;
  public $confirm_password;
  public $user_type;
  public $company_name;
  public $status;
  public $auth_key;
  public $confirmed_at;
  public $registration_ip;
  public $verify_code;
  public $created_at;
  public $updated_at;
  public $_user = false;

  public static function tableName() {
    return 'users';
  }

  public function rules() {
    return [
      //First Name
      'FirstNameLength' => ['first_name', 'string', 'min' => 3, 'max' => 255],
      'FirstNameTrim' => ['first_name', 'filter', 'filter' => 'trim'],
      'FirstNameRequired' => ['first_name', 'required'],
      //Last Name
      'LastNameLength' => ['last_name', 'string', 'min' => 3, 'max' => 255],
      'LastNameTrim' => ['last_name', 'filter', 'filter' => 'trim'],
      'LastNameRequired' => ['last_name', 'required'],
      //Email ID
      'emailTrim' => ['email', 'filter', 'filter' => 'trim'],
      'emailRequired' => ['email', 'required'],
      'emailPattern' => ['email', 'email'],
      'emailUnique' => ['email', 'unique', 'message' => 'Email already exists!'],
      //Password
      'passwordRequired' => ['password', 'required'],
      'passwordLength' => ['password', 'string', 'min' => 6],
      //Confirm Password
      'ConfirmPasswordRequired' => ['confirm_password', 'required'],
      'ConfirmPasswordLength' => ['confirm_password', 'string', 'min' => 6],
      ['confirm_password', 'compare', 'compareAttribute' => 'password'],
      //Admin Type
      ['user_type', 'required'],
      //company_name
      ['company_name', 'required', 'when' => function($model) {
          return ($model->user_type == 2 ? true : false);
        }, 'whenClient' => "function (attribute, value) {
          return $('input[type="radio"][name="Users[user_type]"]:checked').val() == 2;
      }"], #'enableClientValidation' => false
      //Captcha
      ['verify_code', 'captcha'],

      [['auth_key','registration_ip','created_at'],'safe'] 
    ];
  }

  public function attributeLabels() {
    return [
      'id' => 'ID',
      'first_name' => 'First Name',
      'last_name' => 'Last Name',
      'email' => 'Email',
      'password' => 'Password',
      'user_type' => 'User Type',
      'company_name' => 'Company Name',
      'status' => 'Status',
      'auth_key' => 'Auth Key',
      'confirmed_at' => 'Confirmed At',
      'registration_ip' => 'Registration Ip',
      'confirm_id' => 'Confirm ID',
      'created_at' => 'Created At',
      'updated_at' => 'Updated At',
      'verify_code' => 'Verification Code',
    ];
  }

  //custom methods
  public static function findIdentity($id) {
    return static::findOne($id);
  }

  public static function instantiate($row) {
    return new static($row);
  }

  public static function findIdentityByAccessToken($token, $type = null) {
    throw new NotSupportedException('Method "' . __CLASS__ . '::' . __METHOD__ . '" is not implemented.');
  }

  public function getId() {
    return $this->id;
  }

  public function getAuthKey() {
    return $this->auth_key;
  }

  public function validateAuthKey($authKey) {
    return $this->auth_key === $auth_key;
  }

  public function validatePassword($password) {
    return $this->password === $password;
  }

  public function getFirstName() {
    return $this->first_name;
  }

  public function getLastName() {
    return $this->last_name;
  }

  public function getEmail() {
    return $this->email;
  }

  public function getCompanyName() {
    return $this->company_name;
  }

  public function getUserType() {
    return $this->user_type;
  }

  public function getStatus() {
    return $this->status;
  }

  public function getUserTypeValue() {
    $UserType = $this->user_type;
    $UserTypeValue = UserType::find()->select(['type'])->where(['id' => $UserType])->one();
    return $UserTypeValue['type'];
  }

  public function getCreatedAtDate() {
    $CreatedAtDate = $this->created_at;
    $CreatedAtDate = date('d-m-Y h:i:s A', strtotime($CreatedAtDate));
    return $CreatedAtDate;
  }

  public function getLastUpdatedDate() {
    $UpdatedDate = $this->updated_at;
    if ($UpdatedDate != 0) {
      $UpdatedDate = date('d-m-Y h:i:s A', strtotime($UpdatedDate));
      return $UpdatedDate;
    } else {
      return '';
    }
  }

  public function register() {
    if ($this->validate()) {
      return true;
    }
    return false;
  }

  public static function findByEmailAndPassword($email, $password) {
    $password = md5($password);
    $model = Yii::$app->db->createCommand("SELECT * FROM users WHERE email ='{$email}' AND password='{$password}' AND status=1");
    $users = $model->queryOne();
    if (!empty($users)) {
      return new Users($users);
    } else {
      return false;
    }
  }

  public static function getConfirmationLink() {
    $characters = 'abcedefghijklmnopqrstuvwxyzzyxwvutsrqponmlk';
    $confirmLinkID = '';
    for ($i = 0; $i < 10; $i++) {
      $confirmLinkID .= $characters[rand(0, strlen($characters) - 1)];
    }
    return $confirmLinkID = md5($confirmLinkID);
  }

}

Любая помощь заметна. Пожалуйста, помогите мне.

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Yii2 fullcalendar error dist
  • Yii2 field error message
  • Yii2 exception error
  • Yii2 error reporting
  • Yii2 error report

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии