Рубрики
Uncategorized

Способ практики Yii – Запись активных записей

Автор оригинала: David Wong.

краткое введение

Yii предоставляет очень мощную библиотеку классов для поддержки работы всего бизнеса фреймворка, которым является Active Record (Активная запись, далее именуемая AR).

Основные понятия

Класс AR предоставляет объектно-ориентированный интерфейс для доступа к данным в базе данных.

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

Использование AR вместо собственных операторов SQL для выполнения запросов к базе данных может вызвать интуитивно понятные методы для достижения той же цели.

Например, вызов метода yii db ActiveRecord:: save () для выполнения опроса вставки или обновления создаст или обновит строку данных в таблице данных, связанной с классом AR:

$customer = new Customer();
$customer->name = 'Qiang';
$customer - > save (); // A row of new data is inserted into the customer table

Приведенный выше код эквивалентен использованию следующих собственных операторов SQL, но, очевидно, первый более интуитивно понятен, менее подвержен ошибкам и с меньшей вероятностью вызовет проблемы совместимости с различными системами баз данных (СУБД, Система управления базами данных).

$db->createCommand('INSERT INTO customer (name) VALUES (:name)', [
    ':name' => 'Qiang',
])->execute();

Ниже приведен список всех баз данных, которые в настоящее время поддерживаются функциями AR Yii:

  • MySQL 4.1 и выше: через yii db ActiveRecord

  • PostgreSQL 7.3 и выше: через yii db ActiveRecord

  • SQLite 2 и 3: Через активную запись базы данных yii

  • Microsoft SQL Server 2010 и выше: через yii db ActiveRecord

  • Оракул: Через yii db ActiveRecord

  • CUBRID 9.1 и выше: через yiidbActiveRecord

  • Сфинкс: Требования к расширениям yii2-sphinx через yii sphinx ActiveRecord

  • Эластичный поиск: Требования yii2-расширение elasticsearch через yii elasticsearch ActiveRecord

  • Redis 2.6.12 и выше: Требования yii2-расширение redis через yii redis ActiveRecord

  • MongoDB 1.3.0 и выше: Требования к расширениям yii2-mongodb через yii mongodb ActiveRecord

Как вы можете видеть, Yii обеспечивает не только поддержку AR для реляционных баз данных, но и поддержку баз данных NoSQL.

Объявить класс AR

Чтобы объявить класс AR, необходимо расширить базовый класс yiidbActiveRecord и реализовать метод tableName для возврата имени связанной с ним таблицы:

namespace app\models;

use yii\db\ActiveRecord;

class Customer extends ActiveRecord{
    /**
     *@ Return string returns the table name associated with the AR class
     */
    public static function tableName()
    {
        return 'customer';
    }
}

Доступ к данным столбца

AR отображает каждое поле соответствующей строки данных в переменную свойства (атрибут) и свойство объекта AR, точно так же, как общее свойство обычного объекта.

Имя переменной свойства совпадает с именем соответствующего поля, и размер имени такой же.

Прочитайте значения столбцов, используя следующий синтаксис:

// "id" and "mail" are the corresponding field names of the tables associated with the $customer object
$id = $customer->id;
$email = $customer->email;

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

$customer->email = '[email protected]';
$customer->save();

Установление соединения с базой данных

AR обменивается данными с базой данных с помощью объекта подключения к базе данных yii.

По умолчанию он использует компонент БД в качестве объекта подключения. Вы можете установить компонент БД в файле конфигурации приложения, точно так же, как это:

return [ 
    'components' => [ 
        'db' => [ 
            'class' => 'yii\db\Connection', 
            'dsn' => 'mysql:host=localhost;dbname=testdb', 
            'username' => 'demo', 
            'password' => 'demo', 
        ],
    ],
];

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

class Customer extends ActiveRecord{
    // ...
    public static function getDb()
    {
        Return Yii:: $app - > db2; // Use an application component called "db2"
    }
}

Запрос данных

AR предоставляет два способа построения запросов к БД и заполнения данных в экземплярах AR:

  • активная запись базы данных yii::найти()

  • активная запись базы данных yii::findBySql()

Оба вышеперечисленных метода возвращают экземпляры yiidbActiveQuery, которые наследуются от запроса БД yii, поэтому все они поддерживают один и тот же набор гибких и мощных методов запроса БД, таких как where (), join (), OrderBy () и так далее.

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

// Retrieve all active customers (customers with * active * status) and rank them by their ID:
$customers = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->orderBy('id')
    ->all();

// Returns a customer with ID 1:
$customer = Customer::find()
    ->where(['id' => 1])
    ->one();

// Return the number of active customers:
$count = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->count();

// Index result sets with customer ID:
// The $customers array is indexed by ID
$customers = Customer::find()->indexBy('id')->all();

// Retrieve customers with native SQL statements:
$sql = 'SELECT * FROM customer';
$customers = Customer::findBySql($sql)->all();

Совет: В приведенном выше коде Customer:: STATUS_ACTIVE-это константа, определенная в классе Customer. Это лучшая привычка программирования-использовать более значимое имя константы, чем писать мертвые строки или числа непосредственно в коде.

Существует два ярлыка: findOne и findAll () для возврата одного или набора экземпляров ActiveRecord. Первый возвращает первый сопоставленный экземпляр, а второй возвращает все. Например:

// Return a customer with ID 1
$customer = Customer::findOne(1);

// Returns a customer with ID 1 and status * active*
$customer = Customer::findOne([
    'id' => 1,
    'status' => Customer::STATUS_ACTIVE,
]);

// Returns a set of customers with ID 1, 2, 3
$customers = Customer::findAll([1, 2, 3]);

// Returns all customers whose status is "deleted"
$customer = Customer::findAll([
    'status' => Customer::STATUS_DELETED,
]);

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

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

Вы можете сделать это с помощью функции asArray ():

// Retrieve customer information in the form of an array rather than an object:
$customers = Customer::find()
    ->asArray()
    ->all();
// Each element of $customers is a key-value pair array

Пакетный сбор данных

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

Возможно, вы захотите использовать те же методы в AR, например…

// Extracting 10 customer information at a time
foreach (Customer::find()->batch(10) as $customers) {
    // Cusmers is an array of 10 or fewer customer objects
}
// Extracting 10 customers at a time and traversing them one by one
foreach (Customer::find()->each(10) as $customer) {
    // Customer is a "Customer" object
}
// Batch Query in Greedy Loading Mode
foreach (Customer::find()->with('orders')->each() as $customer) {

}

Оперативные данные (ТВОРОГ)

AR предоставляет следующие методы для вставки, обновления и удаления строки в таблице, связанной с объектом AR:

  • активная запись базы данных yii::сохранить()

  • активная запись базы данных yii::вставить()

  • активная запись базы данных yii::обновление()

  • активная запись базы данных yii::удалить()

AR также предоставляет некоторые статические методы, которые могут быть применены ко всей таблице, связанной с классом AR.

Будьте осторожны при использовании этих методов, потому что они работают на всем столе!

Например, deleteAll () удаляет все записи в таблице.

  • активная запись базы данных yii::счетчики обновлений()

  • активная запись базы данных yii::обновить все()

  • yiidbActiveRecord::Обновить все счетчики()

  • активная запись базы данных yii::удалить все()

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

// Insert new customer records
$customer = new Customer();
$customer->name = 'James';
$customer->email = '[email protected]';
$customer - > save (); // equivalent to $customer - > insert ();

// Update existing customer records
$customer = Customer::findOne($id);
$customer->email = '[email protected]';
$customer - > save (); // equivalent to $customer - > update ();

// Delete existing customer records
$customer = Customer::findOne($id);
$customer->delete();

// Delete multiple customer records older than 20 and male (Male)
Customer::deleteAll(
    'age > :age AND gender = :gender', 
    [':age' => 20, ':gender' => 'M']
);

// Add 1 to the age (age) field for all customers:
Customer::updateAllCounters(['age' => 1]);

Примечание: Метод save () вызывает один из методов insert () и update () в зависимости от того, является ли текущий объект AR новым объектом (внутри функции он проверяет значение yiidbActiveRecord:: isNewRecord).

Если объект AR инициализируется оператором new, метод save () вставляет данные в таблицу; если объект AR получен методом find (), метод save () обновляет соответствующие записи строк в таблице.

Ввод и проверка данных

Поскольку AR наследуется от базовой модели yii, он также поддерживает ввод данных, проверку и другие функции модели.

Например, вы можете объявить метод правил, чтобы переопределить базовую модель yii:: правила (); вы также можете выполнить пакетную обработку экземпляра AR; вы также можете выполнить проверку данных, вызвав yiibaseModel:: validate ().

При вызове функций save (), insert () и update () автоматически вызывается метод yii base Model:: validate (). Если проверка завершится неудачно, данные не будут сохранены в базе данных.

В следующем примере показано, как использовать AR для получения/проверки введенных пользователем данных и сохранения их в базе данных:

// Create a new record
$model = new Customer;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
    // Get user input data, validate and save it
}

// Update AR with primary key $id
$model = Customer::findOne($id);
if ($model === null) {
    throw new NotFoundHttpException;
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
    // Get user input data, validate and save it
}

Считывание значений по умолчанию

В столбце таблицы могут быть определены значения по умолчанию. Иногда при использовании веб-форм может потребоваться задать некоторые значения для AR.

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

php
     $customer = new Customer();
     $customer - > Load DefaultValues (); //... Render the HTML form of $customer ()...
?>

Жизненный цикл AR

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

В жизненном цикле обычно происходят некоторые типичные события.

Это очень полезно для развития поведения AR.

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

1. constructor
2. yii db ActiveRecord:: init (): triggers a yii db ActiveRecord:: EVENT_INIT event

При запросе данных с помощью метода yiidbActiveRecord:: find () каждый экземпляр AR будет иметь следующий жизненный цикл:

1. constructor
2. yii db ActiveRecord:: init (): triggers a yii db ActiveRecord:: EVENT_INIT event
3. yii db ActiveRecord:: afterFind (): triggers a yii db ActiveRecord:: EVENT_AFTER_FIND event

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

1. yii db ActiveRecord:: beforeValidate (): triggers a yii db ActiveRecord:: EVENT_BEFORE_VALIDATE event
2. yii db ActiveRecord:: afterValidate (): triggers a yii db ActiveRecord:: EVENT_AFTER_VALIDATE event
3. yii db ActiveRecord:: beforeSave (): triggers a yii db ActiveRecord:: EVENT_BEFORE_INSERT or yii db ActiveRecord:: EVENT_BEFORE_UPDA event
4. Perform actual data write or update
5. yii db ActiveRecord:: afterSave (): A yii db ActiveRecord:: EVENT_AFTER_INSERT or yii db ActiveRecord:: EVENT_AFTER_UPDATE event is triggered

Наконец, когда мы вызываем yii db ActiveRecord:: удалить () чтобы удалить данные, мы получаем следующий жизненный цикл:

1. yii db ActiveRecord:: beforeDelete (): triggers a yii db ActiveRecord:: EVENT_BEFORE_DELETE event
2. Execute actual data deletion
3. yii db ActiveRecord:: afterDelete (): triggers a yii db ActiveRecord:: EVENT_AFTER_DELETE event

Запрос связанных данных

Метод AR также может использоваться для запроса связанных данных таблицы данных (например, данные, выбранные из таблицы A, могут извлекать связанные данные таблицы B).

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

После установления связи массив объектов заказов, представляющих набор заказов текущего объекта клиента, можно получить с помощью $customer – > заказы.

Определение отношений ассоциации использует метод получения, который возвращает объекты yiidbActiveQuery, объекты yiidbActiveQuery содержат соответствующую информацию о контексте ассоциации, поэтому вы можете запрашивать только связанные данные.

Например:

class Customer extends \yii\db\ActiveRecord{ 
    public function getOrders() { 
        // Customers and orders establish a one-to-many relationship through Order. customer_id-> ID Association 
        return $this->hasMany(Order::className(), ['customer_id' => 'id']); 
    } 
} 

class Order extends \yii\db\ActiveRecord{ 
    // Order and customer establish one-to-one relationship through Customer.id-> customer_id Association 
    public function getCustomer() { 
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']); 
    } 
}

В приведенном выше примере используются методы yii db ActiveRecord:: hasMany () и yii db ActiveRecord:: имеет один ().

Два приведенных выше примера являются примерами моделирования отношения “многие к одному” и отношения “один к одному” ассоциативных данных.

Например, у клиента много заказов, и заказ принадлежит только одному клиенту.

Оба метода имеют два параметра и возвращают объект ActiveQuery базы данных yii.

$class: The association model class name, which must be a fully qualified class name.

$link: The associated column of the two tables should be in the form of a key-value pair of arrays. 

The key of the array is the column name of the associated table of $class, and the array value is the column name of the associated class of $class. 

Defining relationships based on foreign keys is the best method.

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

// Obtain orders from customers
$customer = Customer::findOne(1);
$orders = $customer - > orders; // orders is an array of Order objects

Приведенный выше код фактически выполняет следующие две инструкции SQL:

SELECT * FROM customer WHERE id=1;
SELECT * FROM order WHERE customer_id=1;

Совет: Выражение $customer – > заказы снова не будет выполнять второй SQL-запрос, и SQL-запрос будет выполняться только при первом использовании выражения.

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

unset($customer->orders);。

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

class Customer extends \yii\db\ActiveRecord{ 
    public function getBigOrders($threshold = 100) { 
        return $this->hasMany(Order::className(), ['customer_id' => 'id']) 
            ->where('subtotal > :threshold', [':threshold' => $threshold]) 
            ->orderBy('id'); 
    } 
}

Имеет много () возвращает объект ActiveQuery базы данных yii, который позволяет настраивать запрос с помощью метода yiidbActiveQuery.

Как указано выше, выполнение $customer – > bigOrders возвращает заказы общей стоимостью более 100. Используйте следующий код для изменения настроек:

$orders = $customer->getBigOrders(200)->all();

Примечание: Связанный запрос возвращает экземпляр yii db ActiveQuery. Если связанные данные связаны как объект (например, атрибут класса), возвращаемый результат является результатом связанного запроса, то есть экземпляром yiidbActiveRecord, или массивом, или нулем, в зависимости от разнообразия отношений ассоциации.

For example, $customer - > getOrders () returns an ActiveQuery instance, and $customer - > orders returns an array of Order objects (an empty array if the query result is empty).

Промежуточная таблица ассоциаций

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

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

class Order extends \yii\db\ActiveRecord{ 
    public function getItems() { 
        return $this->hasMany(Item::className(), ['id' => 'item_id']) 
            ->viaTable('order_item', ['order_id' => 'id']); 
    } 
}

Эти два метода похожи, за исключением того, что первым параметром метода yiidbActiveQuery:: via () является использование имени ассоциации, определенного в классе AREA. Описанный выше метод заменяет промежуточную таблицу и эквивалентен:

class Order extends \yii\db\ActiveRecord{
    public function getOrderItems()
    {
        return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
    }

    public function getItems()
    {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->via('orderItems');
    }
}

Отложенная загрузка и мгновенная загрузка (также известная как отложенная загрузка и жадная загрузка)

Как упоминалось ранее, при первом подключении связанного объекта AR выполнит запрос к базе данных для получения запрошенных данных и заполнения соответствующих атрибутов связанного объекта. Если один и тот же связанный объект подключен снова, и оператор запроса не выполняется, метод выполнения этого запроса базы данных называется “отложенная загрузка”.

Такие как:

// SQL executed: SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// SQL executed: SELECT * FROM order WHERE customer_id=1
$orders = $customer->orders;
// No SQL statement is executed
$orders2 = $customer - > orders; // Retrieve the cached data from the last query

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

// SQL executed: SELECT * FROM customer LIMIT 100
$customers = Customer::find()->limit(100)->all();

foreach ($customers as $customer) {
    // SQL executed: SELECT * FROM order WHERE customer_id=...
    $orders = $customer->orders;
    // Processing $orders...
}

Предполагая, что база данных находит более 100 клиентов, сколько операторов SQL будет выполняться в приведенном выше коде? 101! Первый SQL-запрос извлекает 100 клиентов, а затем каждый клиент выполняет SQL – запрос для извлечения всех заказов от этого клиента.

Для решения вышеуказанных проблем с производительностью вы можете использовать немедленную загрузку, вызвав метод yii db ActiveQuery:: with ().

// SQL executed: SELECT * FROM customer LIMIT 100;
// SELECT * FROM orders WHERE customer_id IN (1,2,...)
$customers = Customer::find()->limit(100)
    ->with('orders')->all();

foreach ($customers as $customer) {
    // No SQL statement is executed
    $orders = $customer->orders;
    // Processing $orders...
}

Как вы можете видеть, для одной и той же задачи требуется только два оператора SQL.

Примечание: Обычно N взаимосвязей ассоциаций загружаются немедленно, и M взаимосвязей ассоциаций определяются с помощью via () или через таблицу (), и выполняются 1 + M + N SQL-запросов: один запрос извлекает количество строк в основной таблице, один запрос дается каждой (M) промежуточной таблице, и один запрос дается каждой (N) таблице ассоциаций.

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

$orders = Order::find()
    ->select(['id', 'amount'])
    ->with('customer')
    ->all();
     
// orders [0] - > customer is always empty, so use the following code to solve this problem:

$orders = Order::find()
    ->select(['id', 'amount', 'customer_id'])
    ->with('customer')
    ->all();

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

$customer = Customer::findOne(1);
// Delayed loading: SELECT * FROM order WHERE customer_id = 1 AND subtotal > 100
$orders = $customer->getOrders()->where('subtotal>100')->all();

// Instant loading: SELECT * FROM customer LIMIT 100
//          SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100
$customers = Customer::find()
    ->limit(100)
    ->with([
        'orders' => function($query) {
            $query->andWhere('subtotal>100');
        },
    ])
    ->all();

Обратная зависимость

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

class Customer extends ActiveRecord{ 
    .... 
    public function getOrders() { 
        return $this->hasMany(Order::className(), ['customer_id' => 'id']); 
    } 
} 

class Order extends ActiveRecord{ 
    .... 
    public function getCustomer() { 
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']); 
    } 
}

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

Подключение клиента – > заказы запускает одну инструкцию SQL, а подключение клиента к одному заказу запускает другую.

// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// Output "different"
// SELECT * FROM order WHERE customer_id=1
// SELECT * FROM customer WHERE id=1
if ($customer->orders[0]->customer === $customer) {
    Echo'same';
} else {
    Echo'different';
}

Чтобы избежать избыточного выполнения последнего оператора, мы можем определить противоположную ассоциацию для ассоциации клиента или заказа, что может быть достигнуто путем вызова yiidbActiveQuery:: inverseOf ().

class Customer extends ActiveRecord{ 
    .... 
    public function getOrders() { 
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])
            ->inverseOf('customer'); 
    } 
}

Теперь мы также выполним приведенный выше запрос, и мы получим:

// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// The same output
// SELECT * FROM order WHERE customer_id=1
if ($customer->orders[0]->customer === $customer) {
    Echo'same';
} else {
    Echo'different';
}

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

// SELECT * FROM customer
// SELECT * FROM order WHERE customer_id IN (1, 2, ...)
$customers = Customer::find()->with('orders')->all();
// The same output
if ($customers[0]->orders[0]->customer === $customers[0]) {
    Echo'same';
} else {
    Echo'different';
}

Примечание: Относительные отношения не могут быть определены в ассоциативных отношениях, содержащих промежуточные таблицы.

That is, if your relationship is defined by the yii db ActiveQuery:: via () or yii db ActiveQuery:: viaTable () method, you cannot call the yii db ActiveQuery:: inverseOf () method.

Запрос ассоциации Типов соединений

При использовании реляционных баз данных обычно соединяют несколько таблиц и явно используют различные запросы на ОБЪЕДИНЕНИЕ.

Условия запроса и параметры операторов SQL соединения могут быть повторно использованы с помощью yiidbActiveQuery:: join с () вместо использования yiidbActiveQuery:: join () для достижения цели.

// Find all orders and sort them by customer ID and order ID, and load the "customer" table greedily
$orders = Order::find()
    ->joinWith('customer')
    ->orderBy('customer.id, order.id')
    ->all();
// Find all orders that include books and load the "books" table instantly with the `INNER JOIN'connection
$orders = Order::find()
    ->innerJoinWith('books')
    ->all();

Выше, метод yii db ActiveQuery:: innerJoinWith () – это быстрый способ доступа к yii db ActiveQuery:: joinWith () типа ВНУТРЕННЕГО СОЕДИНЕНИЯ.

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

// Connecting multiple relationships
// Identify orders for books from registered customers within 24 hours
$orders = Order::find()
    ->innerJoinWith([
        'books',
        'customer' => function ($query) {
            $query->where('customer.created_at > ' . (time() - 24 * 3600));
        }
    ])
    ->all();
    
// Connect nested relationships: Connect books tables and their author columns
$orders = Order::find()
    ->joinWith('books.author')
    ->all();

За кодом Yii сначала выполняет инструкцию JOIN SQL, чтобы определить основную модель, которая удовлетворяет условиям запроса инструкций JOIN SQL, затем выполняет инструкцию запроса для каждой связи, а Bing заполняет соответствующие связанные записи.

The difference between yii db ActiveQuery:: join With () and yii db ActiveQuery:: with () is:
The former links the data tables of the main model class and the associated model class to retrieve the main model, while the latter only queries and retrieves the main model class. 

Because of this difference, you can apply query conditions that only work for one JOIN SQL statement. 

For example, the main model can be filtered by the query conditions of the association model. For example, the column of the association table can be used to select the main model data.
When using yii db ActiveQuery:: joinWith () method, you can respond to unambiguous column names.
 
When connecting an association, the association is loaded instantly by default.

You can pass the parameter $eagerLoading to determine whether to use immediate loading in the specified associated query.

The default yii db ActiveQuery:: joinWith () uses a left join to join the associated tables. 

You can also pass the $joinType parameter to customize the connection type. 

You can also use yii db ActiveQuery:: innerJoinWith ().

Ниже приведен краткий пример ВНУТРЕННЕГО СОЕДИНЕНИЯ:

// Find all orders that include books, but the "books" table does not use instant loading
$orders = Order::find()
    ->innerJoinWith('books', false)
    ->all();

// Equivalent to:
$orders = Order::find()
    ->joinWith('books', false, 'INNER JOIN')
    ->all();

Иногда при соединении двух таблиц необходимо указать дополнительные условия в части “ВКЛЮЧЕНО” связанного запроса.

Это может быть достигнуто путем вызова метода yii db ActiveQuery:: при условии ():

class User extends ActiveRecord{ 
    public function getBooks() { 
        return $this->hasMany(Item::className(), ['owner_id' => 'id'])
            ->onCondition(['category_id' => 1]); 
    } 
}
Above, the yii db ActiveRecord:: hasMany () method returns a yii db ActiveQuery object. When you execute a query with yii db ActiveQuery:: joinWith (), it depends on which yii db ActiveQuery:: onCondition () is being invoked, and returns items with category_id of 1.

При запросе с помощью yii db ActiveQuery:: joinWith () условие “при условии” помещается в часть “ВКЛЮЧЕНО” соответствующего оператора запроса, например:

// SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1 
// SELECT * FROM item WHERE owner_id IN (...) AND category_id=1 

$users = User::find()->joinWith('books')->all();

Примечание: Если жадная загрузка или отложенная загрузка выполняется с помощью yiidbActiveQuery:: с (), условие on помещается в раздел WHERE соответствующего оператора SQL. Потому что здесь нет запроса на ПРИСОЕДИНЕНИЕ. Например:

// SELECT * FROM user WHERE id=10
$user = User::findOne(10);

// SELECT * FROM item WHERE owner_id=10 AND category_id=1
$books = $user->books;

Работа с таблицей ассоциаций

AR предоставляет следующие два метода для установления и устранения связи между двумя связанными объектами:

  • активная запись базы данных yii::ссылка()

  • активная запись базы данных yii::разорвать связь()

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

$customer = Customer::findOne(1); $order = new Order(); $order->subtotal = 100; $customer->link('orders', $order);

YiidbActiveRecord:: link () вызывает описанное выше, чтобы установить порядок customer_id в качестве значения первичного ключа $customer, а затем вызывает yiidbActiveRecord:: save (), чтобы сохранить заказ в базе данных.

Масштаб

Когда вы вызываете метод yii db ActiveRecord:: find () или yii db ActiveRecord:: findBySql (), вы возвращаете экземпляр yiidbActiveQuery.

Позже вы можете вызвать другие методы запроса, такие как yii db ActiveQuery:: где (), yiidbActiveQuery:: OrderBy (), чтобы указать дополнительные условия запроса.

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

Область действия-это, по сути, метод, который требует набора методов запроса для изменения определения в пользовательском классе запроса объекта запроса.

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

Во-первых, создайте пользовательский класс запросов для вашей модели, который определяет необходимые методы области.

Например, создайте класс запроса комментариев для модели комментариев, а затем определите метод active () в качестве области в классе запроса комментариев, как показано в следующем коде:

namespace app\models; use yii\db\ActiveQuery; 

class CommentQuery extends ActiveQuery{ 
    public function active($state = true) { 
        $this->andWhere(['active' => $state]); 
        return $this; 
    } 
}

Ключ:

  1. Классы должны наследовать yiidbActiveQuery (или другие Активные запросы, такие как yii mongodb ActiveQuery).

  2. Это должен быть метод открытого типа, и он должен возвращать $this для операции цепочки. Вы можете передать параметры.

  3. Проверка yiidbActiveQuery-очень полезный способ изменения условий запроса.

Во-вторых, переопределите метод yiidbActiveRecord:: find (), чтобы вернуть пользовательский объект запроса вместо обычного yiidbActiveQuery.

Для приведенного выше примера вам нужно написать следующий код:

namespace app\models; 

use yii\db\ActiveRecord; 

class Comment extends ActiveRecord{ 
    /** * @inheritdoc * @return CommentQuery */ 
    public static function find() { 
        return new CommentQuery(get_called_class()); 
    } 
}

Итак, теперь вы можете использовать пользовательский метод определения области:

$comments = Comment::find()
    ->active()
    ->all(); 

$inactiveComments = Comment::find()
    ->active(false)
    ->all();

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

class Post extends \yii\db\ActiveRecord{ 
    public function getActiveComments() { 
        return $this->hasMany(Comment::className(), ['post_id' => 'id'])
            ->active(); 
    } 
}

Или используйте его при выполнении запросов на ассоциацию (что такое “на лету”?):

$posts = Post::find()
    ->with([ 'comments' => function($q) { $q->active(); } ])
    ->all();

Область по умолчанию

Если вы уже использовали Yii 1.1 раньше, вы должны знать концепцию области действия по умолчанию.

Область по умолчанию действует для всех запросов.

Вы можете легко определить область по умолчанию, переписав yiidbActiveRecord:: find (), например:

