Автор: Kuzya
Сайт:
kuzya.name
Начальный практикум в CakePHP.
Официальный сайт фреймворка: http://cakephp.org/
Русскоязычный сайт фреймворка: http://cake-php.ru/
Версия фреймворка на момент написания статьи: 1.2
К сожалению редактор форума не позволяет создавать структурированный текст, из-за чего большие части кода порой трудно читаются. В этом случае рекомендую Вам скачать PDF-вариант статьи:
practical_intro_cake.pdf (16 страниц, шрифт Times New Roman, 12pt).
1. Введение.
Здравствуйте. В этой статье я хочу описать базовые практические методы работы с фреймворком
CakePHP (далее CP). Почему я пишу именно «практические»? Потому что в этой статье не будет
описания основ CP, теории его дизайна, MVC и всего подобного. Здесь будет лишь практика. Во многих
науках есть теоретическая и практическая части. Программирование не исключение. Теорию Вы можете
почерпнуть из документации, а практика нарабатывается либо самостоятельно, либо берётся из
различных статей, в том числи и из этой. Второй вариант естественно легче, а в связке с документацией
является отличным стартом для начинающих или познающих инструмент «с нуля».
Остановимся на документации подробнее. Так как у данного фреймворка есть представительство
в нашей стране, то есть и русскоязычный перевод документации. Его Вы можете найти в Wiki сайта
(путеводитель по ней есть на главной странице). Описание версии 1.2 ещё не до конца переведено и
находится на сайте в неполном виде. Зато документация к версии 1.1 переведена полностью и может
служить хорошим подспорьем разработчикам для которых языковой барьер является существенным
препятствием на пути изучения чего-то нового. Конечно же в текущей версии языка имеются отличия от
предыдущей, но базовые основы и большая часть инструментария осталась та же. Поэтому в каких-либо
трудных ситуациях Вы можете обращаться к описанию версии 1.1. Официальная документация
находится по адресу http://book.cakephp.org/. В ней дано полное и, на мой взгляд, понятное описание
всего что есть в CP. Иногда Вам может понадобится посмотреть описание метода или свойства
определённого класса. В этом случае стоит обращаться по адресу http://api.cakephp.org/. Этот сайт
содержит данные по всему что есть внутри фреймворка. Очень удобно сделаны несколько вариантов
просмотра — по таблице классов и по списку файлов. Вы в любом случае найдёте то что Вас
интересует.
Для хорошего понимания материала читателю достаточно ознакомится с документацией в общих
чертах. Желательно создать тестовое приложение. Можно самостоятельно, можно взять описание
тестового блога. Главное, иметь хоть какое-то представление о работе фреймворка и структуре его
документации (как на русскоязычном сайте, так и на официальном). Внимание! Обязательно
ознакомьтесь с соглашениями CakePHP (http://cake-php.ru/wiki/Manual/BasicPrinciples/Conventions) и
постарайтесь всегда держать их рядом. По началу у меня было много проблем именно из-за соглашений,
поэтому что бы не мучаться почаще к ним обращайтесь.
Целью нашей практики будет написание слабой имитации интернет-магазина. В этом магазине
можно будет просматривать категории товаров, списки их содержимого, сами товары. Отдельно с
товарами можно будет производить следующие операции — заказывать, голосовать за них и оставлять о
них отзывы. Приступим.
2. Приготовления
Начнём с предварительной подготовки. Первым шагом Вам нужно скопировать файлы CP на «чистый»
хост. У меня он называется «cakephp».Если хост у Вас назван по другому то будьте внимательны к
ссылкам из статьи во избежание каких-либо проблем.
2.1 База данных
После копирования откройте файл «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 – цена (единицы цен будут обсуждаться ниже).
2.2 Дизайн
Возьмёмся за внешний вид. Всё что нужно для этого находится в прикреплённом архиве «templates.zip».
Директории «img» и «css» скопируйте прямо в «app/webroot», предварительно удалив такие-же старые
директории. Далее пройдите в папку «./cake/libs/view/layouts/» и поместите туда шаблон
«default_layout.ctp», попутно переименовав его в «default.ctp». Это наш основной дизайн. Обратитесь к
корню сайта и Вы его увидите.
Обратите внимание на отладочную информацию внизу экрана. Там отображаются запросы к базе
данных, количество затронутых ими рядов и т.д. Нам эта информация не понадобится и мы её
отключим. Для этого снова откройте файл «./app/config/core.php» и установите параметр «debug» в 0.
Обновите страницу. Информация о запросах пропала. Теперь можно перейти непосредственно к
программированию.
3.0 Программирование.
Сейчас мы займёмся непосредственно кодом. Если в отношении него у Вас возникнут какие-то вопросы
то обращайтесь к документации. Всё что описано ниже делалось именно по ней, хотя многочисленные
ссылки на неё должны не дать Вам запутаться. В случае если у Вас что-то не будет получаться то
попробуйте вернуться к началу раздела и перечитать его заново.
3.1 Категории.
Самое первое что должен делать наш магазин — показывать список категорий товаров. Для этого мы
создадим контроллер categories и соответствующую модель. В папке контроллеров(app/controllers)
создайте файл «categorie_controler.php». В классе нового контроллера объявите функцию «index»,
которая и будет выводить нужный нам список.
PHP код:
<?php
class CategorieController extends AppController
{
function index()
{
}
}
?>
И создадим пустую модель, сохранив её в директории app/models. Файл в котором она будет храниться
назовите «categorie.php».
PHP код:
<?php
class Categorie extends AppModel
{
}
?>
С функционалом всё очень просто. В контроллере мы будем получать массив категорий и передавать их
отображению. Там они, с помощью цикла foreach, будут отображаться. Для получения данных можно
пойти двумя путями — использовать стандартные средства выборки (http://cakephp.
ru/wiki/Manual/Developing/Models/Retrieving) или создать и использовать свои. Я решил выбрать
второй вариант т.к. он «чище» в плане возвращаемых в контроллер данных. В модель «Categorie»
добавим функцию getCatsList(), которая будет возвращать массив категорий. Вот её код:
PHP код:
function getCatsList()
{
$cats = Array();
$result = $this->findAll();
foreach($result as $line)
$cats[] = $line['Categorie'];
return $cats;
}
У такого решения есть 2 плюса - в контроллер поступят уже обработанные данные, тогда как на выходе
той же findAll-функции они достаточно «сырые» и требуют дополнительной обработки(что мы и делаем
в теле функции), и соответствие концепции MVC по которой все операции с данными должны
происходить лишь в модели.
Теперь в контроллер добавим вызов этой функции и передачу результатов её работы в
отображение.
PHP код:
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 и замените две хранящиеся там настройки
следующей строкой:
PHP код:
Router::connect('/', array('controller' => 'categorie', 'action' => 'index'));
Мы указали что при обращении к корню сайта нужно подгружать контроллер «categorie» и вызывать его
метод «index». Подробнее о работе с роутами Вы можете почитать тут - http://cakephp.
ru/wiki/Manual/Developing/Configuration/Routes.
Как видно из прошлого отображения, за просмотр отдельной категории отвечает метод «view».
При этом в ссылке ему передаётся её транслитеррационное имя. Все продукты мы будем получать
стандартно — по порядковому номеру выбранной категории. Следовательно, нам нужно написать в
модели функцию которая по переданному имени определит этот номер. Назовём эту функцию
«getCatIdByTName»
PHP код:
function getCatIdByTName($cat_t_name)
{
$result = $this->find("cat_t_name='$cat_t_name'",'id');
return $result['Categorie']['id'];
}
При вызове ей должно передаваться имя категории в транслите, и с помощью своего метода find она
получает её номер из таблицы. Описанный только что метод мы вызовем в самом начале метода «view»
и с помощью его результата получим список товаров из категории.
Список товаров можно получить той же функцией find. Но мы поступим немного по иному и
воспользуемся ассоциациями. Мне кажется что ассоциации являются очень удачным инструментом в
CakePHP. Подробнее о них Вы можете почитать по адресу http://cakephp.
ru/wiki/Manual/Developing/Models/Associations . Из четырёх ассоциаций нам потребуется лишь одна
- «один ко многим». Но что бы связать 2 модели нужно сначала их создать. А у нас есть всего одна
модель. Так как мы получаем список продуктов находящихся в таблице «products» то, следуя
соглашениям, нужно создать модель «Product». Создадим её совершенно пустой:
PHP код:
<?php
class Product extends AppModel
{
}
?>
И теперь можем связать их с помощью ассоциации “hasMany”. Так как мы в первую очередь работаем с
категориями то и ассоциацию будем располагать там-же. Связывать модели будем по полю
«categorie_id» из таблицы продуктов. Вот код нашей ассоциации которая должна располагаться в модели
«Categorie».
PHP код:
var $hasMany = array('Product' =>
array('className' => 'Product',
'conditions' => '',
'order' => '',
'limit' => '',
'foreignKey' => 'category_id',
'finderQuery' => ''
)
);
Ничего сложного. Мы лишь указали вторую модель и поле для связки. Теперь, при вызове метода «find»
мы будем получать данные не только из таблицы категорий, но и из таблицы продуктов. Полученная
информация будет иметь следующий вид.
PHP код:
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».
PHP код:
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 группы данных.
Вы должны помнить о том что в самом начале статьи я писал о изображениях продуктов
находящихся в веб-директории фреймворка, в папке «img». Их нету в приведенном выше отображении
потому что ими мы займёмся отдельно. Сейчас мы напишем хэлпер который будет заниматься
изменением размера изображений. Если Вы не знакомы с хэлперами то можете почитать про них тут —
http://cake-php.ru/wiki/Manual11/helpers (описание с версии 1.1) или тут - http://book.cakephp.org/view/98/
Helpers (официальное описание для версии 1.2). Это небольшие инструменты которые можно
использовать на уровне отображений. Хранятся пользовательские хэлперы в директории
«./app/views/helpers/» (создание собственных хэлперов описано здесь -
http://book.cakephp.org/view/101/Creating-Helpers). Так как наш хэлпер будет отвечать за работу с
изображениями то назовём его «image». Следовательно в директории пользовательских хэлперов
создаём файл image.php. Вписываем туда следующий код.
PHP код:
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» контроллера.
PHP код:
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
пикселей.
