Кешування даних
===============

Кешування даних — це зберігання деякої змінної PHP в кеші та її отримання звідти. Для цієї мети базовий клас [CCache] компонента кешу має два найбільш використовуваних методи: [set()|CCache::set] та [get()|CCache::get].

Для кешування змінної `$value` ми вибираємо унікальний ідентифікатор (ID) і викликаємо метод [set()|CCache::set] для її збереження в кеші:

~~~
[php]
Yii::app()->cache->set($id, $value);
~~~

Дані будуть залишатися в кеші до тих пір, поки не будуть видалені із-за деяких умов функціонування кеша (наприклад, не залишилося місця для кешування, тоді більш старі дані видаляються). Для зміни такої поведінки ми можемо встановити значення терміну дії кешу при виклику методу [set()|CCache::set]. Тоді дані будуть видалені з кешу після певного періоду часу:

~~~
[php]
// зберігаємо значення змінної в кеші не більше 30 секунд
Yii::app()->cache->set($id, $value, 30);
~~~

Пізніше, коли нам знадобиться доступ до цієї змінної (у цьому ж або іншому запиті), ми викликаємо метод [get()|CCache::get] з ідентифікатором змінної. Якщо отримане значення - false, то змінна не доступна в кеші і ми повинні регенерувати її (оновити в кеші).

~~~
[php]
$value=Yii::app()->cache->get($id);
if($value===false)
{
	// оновлюємо $value, тому що змінна не знайдена у кеші,
	// і зберігаємо в кеш для подальшого використання:
	Yii::app()->cache->set($id,$value);
}
~~~

При виборі ідентифікатора для кешованої змінної, враховуйте, що він повинен бути унікальним для кожної змінної з тих, що можуть бути кешованими у додатку. *НЕ* потрібно, щоб ідентифікатор був унікальним між різними додатками, тому що компонент кешу досить розумний для розрізнення ідентифікаторів різних додатків.

Деякі кеш-сховища, такі як MemCache, APC, підтримують завантаження декількох кешованих значень у пакетному режимі, що може зменшити накладні витрати на отримання даних, збережених в кеші. Метод [mget()|CCache::mget] дозволяє використовувати цю особливість. У випадку, коли кеш-сховище не підтримує цю функцію, [mget()|CCache::mget] буде як і раніше імітувати її.

Для видалення кешованого значення з кешу потрібно викликати метод [delete()|CCache::delete], а для очищення всього кешу - викликати метод [flush()|CCache::flush]. Потрібно бути обережним при виклику метода [flush()|CCache::flush], так як він також видаляє кешовані дані інших додатків.

> Tip|Підказка: клас [CCache] реалізує `ArrayAccess`, 
> тому компонент кешу може використовуватися як масив. Нижче наведено приклади:
> ~~~
> [php]
> $cache=Yii::app()->cache;
> $cache['var1']=$value1;  // еквівалентно $cache->set('var1',$value1);
> $value2=$cache['var2'];  // еквівалентно $value2=$cache->get('var2');
> ~~~

Залежність кешу
---------------

Крім встановлення терміну дії, кешовані дані також можуть стати недійсними відповідно з деякими змінами залежності (dependency). Наприклад, якщо ми кешуємо вміст деякого файлу, і файл змінився, ми повинні прийняти кешовану копію як недійсну і зчитати свіжий вміст із файлу, а не з кешу.

Ми представляємо залежність як екземпляр класу [CCacheDependency] або класів, які його успадковують. Ми передаємо екземпляр залежності разом з кешованими даними, коли викликаємо метод [set()|CCache::set].

~~~
[php]
// значення дійсно не більше 30 секунд
// також, значення може стати недійсним раніше, якщо залежний файл змінено
Yii::app()->cache->set($id, $value, 30, new CFileCacheDependency('FileName'));
~~~

Тепер, якщо ми спробуємо одержати значення `$value` з кешу, викликавши метод [get()|CCache::get], залежність буде перевірена і, якщо вона змінилася, ми отримаємо значення false, що вказує, що дані потребують оновлення.

Нижче наведено перелік доступних залежностей кешу:

   - [CFileCacheDependency]: залежність змінюється, якщо час модифікації файлу змінено;

   - [CDirectoryCacheDependency]: залежність змінюється, якщо будь-який файл в каталозі або в підкаталогах змінений;

   - [CDbCacheDependency]: залежність змінюється, якщо результат запиту деякого певного SQL виразу змінено;

   - [CGlobalStateCacheDependency]: залежність змінюється, якщо значення певного глобального стану змінено. Глобальне стан — це змінна, яка є постійною в багаторазових запитах і сесіях програми. встановлюється методом [CApplication::setGlobalState()];

   - [CChainedCacheDependency]: залежність змінюється, якщо будь-яка залежність ланцюжка змінена;

   - [CExpressionDependency]: залежність змінюється, якщо результат певного виразу PHP змінено.

Кешування запитів
-----------------

У версію 1.1.7 Yii додана підтримка кешування запитів. Побудоване на кешуванні даних, кешування запитів зберігає результат запиту до бази даних в кеші і, тим самим, заощаджує час, що витрачається на одні й ті ж запити.

> Info|Інформація: Деякі СУБД, такі як [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html), підтримують кешування на стороні сервера бази даних. Підтримка кеша в Yii більш гнучка і потенційно більш ефективна.

### Увімкнення кешування запитів

Для того, щоб увімкнути кешування запитів, переконайтеся, що в [CDbConnection::queryCacheID] знаходиться ID підключеного компонента, що реалізує кешування. За замовчуванням це компонент `cache`.

### Використання кешування запитів з DAO

Для того, щоб використовувати кешування запитів необхідно викликати метод [CDbConnection::cache()].

Приклад:

~~~
[php]
$sql = 'SELECT * FROM tbl_post LIMIT 20';
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');
$rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
~~~

При виконанні наведеного коду, Yii спочатку перевірить, чи є в кеші актуальний результат, відповідний SQL-запиту, який ми збираємося виконати. При цьому перевіряється:

- що в кеші є дані із запитом у якості індексу.
- що ці дані не застаріли (минуло менше 1000 секунд з останнього запису в кеш).
- що залежність не змінилася (максимальне значення `update_time` те саме, що було при збереженні результату запиту в кеш).

Якщо всі три умови виконуються, то результат береться з кешу. Інакше виконується SQL запит, його результат записується в кеш і повертається.

### Використання кешування запитів з ActiveRecord

Кешування запитів також можна використовувати з [Active Record](/doc/guide/database.ar). Для цього ми використовуємо метод [CActiveRecord::cache()]:

~~~
[php]
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');
$posts = Post::model()->cache(1000, $dependency)->findAll();
// реляційний запит
$posts = Post::model()->cache(1000, $dependency)->with('author')->findAll();
~~~

Метод `cache()` є коротким записом виклику [CDbConnection::cache()]. При виконанні SQL запиту, згенерованого ActiveRecord, Yii спробує використовувати кешування так само, як це було описано в попередньому підрозділі.

### Кешування кількох запитів

За замовчуванням, кожний раз, коли ми викликаємо метод `cache()` (як [CDbConnection], так і [CActiveRecord]), він кешує наступний за його викликом запит. Всі інші запити НЕ кешуються доки ми не викличемо `cache()` ще раз. Наприклад:

~~~
[php]
$sql = 'SELECT * FROM tbl_post LIMIT 20';
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');

$rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
// кешування запиту НЕ використовується
$rows = Yii::app()->db->createCommand($sql)->queryAll();
~~~

Передаючи методу `cache()` додатковий параметр `$queryCount`, ми можемо закешувати кілька запитів, які виконуються підряд. У наступному прикладі ми кешуємо два запити:

~~~
[php]
// ...
$rows = Yii::app()->db->cache(1000, $dependency, 2)->createCommand($sql)->queryAll();
// буде використовуватися кешування запитів
$rows = Yii::app()->db->createCommand($sql)->queryAll();
~~~

Як відомо, при виконанні реляційного AR-запиту, можуть використовуватися кілька SQL запитів (це можна дізнатися, перевіривши [журнал повідомлень](/doc/guide/topics.logging)). Приміром, якщо відношення між `Post` і `Comment` `HAS_MANY`, то код, наведений нижче, виконає два запити:

- спочатку будуть обрані 20 записів;
- після цього будуть обрані коментарі для цих записів.

~~~
[php]
$posts = Post::model()->with('comments')->findAll(array(
	'limit'=>20,
));
~~~

Якщо використовувати кешування запитів, як показано нижче, закешований буде тільки перший запит до БД:

~~~
[php]
$posts = Post::model()->cache(1000, $dependency)->with('comments')->findAll(array(
	'limit'=>20,
));
~~~

Для того, щоб в кеш потрапили обидва запити, необхідно передати додатковий параметр, що задає кількість кешованих запитів:

~~~
[php]
$posts = Post::model()->cache(1000, $dependency, 2)->with('comments')->findAll(array(
	'limit'=>20,
));
~~~


### Обмеження

Кешування запитів не працює з результатами, які містять посилання на ресурс. Приміром, вона повертається в деяких СУБД при використанні типу `BLOB`.

У деяких сховищах кешу є обмеження на розмір збережених даних. Приміром, в memcache максимальний розмір однієї одиниці даних дорівнює одному мегабайту. Тому, якщо розмір результату запита перевищить дане обмеження, то кешування не спрацює.