К сожалению редактор этой Wiki не позволяет создавать сложно форматированный текст, из-за чего в некоторых местах статьи трудно читаются. Некоторые участки кода не подсвечены т.к. при их записи я не указывал теги <?php и ?>". Это связано с тем что такой код помещается внутри файла/класса/функции.
Для просмотра статьи в оригинальном виде рекомендую Вам скачать её PDF-вариант: practical_intro_cake.pdf (16 страниц, шрифт Times New Roman, 12pt).
Автор: Kuzya
Сайт: http://kuzya.name
Оригинал статьи: Начальный практикум в CakePHP
Официальный сайт фреймворка: http://cakephp.org/
Русскоязычный сайт фреймворка: /
Версия фреймворка на момент написания статьи: 1.2
Здравствуйте. В этой статье я хочу описать базовые практические методы работы с фреймворком CakePHP (далее CP). Почему я пишу именно «практические»? Потому что в этой статье не будет описания основ CP, теории его дизайна, MVC и всего подобного. Здесь будет лишь практика. Во многих науках есть теоретическая и практическая части. Программирование не исключение. Теорию Вы можете почерпнуть из документации, а практика нарабатывается либо самостоятельно, либо берётся из различных статей, в том числи и из этой. Второй вариант естественно легче, а в связке с документацией является отличным стартом для начинающих или познающих инструмент «с нуля». Остановимся на документации подробнее. Так как у данного фреймворка есть представительство
в нашей стране, то есть и русскоязычный перевод документации. Его Вы можете найти в Wiki сайта (путеводитель по ней есть на главной странице). Описание версии 1.2 ещё не до конца переведено и находится на сайте в неполном виде. Зато документация к версии 1.1 переведена полностью и может служить хорошим подспорьем разработчикам для которых языковой барьер является существенным препятствием на пути изучения чего-то нового. Конечно же в текущей версии языка имеются отличия от предыдущей, но базовые основы и большая часть инструментария осталась та же. Поэтому в каких-либо трудных ситуациях Вы можете обращаться к описанию версии 1.1. Официальная документация находится по адресу http://book.cakephp.org/. В ней дано полное и, на мой взгляд, понятное описание всего что есть в CP. Иногда Вам может понадобится посмотреть описание метода или свойства определённого класса. В этом случае стоит обращаться по адресу http://api.cakephp.org/. Этот сайт содержит данные по всему что есть внутри фреймворка. Очень удобно сделаны несколько вариантов просмотра — по таблице классов и по списку файлов. Вы в любом случае найдёте то что Вас интересует. Для хорошего понимания материала читателю достаточно ознакомится с документацией в общих чертах. Желательно создать тестовое приложение. Можно самостоятельно, можно взять описание тестового блога. Главное, иметь хоть какое-то представление о работе фреймворка и структуре его документации (как на русскоязычном сайте, так и на официальном). Внимание! Обязательно ознакомьтесь с соглашениями Cake PHP? (/wiki/Manual/BasicPrinciples/Conventions) и постарайтесь всегда держать их рядом. По началу у меня было много проблем именно из-за соглашений, поэтому что бы не мучаться почаще к ним обращайтесь. Целью нашей практики будет написание слабой имитации интернет-магазина. В этом магазине можно будет просматривать категории товаров, списки их содержимого, сами товары. Отдельно с товарами можно будет производить следующие операции — заказывать, голосовать за них и оставлять о них отзывы. Приступим.
Начнём с предварительной подготовки. Первым шагом Вам нужно скопировать файлы CP на «чистый» хост. У меня он называется «cakephp».Если хост у Вас назван по другому то будьте внимательны к ссылкам из статьи во избежание каких-либо проблем.
После копирования откройте файл «app/config/core.php» и измените значение настройки «Security.salt» на любое другое что бы фреймворк перестал показывать предупреждения безопасности (информацию по работе с настройками Вы можете получить здесь – http://cakephp. ru/wiki/Manual/Developing/Configuration/Core). Затем в файл настроек БД (app/config/database.php), в массив «default», внесите данные для подключения к базе. Так же добавьте в него ячейку «encoding»,
содержащую текст «utf8». Этим мы определим кодировку работы с БД. Теперь нужно заняться непосредственно данными. В прикреплённых файлах имеется дамп базы магазина — «database.sql». Импортируйте его в Вашу БД. При импорте, во избежание проблем с русскоязычным текстом, следует учитывать что содержимое файла имеет кодировку utf8. Структура получившейся базы очень проста. В ней всего 3 таблицы:
1. categories – содержит категории продуктов. Их у нас 3 — книги, музыка, электроника. Поля в этой таблице следующие: id – порядковый номер категории, cat_name – имя категории, cat_t_name – имя категории в транслите (для ЧПУ), about – описание категории.
2. comments – таблица с комментариями. Поля: id – номер комментария, product_id – номер продукта к которому оставлен комментарий, author – автор комментария и answer – его содержимое.
3. products – таблица содержащая продукты нашего псевдо-магазина. Поля в ней следующие: id – порядковый номер товара, category_id – номер категории к которой принадлежит товар, name – имя товара, t_name – транслитеррационное имя товара, about – описание, photo – фотография (они будут храниться в папке «img/products» в веб-директории «app/webroot») товара, rating – его рейтинг, cost – цена (единицы цен будут обсуждаться ниже).
Возьмёмся за внешний вид. Всё что нужно для этого находится в прикреплённом архиве «templates.zip». Директории «img» и «css» скопируйте прямо в «app/webroot», предварительно удалив такие-же старые директории. Далее пройдите в папку «./cake/libs/view/layouts/» и поместите туда шаблон default_layout.ctp», попутно переименовав его в «default.ctp». Это наш основной дизайн. Обратитесь к корню сайта и Вы его увидите. Обратите внимание на отладочную информацию внизу экрана. Там отображаются запросы к базе данных, количество затронутых ими рядов и т.д. Нам эта информация не понадобится и мы её отключим. Для этого снова откройте файл «./app/config/core.php» и установите параметр «debug» в 0. Обновите страницу. Информация о запросах пропала. Теперь можно перейти непосредственно к программированию.
Сейчас мы займёмся непосредственно кодом. Если в отношении него у Вас возникнут какие-то вопросы то обращайтесь к документации. Всё что описано ниже делалось именно по ней, хотя многочисленные ссылки на неё должны не дать Вам запутаться. В случае если у Вас что-то не будет получаться то попробуйте вернуться к началу раздела и перечитать его заново.
Самое первое что должен делать наш магазин — показывать список категорий товаров. Для этого мы создадим контроллер categories и соответствующую модель. В папке контроллеров(app/controllers) создайте файл «categorie_controler.php». В классе нового контроллера объявите функцию «index», которая и будет выводить нужный нам список.
<?php
class CategorieController extends AppController
{
function index()
{
}
}
?>
<?php
class Categorie extends AppModel
{
}
?>
function getCatsList()
{
$cats = Array();
$result = $this->findAll();
foreach($result as $line)
$cats[] = $line['Categorie'];
return $cats;
}
function index()
{
$categories = $this->Categorie->getCatsList();
$this->set('categories',$categories);
}
<?foreach($categories as $categorie):?>
<h2><?=$categorie['cat_name']?></h2>
<div class="entry">
<?=$categorie['about']?> <a href='/categorie/view/<?=$categorie['cat_t_name']? >/>Подробнее >></a>
<p></p>
<?endforeach;?>
Router::connect('/', array('controller' => 'categorie', 'action' => 'index'));
function getCatIdByTName($cat_t_name)
{
$result = $this->find("cat_t_name='$cat_t_name'",'id');
return $result['Categorie']['id'];
}
<?php
class Product extends AppModel
{
}
?>
var $hasMany = array('Product' =>
array('className' => 'Product',
'conditions' => '',
'order' => '',
'limit' => '',
'foreignKey' => 'category_id',
'finderQuery' => ''
)
);
array(2) {
["Categorie"]=> // Имя модели
array(4) {
// Данные запрошенной категории
}
["Product"]=> // Имя модели
array(5) {
[0]=>
array(8) {
Данные продукта 1
}
[1]=>
array(8) {
Данные продукта 2
}
[2]=>
array(8) {
Данные продукта 3
}
[3]=>
array(8) {
Данные продукта 4
}
}
}
function view($cat_t_name)
{
$categorie_id = $this->Categorie->getCatIdByTName($cat_t_name);
$categorie = $this->Categorie->find("id='$categorie_id'");
$this->set('categorie',$categorie);
}
<h2>Категория <<?=$categorie['Categorie']['cat_name'];?>></h2>
<p class="posted"><?=$categorie['Categorie']['about'];?></p>
<div class="entry">
<?foreach($categorie['Product'] as $product):?>
<p><a href='/product/view/<?=$product['t_name'];?>/'><?=$product['name'];?></a></p>
<blockquote>
<p>
<?=$product['about'];?>
<br /><br />
</p>
</blockquote>
<br /><br /><br /><br />
<?endforeach;?>
</div>
class ImageHelper extends AppHelper
{
function resize($image_path,$w,$h='auto')
{
$image_name = basename($image_path);
$cached_image_name = "./img/cache/{$image_name}_{$w}_{$h}.jpg";
if(!file_exists($cached_image_name))
{
$src = imagecreatefromjpeg($image_path);
list($width,$height)=getimagesize($image_path);
if ($width < $w)
{
imagejpeg($image,$cached_image_name);
} else {
$a = $width/$w;
if($h == 'auto') $h = ceil($height/$a);
$image = imagecreatetruecolor($w,$h);
imagecopyresampled($image,$src,0,0,0,0,$w,$h,$width,$height);
imagejpeg($image,$cached_image_name);
}
imagedestroy($image);
}
$image_name = basename($cached_image_name);
return $this->output('/img/cache/'.$image_name);
}
}
var $helpers = Array('Image');
<img src='<?=$image->resize('./img/products/'.$product['photo'],60);?>' hspace='5' vspace='5' align='left'/>
Займёмся продуктами. Из отображения списка товаров видно что за их просмотр отвечает метод «view» контроллера «products». Создадим его. В нужном нам методе мы просто будем через модель (которая уже создана) получать и передавать в отображение данные товара. Из-за того что в ссылке передаётся имя продукта в транслите нам нужно описать в модели функцию получения порядкового номера по этому имени.
function getProductIdByTName($t_name)
{
$result = $this->find("t_name='$t_name'","id");
return $result['Product']['id'];
}
function view()
{
$product_id = $this->Product->getProductIdByTName($t_name);
$product = $this->Product->find("id='$product_id'");
$this->set('product',$product['Product']);
}
<h2><?=$product['name'];?></h2>
<div class="entry">
<p>
<img src='/img/products/<?=$product['photo'];?>' width='70px' hspace='5' vspace='5' align='left' />
<?=$product['about'];?>
<br />Цена: <?=$product['cost'];?>
<br /><br />
<br />Рейтинг:<?=$product['rating'];?>
</p>
<br />
</div>
Проверьте правильно ли отображается информация о выбранном товаре. Сейчас немного приукрасим выводимую информацию с помощью хэлпера Number (http://book.cakephp.org/view/215/Number). У него есть функция currency с помощью которой можно отображать числа в денежном формате — в долларах, евро и марках (если я не ошибаюсь). В качестве аргументов ей нужно передать число и формат выводимых денежных единиц. Мы выведем цену продукта в евро. Для этого в контроллер, в список используемых хэлперов, нужно добавить хэлпер Number. А в отображении строку
<br />Цена: <?=$product['cost'];?>
заменить на вызов метода «currency»
<br />Цена: <?=$number->currency($product['cost'],'EUR');?>
Обновите страницу и Вы увидите что цена отображается в новом формате.
Сейчас мы дополним просмотр продукта и чуть пониже информации о нём будем отображать товар который был просмотрен до этого. Здесь нам поможет компонент для работы с сессиями Session (http://book.cakephp.org/view/173/Sessions). Для начала добавим его в контроллер
var $components = Array('Session');
Теперь в конец метода view впишем запоминание текущего товара (ведь при просмотре следующего он будет последним просмотренным) и, если в сессии уже записан такой номер (пользователь смотрел что-то до этого), получение данных о прошлом продукте и передаче их в отображение. Для этого мы добавим в модель Product функцию получения данных о товаре по его порядковому номеру. Она очень проста.
function getProductData($product_id)
{
$product_id = (int) $product_id;
$result = $this->find("id={$product_id}");
return $result['Product'];
}
// Читаем содержимое ячейки Product.last. В ней у нас будет храниться этот номер.
$last_product_id = $this->Session->read('Product.last');
// Если в ней есть номер продукта и он не совпадает с текущим
// даём отображению эти данные
if($last_product_id && $last_product_id != $product_id)
{
$last_product_data = $this->Product->getProductData($last_product_id);
$this->set('last_product',$last_product_data);
}
// Записываем номер текущего продукта как последний просмотренный
$this->Session->write('Product.last',$product_id);
<?if(isset($last_product)):?>
До этого Вы смотрели: <a href='/product/view/<?=$last_product['t_name'];?>/'><?=$last_product['name'];?></a>
<?endif;?>
<form action='/product/change_rating/<?=$product['id'];?>/' method='POST'>
Ваша оценка товару: <input type='radio' value='1'> 1
<input type='radio' name='data[estimation]' value='2'> 2
<input type='radio' name='data[estimation]' value='3'> 3
<input type='radio' name='data[estimation]' value='4'> 4
<input type='radio' name='data[estimation]' value='5'> 5
<input type='submit' value='Оценить'>
</form>
function rating($product_id,$estimation)
{
// Указыаем id продукта для формирования условия WHERE при обновлении
$this->id = $product_id;
// Получаем текущий рейтинг продукта и к нему прибавляем оценку
$rating = $this->find("id='$product_id'","rating");
$rating = $rating['Product']['rating'];
$rating += $estimation;
// Формируем массив данных для обновления с одним полем - "rating"
$data = Array('rating'=>$rating);
// Сохраняем изменённый рейтинг у товара, номер которого указан в $this->id
$this->save($data);
}
function change_rating($product_id)
{
// Изменяем рейтинг
$this->Product->rating((int)$product_id,(int)$this->data['estimation']);
// Получаем данные текущего продукта (нам нужно его имя в транслите для редиректа)
$product = $this->Product->find("id={$product_id}","t_name");
// Показываем окно редиректа с сообщением о том что оценка сохранена
$this->flash('Estimation saved','/product/view/'.$product['Product']['t_name']);
}
Думаю этот код не вызовет вопросов. Перед проверкой его работоспособности нам нужно заменить стандартный шаблон flash-страниц (http://book.cakephp.org/view/426/flash). Возьмите его а архиве шаблонов, приложенном к статье, (он называется «flash_layout.ctp») и скопируйте в папку «./cake/libs/view/layouts/», переименовав во «flash.ctp». Теперь можно поставить любому товару оценку и посмотреть как всё сработает.
Следующей частью функционала, которой мы займёмся, будет формирование заказа. На страничке каждого товара будет выводиться специальная форма с именем и адресом заказчика. После её заполнения данные будут уходить на e-mail менеджеру и заказ будет считаться оформленным. В отображении код этой формы будет располагаться самым последним.
<br />
<br />
<h3>Хотите заказать?</h3>
<form action="/product/order/<?=$product['id'];?>/" method="post" id="commentform">
<p><label for="author">Ваше имя:</label>
<input type="text" name="data[customer]" id="author" value="" size="22" tabindex="1" /></p>
<label for="answer">Ваш адрес:</label>
<p valign='top'><textarea name="data[address]" id="answer" cols="40" rows="3" tabindex="4"></textarea></p>
<p><input name="submit" type="submit" class="submit" tabindex="5" value="Заказать" />
</p>
</form>
Далее мы опишем метод «order». Так как оформленный заказ уходит на E-mail то нам понадобится одноимённый компонент для работы с электронными письмами (http://book.cakephp.org/view/176/Email). Добавьте его имя в массив загружаемых компонентов.
var $components = Array('Email','Session');
Для отправки письма мы установим 3 свойства, название которых говорит само за себя — «to», «from» и «subject». И с помощью метода «send» отправим письмо менеджеру.
function order($product_id)
{
// Получаем данные о заказываемом продукте
$product_data = $this->Product->getProductData($product_id);
// От кого идёт письмо
$this->Email->from = 'Site <noreply@our.shop>';
// Кому идёт письмо
$this->Email->to = 'Manager <manager@our.shop>';
// Тема письма
$this->Email->subject = 'New order';
// Формируем текст письма
$text = "Hello manager. We have new order. Recipient is {$this->data['customer']}. His address - {$this->data['address']}. Customer need product `{$product_data['name']}`";
// и отправляем
$this->Email->send($text);
}
Для проверки работоспособности данного кода я использовал заглушку для sendmail, которая имеется в комплекте Denwer 3. Она не отправляет письма, а складывает их в папку «tmp». Попробуйте отправить заказ и с сайта должно уйти письмо типа «Hello manager. We have new order. Recipient is Kuzya. His address – Russia, my town. Customer need product `Независимая Украина. Крах проекта`». Т.к. у нас нет шаблона для действия «order», CP может показать Вам ошибку отсутствия страницы – «Not Found. Error: The requested address '/product/order/11' was not found on this server.» Это не означает того что данной страницы, контроллера или действия нет на самом деле. Если Вы в настройках вернёте параметр «debug» в значение 2 то увидите что за место этой ошибки покажется сообщение об отсутствии отображения для действия «order». Мы бы могли создать отображение с надписью типа «Ваш заказ принят», или с помощью flash-метода возвращать пользователя обратно, но мы поступим немного по другому.
var $helpers = Array('Number','Ajax','Javascript');
<?=$javascript->link('prototype');?>
<?=$javascript->link('scriptaculous');?>
<?=$ajax->submit('Отправить',Array('url'=>"/product/order/{$product['id']}/",'after'=>'alert("Your order is sended!")'));?>
Попробуйте теперь обновить страницу и оформить заказ. Браузер должен показать сообщение «Your order is sended», а с сайта должно отправиться письмо с заказом.
Сейчас мы добавим ко всему сделанному ещё и возможность оставлять отзывы о товаре. Для этого мы будем использовать контроллер comment и модель с таким же именем. Пока что создайте их совершенно пустыми классами. Вернёмся к шаблону просмотра товара и в самый низ добавим форму отправки сообщений.
<h3>Оставьте отзыв о товаре</h3>
<form action="/comment/send/<?=$product['id'];?>/<?=$product['t_name'];?>/" method="post" id="commentform">
<p><input type="text" name="data[author]" id="author" value="" size="22" tabindex="1" />
<label for="author">Ваше имя</label></p>
<p><textarea name="data[answer]" id="answer" cols="40" rows="3" tabindex="4"></textarea></p>
<p><input name="submit" type="submit" class="submit" tabindex="5" value="Опубликовать" /></p>
</form>
function send($product_id,$product_t_name)
{
// Устанавливаем id будущей записи в 0 т.к. в таблице комментариев у нас
// автоматическое увеличение счётчика (auto increment)
$this->data['id'] = 0;
// Устанавливаем номер продукта
$this->data['product_id'] = $product_id;
// Сохраняем данные
$this->Comment->save($this->data);
// Показываем сообщение о успешном сохранении комментария
// и отправляем пользователя обратно
$this->flash('Comments saved!','/product/view/' . $product_t_name);
}
var $validate = Array(
'author'=>Array(
'rule'=>'alphaNumeric',
'minLength'=>3,
)
);
function send($product_id,$product_t_name)
{
// Устанавливаем id будущей записи в 0 т.к. в таблице комментариев у нас
// автоматическое увеличение счётчика (auto increment)
$this->data['id'] = 0;
// Устанавливаем номер продукта
$this->data['product_id'] = $product_id;
// Передаём данные модели
$this->Comment->set($this->data);
// Проверяем их
if($this->Comment->validates())
{
// Если данные прошли проверку то проводим сохранение
$this->Comment->save($this->data);
// И показываем сообщение об успешном сохранении комментария
// отправляя пользователя обратно
$this->flash('Comments saved!','/product/view/' . $product_t_name);
} else {
// Если проверка не пройдена то сообщаем об этом пользователю
// и отправляем его обратно
$this->flash('Input error!','/product/view/' . $product_t_name);
}
}
Примемся за «Sanitize». Подключается он к приложению методом «import», класса «App» (http://book.cakephp.org/view/499/The-App-Class).
App::import('Sanitize');
Далее к обрабатываемым данным может быть применено несколько функций. Но мы воспользуемся лишь одной — вырезанием спец-символов HTML. Для этого сразу после установки поля «product_id» добавим следующие строки.
// Чистим HTML-спецсимволы в тексте ответа
$this->data['answer'] = Sanitize::html($this->data['answer']);
Вот и всё. Управлять данным классом крайне просто. Стоит лишь заметить что функция «paranoid», этого класса, русские буквы удаляет начисто. Поэтому не стоит ею пользоваться при создании русскоязычных сайтов.
var $uses = Array('Product','Comment');
Теперь мы можем обращаться к модели комментариев так же как и к модели продуктов. Для получения списка отзывов мы можем воспользоваться уже знакомой нам функцией «find», а можем пойти по иному пути. Мы задействуем «магическую» функцию «Find All By?». Её «волшебство» заключается в том что после её название Вы можете указать имя поля по которому требуется извлечь данные. То есть если мы берём комментарии из таблицы по номеру продукта (поле product_id), то можем получить их функцией «Find All By Product_id?».
$comments = $this->Comment->findAllByproduct_id($product_id);
Настолько всё просто. Далее нам следует передать полученный массив отзывов в отображение
$this->set('comments',$comments);
и в сам шаблон добавить код показа сообщений.
<?foreach($comments as $comment):?>
<!-- ### Post Entry Begin ### -->
<div class="post">
<h2>Answer from <?=$comment['Comment']['author'];?></h2>
<div class="entry">
<p><?=$comment['Comment']['answer'];?></p>
</div>
</div>
<!-- ### Post Entry End ### -->
<?endforeach;?>
Поместите его в самый конец. Вы можете проверить работу комментариев посмотрев отзывы о продукте «Canon HG20». О нём заранее написано 5 отзывов.
var $paginate = array(
'limit' => 4,
'order' => array('id' => 'desc')
);
Теперь мы можем использовать метод «paginate» этого хэлпера для получения списка комментариев текущей страницы (её номер будет передаваться в ссылке, об этом чуть ниже). Вызовом этого метода нужно заменить вызов функции «Find All By?*».
$comments = $this->paginate('Comment');
Просмотрев теперь страницу с отзывами Вы обнаружите что на ней сообщений осталось ровно столько сколько нам было нужно. Осталось немного — отобразить ссылки для навигации по страницам. Это можно сделать с помощью методов «prev», «next» и «counter» (их описание Вы можете найти по следующей ссылке- http://api.cakephp.org/class/paginator-helper). Последний просто отображает какая страница из скольких просматривается (1 из 3, 4-ая из 5, и т.д.). А вот первым двум нужно передать как минимум 2 параметра — название ссылки на следующую/предыдущую страницу и массив настроек. Как названия мы передадим «<< Previous» и «Next>>» для переключения на предыдущую и следующую страницы соответственно. А из настроек передадим лишь один параметр – «url». Он будет содержать транслитеррационное имя продукта для того что бы при переходе между страниц формировался правильный адрес. Засовываем эти 3 метода в простенькую таблицу и получаем следующий код.
<table>
<tr>
<td>
<?=$paginator->prev('<< Previous ', Array('url'=>Array($product['t_name'])));?>
</td>
<td>
<?=$paginator->counter();?>
</td>
<td>
<?=$paginator->next(' Next >>', Array('url'=>Array($product['t_name'])));?>
</td>
</tr>
</table>
Обновите страницу и Вы увидите что в самом её низу появились переключатели между отзывами.
К счастью разработчиков CP представляет несколько механизмов управления кэшированием. На мой взгляд они очень удобны. С их помощью мы сейчас настроем кэширование страниц с описаниями продуктов и отзывами о них. Стандартно, предлагается несколько вариантов хранения кэшированных данных, но мы будем использовать файловый, он стоит по умолчанию. За все нужные нам операции отвечает хэлпер «Cache» (http://book.cakephp.org/view/213/Cache). Подключив его, настроим кэширование только результата действия view (http://book.cakephp.org/view/346/Caching-in-the-Controller). Для этого обозначим массив «cacheAction» как свойство контроллера и поместим туда ячейку «view/» со значением «86400».
var $cacheAction = array(
'view/' => 86400
);
Тем самым мы указываем CP что все результаты действия view нужно кэшировать на 86400 секунд, что эквивалентно 24 часам. Теперь откройте конфигурацию ядра («./app/config/core.php») и раскомментируйте установку опции «Cache.disable?», заменив её значение на «false». И тоже самое сделайте с объявлением настройки «Cache.check?». Сохраните изменения и посмотрите страницу любого товара. Затем откройте директорию «./app/tmp/cache/views», в которой хранится кэш отображений. Если Вы всё сделали правильно то там будет находится файл с именем типа «product_view_canon_hg20_black.php» в котором будет содержаться кэш просмотренной Вами страницы. Осталось научиться очищать кэш конкретных страниц, ведь их контент изменяется когда мы оставляем отзыв о товаре. Механизм кэширования CP уникален тем что кэш автоматически удаляется если через модель, работающую с кэшированным действием, внести какие-то изменения в базу (http://book.cakephp.org/ar/view/348/Clearing-the-Cache). Мы бы могли этим воспользоваться если бы комментарии вносились в базу через модель «Product», но как Вы помните отзывы в базу вносятся через «Comment». Следовательно, нужно обнулять кэш вручную. Для таких действий в CP используется глобальная функция «clearCache». Ей в качестве параметра нужно передать имя закэшированной странице в формате «контролллер_действие_параметр1_параметр2_параметрN». У нас это будет «product_view_имя_продукта_в_транслите». В контроллере «Comment», сразу после вызова метода «save» пишем следующее:
clearCache('product_view_'.$product_t_name);
На этом настройка кэширования завершена. Попробуйте подобавлять отзывы и проверить изменился ли внешний вид страницы.
И последнее что будет рассмотренно в этой статье — логирование данных о работе приложения (http://book.cakephp.org/ar/view/157/Logging). Подобный функционал может очень пригодиться для отслеживания ошибок в крупных приложениях, когда сходу не сообразишь где и какой произошла сбой. Никаких компонентов или хэлперов подключать здесь не нужно. Единственная функция, нужная для логирования, доступна всегда. Это функция «log». Ей нужно передавать 2 параметра. Первый — сообщения. Им может быть информация об ошибке, сбое, действии пользователя и т.п. А второй — тип логируемой информации. В качестве этого параметра может быть передана одна из двух констант — «LOG_ERROR» и «LOG_DEBUG» – ошибочная или отладочная информация соответственно. Рассмотрим пример с ошибкой. В методе «view» контроллера «Product» может быть ошибочно указан товар в ссылке. В связи с этим метод «getProductIdByTName» вернёт ложное значение. Давайте добавим после вызова этого метода строку
if(!$product_id) $this->log('Request to non-existing product', 'products');
и пройдём по ссылке http://cakephp/product/view/11111111, вызвав тем самым несуществующий товар. Сообщение будет записано в файл «error.log». Он находится там же где и все логи — в директории «./app/tmp/logs». Открыв его Вы обнаружите что самая последняя надпись содержит информацию типа этой – «2009–03–16 20:07:33 Error: Request to non-existing product». А можно вообще выделить в отдельную группу например только лог-записи связанные с товарами. Для этого в качестве константы можно передать строку, скажем, «products». В таком случае автоматически создастся файл «products.log» и в него будет записана вся передаваемая информация.
Вот и всё. Код нашего магазина готов и нормально функционирует. Если у Вас что-то не получилось то можете взять исходный код всего приложения в прикреплённом архиве «ready.zip».
На мой взгляд фреймворк хороший, но очень сильно запутанный. Иногда использование некоторых функций не укладывается в голове — по логике они должны использоваться, например, в контроллере, а используются в модели. Что-то должно быть компонентом, а является хэлпером. Не понравились и инструменты для работы с БД. Я больше предпочитаю интерфейсы подобные паттерну ActiveRecord. Ну и худой функционал — мало хэлперов и компонентов. И всё-таки, как говориться, на вкус и цвет... Надеюсь, что он понравится читателям. Удачи Вам в Ваших проектах!