Create, read, update…

Каждый, кто писал с нуля движок сайта со сколько-нибудь развесистой базой данных, знает, что в этом случае творческий процесс заканчивается на планировании базы. А дальше начинается повторение одних и тех же команд SELECT, INSERT, UPDATE, DELETE, приправленных интерфейсом по вкусу.

Среди веб-программистов это проклятие получило название CRUD (C, R = Create, Read). Сегодня я вам расскажу, как вместо бесконечного терзания этой мантры поскорее сделать работу, получить деньги и поехать их тратить.
Любой программист должен понимать, что там, где есть повторение, есть и возможность автоматизации. К счастью, в мире есть люди, которые не только знают и мечтают, но и делают. После успеха фреймворка Ruby on Rails, позволявшего создать в общем виде сайт с БД с помощью запуска одного скрипта и нехитрых подсказок к нему, клоны RoR стали появляться как грибы после дождя: CakePHP, CodeIgniter, Canvas (PHP), Django и TurboGears (Python), Catalyst (Perl) и пр. Поскольку мало кому хочется изучать экзотичный синтаксис Ruby, да и хостеры не торопятся включать поддержку его или Python, то остановимся на классической связке PHP + MySQL. Не буду утомлять сравнением: я пользуюсь CakePHP. Кто захочет – пощупает другие варианты самостоятельно.
Что такое фреймворк? Если коротко – это набор кода, обеспечивающий работу скриптов сайта. Сайты меняются, фреймворк остаётся. В отличие от готовой CMS, вам придётся потрудиться и написать логику работы с данными – внесение, отображение и пр.; в отличие от голого PHP, эта задача будет несравненно проще и быстрее. Скажем так: если библиотека функций – это набор инструментов для мастера, то CMS – это фабрика готовой мебели, которую вы можете расставить по своему вкусу. В таком случае, фреймворк – это обставленная мастерская, где вы будете пилить доски по заказу клиента.
Внимание: статья ни в коем случае не заменяет чтения документации. Также знайте, что здесь рассмотрена версия Cake 1.1, а в разработке сейчас находится 1.2, изменяющая манеру обращения с объектами.

Основы
Базовые принципы RoR известны веб-программистам давным-давно: как правило (если не планируется сделать сайт за две недели и забыть о нём), программер выносит в отдельные функции код запросов к БД, а вывод HTML делает с помощью движка шаблонов. Так вот, в теории программирования такая архитектура программы получила название Model–View–Controller (MVC), где Model – это взаимодействие с БД, View – шаблоны, а Controller – логика поведения программы, согласующая работу M и V с задумкой автора и нуждами пользователя (см. рис. 1).

CakePHP – это доведённые до ума M и C. Чтобы врубиться быстро: если в части нашего сайта, например, обстоятельно представлены ложки, то будет вызван объект SpoonsController, который будет общаться с БД через объект Spoon, заботливо созданный Cake. Пример – в листинге 1.

<?php
class SpoonsController extends AppController {

	function index() {
		$this->set('spoons', $this->Spoon->findAll());
	}

