Для просмотра статьи в оригинальном виде рекомендую Вам скачать её PDF-вариант: practical_intro_cake.pdf (16 страниц, шрифт Times New Roman, 12pt).
Автор: Kuzya
Сайт: http://kuzya.name
Оригинал статьи: Начальный практикум в CakePHP
Русскоязычный сайт фреймворка: /
Версия фреймворка на момент написания статьи: 1.2
в нашей стране, то есть и русскоязычный перевод документации. Его Вы можете найти в Wiki сайта (путеводитель по ней есть на главной странице). Описание версии 1.2 ещё не до конца переведено и находится на сайте в неполном виде. Зато документация к версии 1.1 переведена полностью и может служить хорошим подспорьем разработчикам для которых языковой барьер является существенным препятствием на пути изучения чего-то нового. Конечно же в текущей версии языка имеются отличия от предыдущей, но базовые основы и большая часть инструментария осталась та же. Поэтому в каких-либо трудных ситуациях Вы можете обращаться к описанию версии 1.1. Официальная документация находится по адресу http://book.cakephp.org/. В ней дано полное и, на мой взгляд, понятное описание всего что есть в CP. Иногда Вам может понадобится посмотреть описание метода или свойства определённого класса. В этом случае стоит обращаться по адресу http://api.cakephp.org/. Этот сайт содержит данные по всему что есть внутри фреймворка. Очень удобно сделаны несколько вариантов просмотра — по таблице классов и по списку файлов. Вы в любом случае найдёте то что Вас интересует. Для хорошего понимания материала читателю достаточно ознакомится с документацией в общих чертах. Желательно создать тестовое приложение. Можно самостоятельно, можно взять описание тестового блога. Главное, иметь хоть какое-то представление о работе фреймворка и структуре его документации (как на русскоязычном сайте, так и на официальном). Внимание! Обязательно ознакомьтесь с соглашениями Cake PHP (/wiki/Manual/BasicPrinciples/Conventions) и постарайтесь всегда держать их рядом. По началу у меня было много проблем именно из-за соглашений, поэтому что бы не мучаться почаще к ним обращайтесь. Целью нашей практики будет написание слабой имитации интернет-магазина. В этом магазине можно будет просматривать категории товаров, списки их содержимого, сами товары. Отдельно с товарами можно будет производить следующие операции — заказывать, голосовать за них и оставлять о них отзывы. Приступим.
содержащую текст «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 – цена (единицы цен будут обсуждаться ниже).
<?phpclass CategorieController extends AppController{ function
index() {
}
}
?>
И создадим пустую модель, сохранив её в директории app/models. Файл в котором она будет храниться назовите «categorie.php».
<?phpclass Categorie extends AppModel{}
?>
С функционалом всё очень просто. В контроллере мы будем получать массив категорий и передавать их отображению. Там они, с помощью цикла foreach, будут отображаться. Для получения данных можно пойти двумя путями — использовать стандартные средства выборки (http://cakephp.ru/wiki/Manual/Developing/Models/Retrieving) или создать и использовать свои. Я решил выбрать второй вариант т.к. он «чище» в плане возвращаемых в контроллер данных. В модель «Categorie»
добавим функцию getCatsList(), которая будет возвращать массив категорий. Вот её код:
function getCatsList(){
$cats = Array();
$result = $this->findAll();
foreach($result as $line)
$cats[] = $line['Categorie'];
return $cats;
}
У такого решения есть 2 плюса – в контроллер поступят уже обработанные данные, тогда как на выходе той же findAll-функции они достаточно «сырые» и требуют дополнительной обработки(что мы и делаем в теле функции), и соответствие концепции MVC по которой все операции с данными должны происходить лишь в модели.
Теперь в контроллер добавим вызов этой функции и передачу результатов её работы в отображение.
function index(){
$categories = $this->Categorie->getCatsList();
$this->set('categories',$categories);
}
Последний шаг — отображение. Создайте в папке видов (app/views) директорию нашего контролёра — categorie. Поместите туда файл index.ctp со следующим содержимым.
<?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;?>
Здесь всё крайне просто – проходим по массиву категорий и каждую отображаем в небольшом html-коде. В итоге, обратившись по ссылке http://cakephp/categorie/, Вы должны увидеть желаемый список.
Сделаем небольшое отступление и установим контроллер «categorie» контроллером по умолчанию. Для этого откройте файл /app/config/routes.php и замените две хранящиеся там настройки следующей строкой:
Router::connect('/', array('controller' => 'categorie', 'action' => 'index'));
Мы указали что при обращении к корню сайта нужно подгружать контроллер «categorie» и вызывать его метод «index». Подробнее о работе с роутами Вы можете почитать тут – /wiki/Manual/Developing/Configuration/Routes.
function getCatIdByTName($cat_t_name){
$result = $this->find("cat_t_name='$cat_t_name'",'id');
return $result['Categorie']['id'];
}
При вызове ей должно передаваться имя категории в транслите, и с помощью своего метода find она получает её номер из таблицы. Описанный только что метод мы вызовем в самом начале метода «view» и с помощью его результата получим список товаров из категории.
<?phpclass Product extends AppModel{}
?>
И теперь можем связать их с помощью ассоциации “hasMany”. Так как мы в первую очередь работаем с категориями то и ассоциацию будем располагать там-же. Связывать модели будем по полю «categorie_id» из таблицы продуктов. Вот код нашей ассоциации которая должна располагаться в модели «Categorie».
var $hasMany = array('Product' => array('className' => 'Product',
'conditions' => '',
'order' => '',
'limit' => '',
'foreignKey' => 'category_id',
'finderQuery' => ''
)
);
Ничего сложного. Мы лишь указали вторую модель и поле для связки. Теперь, при вызове метода «find» мы будем получать данные не только из таблицы категорий, но и из таблицы продуктов. Полученная информация будет иметь следующий вид.
array(2) { ["Categorie"]=> // Имя модели
array(4) {
// Данные запрошенной категории
}
["Product"]=> // Имя модели
array(5) {
[0]=>
array(8) {
Данные продукта 1
}
[1]=>
array(8) {
Данные продукта 2
}
[2]=>
array(8) {
Данные продукта 3
}
[3]=>
array(8) {
Данные продукта 4
}
}
}
Осталось собрать всё воедино и описать метод «view».
function view($cat_t_name){
$categorie_id = $this->Categorie->getCatIdByTName($cat_t_name);
$categorie = $this->Categorie->find("id='$categorie_id'");
$this->set('categorie',$categorie);
}
Поработаем над отображением. Оно будет храниться в файле ./app/view/categorie/view.ctp.
<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>
Как видите, в начале шаблона мы отображаем данные о категории, а дальше о продуктах. Именно в этом мы и выиграли используя ассоциацию — за одно обращение к модели получили 2 группы данных.
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);
}
}
Это простой класс с одним методом — resize. Он изменяет размер изображения и создаёт его кэшированную копию в директории ./app/webroot/img/cache. При этом метод возвращает адрес уже изменённой картинки. Обратите внимание на то что хэлпер работает только с JPG-изображениями. Это сделано из-за того что все фотографии продуктов у нас хранятся в формате JPG. Для активизации поместим его имя в массив «helpers» контроллера.
var $helpers = Array('Image');
Пропишем вызов функции resize в шаблоне view.ctp. Прямо перед отображением описания товара ($product['about']) поместим следующий код.
<img src='<?=$image->resize('./img/products/'.$product['photo'],60);?>' hspace='5' vspace='5' align='left'/>
Если Вы всё сделали правильно то при просмотре категории отобразятся изображения шириной 60 пикселей.
function getProductIdByTName($t_name){
$result = $this->find("t_name='$t_name'","id");
return $result['Product']['id'];
}
Всё практически точно так же как и в категориях. Код метода «view» будет следующий.
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;?>
Данные товара отобразятся если будут переданы в массиве «last_product». Если нет, то клиенту не будет показано никакого упоминания о прошлом товаре. Попробуйте проверить это самостоятельно.
<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>
А в контроллер, работающий с продуктами, добавим метод «change_rating». Действия, происходящие в нём, будут делиться на 2 этапа. В начале мы вызовем метод поднятия рейтинга, которому передадим номер товара и его оценку, а затем перенесём пользователя обратно на просмотр этого товара. Функция поднятия рейтинга должна располагаться в модели и имеет следующий код:
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');
Информацию о них Вы можете получить по ссылкам http://book.cakephp.org/view/208/AJAX и http://book.cakephp.org/view/207/Javascript. Прежде чем пойти дальше сделаю ещё одно небольшое отступление. Для того что бы работали оба эти хэлпера нужно загрузить JS-скрипты с сайтов http://www.prototypejs.org/ и http://script.aculo.us/. Но можно загрузить архив лишь с последнего. После его загрузки пройдите в директорию архива «src» и файл «scriptaculous.js» скопируйте в папку «./app/webroot/js/». Туда же поместите файл «prototype.js» из директории «lib». Можно возвращаться к коду. Для активизации обоих библиотек в самое начало отображения впишите следующий код:
<?=$javascript->link('prototype');?><?=$javascript
->link('scriptaculous');?>
Он автоматически создаст ссылки на требуемые скрипты и они будут подключаться при загрузке страницы. Осталась фоновая отправка формы. В этом нам поможет метод «submit» (http://book.cakephp.org/view/629/submit) «Ajax»-хэлпера. Он возвращает код кнопки отправки формы, при нажатии на которую данные отправляются на сервер в фоновом режиме. Ему нужно передать 2 параметра — надпись, которая отобразиться на кнопке, и массив настроек. Настроек мы укажем только две. Это будут адрес отправки и вызов функции «alert» после отправления данных (сообщение о принятии заказа). В итоге код старой кнопки должен замениться вот таким:
<?=$ajax->submit('Отправить',Array('url'=>"/product/order/{$product['id']}/",'after'=>'alert("Your order is sended!")'));?>
Попробуйте теперь обновить страницу и оформить заказ. Браузер должен показать сообщение «Your order is sended», а с сайта должно отправиться письмо с заказом.
<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>
Из кода видно что для сохранения данных в контроллере «comment» мы будем использовать метод «send». Этому действию будут переданы всего 2 поля — автор и его отзыв. Для вставки в таблицу их недостаточно. В массив «data» мы поместим номер продукта (передаваемый как параметр нашему методу) и нулевое значение поля «id» (для auto increment`a)
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);
}
Как любой добросовестный разработчик мы должны провести и проверку введённых данных. Для этого в CP имеются 2 инструмента Validation(http://book.cakephp.org/view/125/Data-Validation) и Sanitize(http://book.cakephp.org/view/153/Data-Sanitization/). Воспользуемся обоими. Имя автора мы проверим с помощью первого, а поле отзыва — с помощью второго. Для валидации нужно создать соответствующее правило в модели. Установим по нему 2 ограничения — имя автора должно состоять только из букв и цифр, и минимальная его длинна должна быть не менее трёх символов.
var $validate = Array( 'author'=>Array(
'rule'=>'alphaNumeric',
'minLength'=>3,
)
);
Обратите внимание на то что под правило «alphaNumeric» русские буквы не попадают. Саму проверку мы будем осуществлять в контроллере с помощью метода «validates» (http://api.cakephp.org/class/model — описание этого метода, http://book.cakephp.org/fr/view/410/Validating-Data-from-the-Controller — описание проведения валидации через контроллер) который в случае удачной проверки вернёт «true». В связи с этим логика сохранения комментария немного изменится. Перед сохранением мы передадим данные в модель, проверим их, и только после этого произведём добавление в базу. В итоге код метода «send» становится следующим.
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>
Обновите страницу и Вы увидите что в самом её низу появились переключатели между отзывами.
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);
На этом настройка кэширования завершена. Попробуйте подобавлять отзывы и проверить изменился ли внешний вид страницы.
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».