public static function find(){ 
    return parent::find()
        ->where(['deleted' => false]); 
}
Note that all queries after you can't use yii db ActiveQuery:: where (), but they can use yii db ActiveQuery:: and where () and yii db ActiveQuery:: or Where (), and they won't override the default scope.

If you want to use default scope, you can't use where () method after xxx:: find (), you must use andXXX () or orXXX () system method, otherwise default scope will not work, as for the reason, open the code of where () method.

Транзакционные операции

При выполнении нескольких связанных операций с базой данных

TODO: FIXME: WIP, TBD, https://github.com/yiisoft/yii2/issues/226
, yii\db\ActiveRecord::afterSave(), yii\db\ActiveRecord::beforeDelete() and/or yii\db\ActiveRecord::afterDelete()

Думаю ли я, что было бы лучше перевести это предложение в “шаблонный метод”? )

Разработчики могут переопределить метод yiidbActiveRecord:: save (), а затем использовать транзакционные операции в своих контроллерах, что, строго говоря, не кажется хорошей практикой (вспоминая основные правила “тонкой модели контроллера/жира”).

Эти методы здесь (если вы не знаете, что вы на самом деле делаете, не используйте их), Модели:

class Feature extends \yii\db\ActiveRecord{ 
    // ... 
    public function getProduct() { 
        return $this->hasOne(Product::className(), ['id' => 'product_id']); 
    } 
} 

class Product extends \yii\db\ActiveRecord{ 
    // ... 
    public function getFeatures() { 
        return $this->hasMany(Feature::className(), ['product_id' => 'id']); 
    } 
}

Перепишите метод yiidbActiveRecord:: сохранить ():

class ProductController extends \yii\web\Controller{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

Используйте транзакции на уровне контроллера:

class ProductController extends \yii\web\Controller{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

В качестве альтернативы этим хрупким методам следует использовать функции схемы атомарных операций.

class Feature extends \yii\db\ActiveRecord{
    // ...

    public function getProduct()
    {
        return $this->hasOne(Product::className(), ['product_id' => 'id']);
    }

    public function scenarios()
    {
        return [
            'userCreates' => [
                'attributes' => ['name', 'value'],
                'atomic' => [self::OP_INSERT],
            ],
        ];
    }
}

class Product extends \yii\db\ActiveRecord{
    // ...

    public function getFeatures()
    {
        return $this->hasMany(Feature::className(), ['id' => 'product_id']);
    }

    public function scenarios()
    {
        return [
            'userCreates' => [
                'attributes' => ['title', 'price'],
                'atomic' => [self::OP_INSERT],
            ],
        ];
    }

    public function afterValidate()
    {
        parent::afterValidate();
        // FIXME: TODO: WIP, TBD
    }

    public function afterSave($insert)
    {
        parent::afterSave($insert);
        if ($this->getScenario() === 'userCreates') {
            // FIXME: TODO: WIP, TBD
        }
    }
}

Код в контроллере будет очень кратким:

class ProductController extends \yii\web\Controller{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

Контроллер очень прост:

class ProductController extends \yii\web\Controller{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

Загрязненные атрибуты

При вызове yii db ActiveRecord:: save () для сохранения экземпляра ActiveRecord сохраняются только загрязненные атрибуты.

Считается ли атрибут загрязненным, зависит от того, было ли изменено его значение с момента последней загрузки из базы данных или последнего сохранения в базе данных. Примечание: Проверка данных всегда выполняется независимо от того, имеет ли Активная запись загрязненные атрибуты.

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

Вы можете получить текущий атрибут загрязнения, вызвав yii db ActiveRecord:: getDirtyAttributes ().

Вы также можете вызвать yii db ActiveRecord:: пометить атрибут Dirty (), чтобы отобразить загрязненный тег.

Если вас интересуют значения атрибутов до последнего изменения, вы можете вызвать yii db ActiveRecord:: getOldAttributes () или yiidbActiveRecord:: getOldAttribute ().

Оригинал: “https://developpaper.com/yiis-way-of-practice-active-record-activity-record/”