	function view($id) {
		$this->set('spoon', $this->Spoon->find(array('id'=>$id));
	}
}
?>
<?php
class Spoon extends AppModel {
  var $name='Spoon';
}
?>

Вам даже не нужно думать, в какие файлы положить код. Контроллер отправляется в файл controllers/spoons_controller.php, модель – в models/spoon.php, всё относительно корня проекта. Одна из сильных черт CakePHP – введение условностей, ограничивающих свободу настройки, зато сильно ускоряющих разработку. Это, в частности, относится к названиям файлов, классов, методов и таблиц: методы объекта SpoonsController в данном случае соответствуют адресам /spoons и /spoons/123, причём во втором случае id записи – 123 – будет автоматически передан в метод. Если кто-то пожелает другие адреса (например, /there/are/spoons) или захочет фривольнее назвать таблицу – ему и только ему, придётся указать это в конфигурации:

<?php
class Spoon extends AppModel {
	var $name='Spoon';
	var $table='adam_s_spoon'; // ложка раздора
}
?>
$Route->connect('/there/are/spoons/:action/*', array('controller'=>'spoons',
	'action'=>'index'));

База и шаблоны
В базе достаточно создать таблицу с именем spoons – и можно практически забыть про SQL в PHP-коде. Например, нам понадобилась форма для редактирования параметров определённой ложки:

<form action="/spoons/edit/<?php print $html->tagValue('Spoon/id'); ?>" method="post">
<?php print $html->hidden('Spoon/id'); ?>
Длина: <?php print $html->input('Spoon/length'); ?><br />
<?php print $html->submit('Сохранить'); ?>
</form>
class SpoonsController extends AppController {
 …
 function edit($id) {
	if (!empty($this->data['Spoon'])) {
		if ($this->Spoon->save($this->data)) {
			$this->flash('Сохранено', '/spoons/edit/'.$id);
		}
	} else {
		$this->data=$this->Spoon->find(array('id'=>$id));
	}
 }
 …

HTML с вызовами вспомогательных функций – в файл views/spoons/edit.thtml. Так в Cake работают шаблоны – используется голый PHP. На первый взгляд, нехорошо, но на самом деле облегчает создание объектов-helper’ов с теми самыми функциями и позволяет использовать нужные логические конструкции, которых, благодаря helper’ам, остаётся совсем немного – всё без ущерба для быстродействия.
Так вот, метод Spoon->save(), если найдёт в таблице поле length, сохранит введённое значение. Если длину ложки знать необходимо, можно указать в классе, что это поле обязательно к заполнению:

class Spoon extends AppModel {
	var $name='Spoon';
	var $validate=array(
		'length'=>VALID_NOT_EMPTY
	);
}

А в шаблоне вписать:

<?php print $html->tagErrorMsg('Spoon/lentgh', 'Не указана длина'); ?>

И опять наслаждаться вожделенной automagic. Остаётся добавить удаление записи из таблицы (с помощью $this->Spoon->del($id)), в /views/spoons/index.thtml вывести список всех записей (из переменной $spoons) – и готов интерфейс для базы данных ложек.

Усложняем
Конечно, основная тоска начинается, когда появляется несколько связанных таблиц. Например, ложки обычно продаются в наборах (см. листинг 2).

create table sets (
	id int not null auto_increment primary key,
	name varchar(255) not null
);

create table spoons (
id int not null auto_increment primary key,
	set_id int not null,
	length smallint not null,
	name varchar(255) not null,
	amount smallint not null,
	index (set_id)
);

class Spoon extends AppModel {
	…
	var $belongsTo=array(
		'Set'=>array(
			'className'  => 'Set',
			//'foreignKey' => 'set_id'
		)
	);
	…

class Set extends AppModel {
	var $name='Set';
	var $hasMany=array(
		'Spoon'=>array(
			'className'  => 'Spoon',
			//'foreignKey' => 'spoon_id'
		)
	);
	var $recursive=0;
}

Теперь, если надо увидеть и параметры набора, и список ложек в нём, можно выбрать данные по обеим таблицам:

$set=$this->Set->findAll(array('id'=>$id), NULL, NULL, 1);
print_r($set);

где «1» как раз и указывает, что нужно выбрать все связанные данные на глубину рекурсии в один уровень (свойство $recursive устанавливает глубину по умолчанию в 0). Получим такой массив:

Array
(
    [Set] => Array
        (
            [id] => 1
            [name] => Походный набор для бригады императорских ниндзюцу
        )

    [Spoon] => Array
        (
            [0] => Array
                (
                    [id] => 1
                    [set_id] => 1
                    [name] => Чайная ложечка
                    [length] => 10
                    [amount] => 4
                )
            [1] => Array
                (
                    [id] => 2
                    [seе_id] => 1
                    [name] => Ложка для рисовой похлебки
                    [length] => 20
                    [amount] => 4
                )
        )
)

Для связи типа «многие ко многим» используется параметр $hasAndBelongsToMany, который задаёт таблицу с двумя полями: set_id и spoon_id – и тогда при выборке Set или Spoon также включаются соответствующие записи другой таблицы. Для связи «один к одному» – параметр $hasOne.

Всё вместе
Теперь, когда вы представляете, как работает CakePHP в типичных случаях, я скажу, что в его составе есть скрипт bake (происшедший от Ruby’вского rake, заменяющего никсовый make), который создаёт в общем виде контроллеры, модели и шаблоны по созданным вами таблицам в БД. Собственно, как можно видеть, его работа не так уж сложна – она сводится к паре вопросов, соответствующим опциям Cake и нескольким минутам программерских потуг.

Расширяемость
Как уже можно было видеть, возможности шаблонов расширяются с помощью helper’ов; в контроллерах для того же служат компоненты. Например, компонент Acl из стандартной поставки позволяет ограничить полномочия пользователей, разнеся их по группам и установив разрешения с помощью ini-файла или БД. Как ни странно, Cake не включает в себя методы для аутентификации пользователя (проверки пароля) – поэтому стоит направиться на сайт cakephp.org и в тамошней «Пекарне» поискать рецепты для своего пирога: узнать, как другие программисты реализовали эту задачу, присмотреть готовые плагины, включающие в себя нужные контроллеры/компоненты, модели и шаблоны.


Рекомендуем почитать: