Интеграция Zend Framework и Doctrine

Хочу предложить вашему вниманию мой новый перевод отличной статьи Integrating Zend Framework and Doctrine от Ruben Vermeersch.

Эта статья научит вас всем шагам, необходимым для создания проекта, использующего Zend Framework и Doctrine. Шаг за шагом мы создадим простейшую доску сообщений.

Прежде, чем начать

Хоть я и старался сохранить статью простой, это не значит, что тут будет введение в обе технологии. Я предлагаю вам поиграть с обеими технологиями по отдельности, прежде чем пытаться объединить их. Они обе имеют достаточно хорошую документацию, что бы с нее начать: Zend Framework Quick Start и Doctrine’s My First Project. Кроме того, Akra’s Zend Framework Tutorial (Введение в Zend Framework” - перевод от Александра Мусаева) тоже очень хорошее введение в тему.

Zend Framework имеет “используй-как-хочешь” архитектуру. Это значит, что вы свободны в использовании только тех частей, которые вам нужны, в отличии от других фреймворков, предлагающих решения “все-или-ничего”. Эта архитектура (используй-как-хочешь) великолепна: она позволяет нам разрабатывать настоящие Zend Framework приложения, без использования ZF абстракции от БД (Zend_Db). Хотя Zend_Db не плохая технология, она все еще довольно низкоуровневая, и слишком близка к базе данных. Используя Doctrine, вы можете манипулировать вашими данными как объектами, не слишком беспокоясь о базе данных.

Zend Framework предоставляет вам много свободы в том, как строить своё приложение. Иными словами, он не заставляет вас использовать строго определенную структуру проекта. В этой статье я старался наиболее близко следовать предлагаемой по умолчанию структура проекта. Тем не менее, все это вопрос личного вкуса.

Итак, приступим!

Сначала мы создадим типовую структуру проекта и установим библиотеки. Откройте файл-менеджер и создайте структуру папок, как показано ниже. Я объясню, назначение этих папок через минуту.

Типовая структура папок

Типовая структура папок

Тут много папок, но большинство из них должны быть вам знакомы, если вы уже разрабатывали приложения на Zend Framework. Вот отличия:

  • application/doctrine/ — в ней содержаться все файлы данных Doctrine, такие как sql и yaml схемы бд, миграции, дампы, и т.п.
  • application/models/ — Doctrine будет автоматически генерировать файлы моделей в этой директории, что легко использовать в рамках вашего Zend Framework приложения.
  • library/ — как правило, вы бы просто установить свою копию Zend Framework в эту папку. В нашем приложении нам нужны две библиотеки: Zend Framework и Doctrine. Таким образом, мы делаем два подкаталога и устанавливаем туда библиотеки.
  • scripts/ — Doctrine поставляется с удобнымм скриптами командной строки (делее просто “скрипты доктрины”), мы будем хранить их здесь (как в предложении, упомянутом выше).

Следующий шаг — установка Zend Framework и Doctrine. Загрузите последнюю версию с соответствующих веб-сайтов и разархивируйте library (для ZF) или lib (для Doctrine) в созданные нами папки. Должно получиться приблизительно следующее:

Zend Framework и Doctrine установлены

Время заняться загрузочным файлом

Если вы помните по Zend Framework Quick Start, мы должны создать загрузочный файл. Мы сделаем это сейчас, но с некоторыми изменениями, с тем, чтобы использовать Doctrine.

Во-первых, мы создадим файлы public/index.php и public/.htaccess. Запустите ваш любимый редактор и скопируйте следующие куски кода:

public/index.php

<?php
require '../application/bootstrap.php';

public/.htaccess

RewriteEngine on

RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1

Как вы видите, все так же, как и в любом приложении на базе Zend Framework.

Файл application/bootstrap.php выглядит немного иначе. Я разделил его на два файла: application/bootstrap.php и application/global.php. Первый занимается обработкой запросов клиентов, последний подключает все необходимые файлы. Я разделил их потому, что код из global.php нужен также в скриптах Doctrine (которое мы увидим через минутку).

application/global.php

<?php
error_reporting(E_ALL | E_STRICT);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
date_default_timezone_set('Europe/Brussels');

/*
* Setup libraries & autoloaders
*/
set_include_path(dirname(__FILE__).'/../library/zendframework'
    . PATH_SEPARATOR . dirname(__FILE__).'/../library/doctrine'
    . PATH_SEPARATOR . dirname(__FILE__).'/models'
    . PATH_SEPARATOR . dirname(__FILE__).'/models/generated'
    . PATH_SEPARATOR . get_include_path());
require 'Zend/Loader.php';
Zend_Loader::registerAutoload('Zend_Loader');

/*
* Set super-global data
*/
Doctrine_Manager::connection("mysql://user:pass@localhost/database");

/*
* Configure Doctrine
*/
Zend_Registry::set('doctrine_config', array(
    'data_fixtures_path'  =>  dirname(__FILE__).'/doctrine/data/fixtures',
    'models_path'         =>  dirname(__FILE__).'/models',
    'migrations_path'     =>  dirname(__FILE__).'/doctrine/migrations',
    'sql_path'            =>  dirname(__FILE__).'/doctrine/data/sql',
    'yaml_schema_path'    =>  dirname(__FILE__).'/doctrine/schema'
));

application/bootstrap.php

<?php
require dirname(__FILE__).'/global.php';

Zend_Controller_Front::run(dirname(__FILE__).'/controllers');
?>

Давайте рассмотрим его шаг за шагом:

<?php
error_reporting(E_ALL | E_STRICT);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
date_default_timezone_set('Europe/Brussels');
?>

Это всегда хорошая идея — установить правильную обработку ошибок и часовой пояс. Здесь ничего особенного.

<?php
/*
* Setup libraries & autoloaders
*/
set_include_path(dirname(__FILE__).'/../library/zendframework'
    . PATH_SEPARATOR . dirname(__FILE__).'/../library/doctrine'
    . PATH_SEPARATOR . dirname(__FILE__).'/models'
    . PATH_SEPARATOR . dirname(__FILE__).'/models/generated'
    . PATH_SEPARATOR . get_include_path());
require 'Zend/Loader.php';
Zend_Loader::registerAutoload('Zend_Loader');
?>

Именно здесь мы подключаем Doctrine. Как вы видите, мы установили include_path для подключения Zend Framework и Doctrine. Мы также подключили папки, в которых Doctrine будет генерировать файлы моделей. Заметим, что нам не нужно настраивать автозагрузчик Doctrine. Использование загрузчика Зенда работает так же хорошо, до тех пор, пока include_path настроен правильно. Предупреждение: в текущей версии ZF (1.5.1) есть ошибка, приводящая к печати безобидных предупреждений при использовании классов шаблонов Doctrine. Она должна быть исправлена в будущих версиях.

<?php
/*
* Set super-global data
*/
Doctrine_Manager::connection("mysql://user:pass@localhost/database");

/*
* Configure Doctrine
*/
Zend_Registry::set('doctrine_config', array(
    'data_fixtures_path'  =>  dirname(__FILE__).'/doctrine/data/fixtures',
    'models_path'         =>  dirname(__FILE__).'/models',
    'migrations_path'     =>  dirname(__FILE__).'/doctrine/migrations',
    'sql_path'            =>  dirname(__FILE__).'/doctrine/data/sql',
    'yaml_schema_path'    =>  dirname(__FILE__).'/doctrine/schema'
));
?>

Это последний кусочек кода. Во-первых, мы создали соединение с базой данных. Чтобы не усложнять, я просто забил в код это строковое значение. В реальных системах вы должны использовать что-нибудь подобное Zend_Config. Я оставлю это вам в качестве домашнего задания. Во-вторых, этот кусок кода настраивает пути к инструментам Doctrine для командной строки (которые генерируют весь код и схемы баз данных). Я сохранил этот массив в Zend_Registry, который является универсальным хранилищем, местом, где вы можете хранить объекты и получить их в нужный момент.

Измените строку соединения Doctrine, в соответствии с параметрами вашей системы. Вы должны указать пустую базу данных. Мы будем наполнять эту базу данных позже.

Содержимое файла application/bootstrap.php не должно вызывать у вас удивления. Опять же, я пытался сохранить этот пример как можно более простым.

Наконец, давайте настроим интерфейс командной строки Doctrine:

scripts/doctrine-cli

#!/usr/bin/env php
<?php
require dirname(__FILE__).'/../application/global.php';

$cli = new Doctrine_Cli(Zend_Registry::get('doctrine_config'));
$cli->run($_SERVER['argv']);

Сделайте этот сценарий исполняемым:

chmod +x scripts/doctrine-cli

и вы готовы двигаться дальше.

Создание приложения

Теперь, когда мы подготовили базовые скрипты, давайте создадим простейшее приложение с использованием Zend Framework и Doctrine. Мы будем создавать очень простую доску сообщений, место, где пользователи могут отправлять сообщения и просматривать чужие сообщения.

Мы будем делать это очень просто: только один контроллер и один скрипт. Скопируйте следующие файлы:

application/views/scripts/index/index.phtml

<html>
<head>
    <title>ZF & Doctrine example</title>
</head>

<body>
<h1>Submit a message:</h1>
<?=$this->form?>

<hr />
<h1>Messages posted:</h1>
<!-- TODO: Show messages here -->
</body>
</html>

application/controllers/IndexController.php

<?php
class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $form = $this->getForm();
        $req = $this->getRequest();
        if ($req->getPost() && $form->isValid($req->getPost())) {
            // TODO: Insert message into database
        }
        $this->view->form = $form;

        // TODO: Retrieve all messages.
    }

    private function getForm()
    {
        $form = new Zend_Form();
        $form->addElement('text', 'name', array(
                'label' => 'Your name',
                'required' => true
        ));
        $form->addElement('textarea', 'message', array(
                'label' => 'Message',
                'required' => true,
                'rows' => 4
        ));
        $form->addElement('submit', 'send');
        return $form;
    }
}
?>

Как вы видите, остались три больших TODO пункта: один в скрипте и два в контроллере. Это места, где мы будем подключать Doctrine. Но для этого мы должны в первую очередь определить некоторые объекты данных. Мы вернемся к контроллеру и скрипту позже, сейчас пришло время для создания схемы базы данных.

Определение схемы базы данных

Doctrine позволяет указать ваши схемы баз данных в YAML-файлах, очень простом текстовом представлении. Мы воспользуемся этим и пусть Doctrine генерирует PHP-файлы автоматически. Я определил следующие схему:

application/doctrine/schema/schema.yml

Message:
    columns:
        id:
            primary: true
            autoincrement: true
            type: integer(4)
        posted:
            type: timestamp
        name:
            type: string(255)
        message:
            type: string

В этом приложении нам нужен только один простой объект: Сообщение, имеющий 4 поля: обязательный уникальный идентификатор, время, когда сообщение было отправлено, название темы и само сообщение.

Теперь мы можем использовать командную строку Doctrine для создания файлов моделей и таблиц баз данных. Из вашего шелла выполните следующее:

$ ./scripts/doctrine-cli generate-models-yaml
generate-models-yaml - Generated models successfully from YAML schema
$ ./scripts/doctrine-cli generate-sql
generate-sql - Generated SQL successfully for models
$ ./scripts/doctrine-cli create-tables
create-tables - Created tables successfully

Если все прошло хорошо, ошибок напечатано не будет. Если это все же произошло, проверьте параметры соединения с БД и указание путей.

Соеденим все вместе

Теперь давайте окончательно решим куски TODO. Мы заменим их шаг за шагом. Полный код для завершенных файлов имеется в конце статьи. Во-первых, мы добавим код для хранения сообщений. Замените это:

<?php
// TODO: Insert message into database
?>

Вот этим (игнорируя тэги <?php и ?>):

<?php
$message = new Message();
$message->fromArray($form->getValues(true));
$message->posted = new Doctrine_Expression('NOW()');
$message->save();
?>

Как вы видите, мы используем объект класса Сообщение. Этот класс был автоматически сгенерирован Doctrine. Вы можете найти его в application/models/. Автозагрузчик позаботится о загрузке всего, что потребуется.

Мы также должны получать сообщения, чтобы показать их. Во-первых, контроллер. Заменить:

<?php
// TODO: Retrieve all messages.
?>

На:

<?php
$messages = Doctrine_Query::create()
        ->from('Message m')
        ->orderBy('m.posted DESC')
        ->execute();
$this->view->messages = $messages;
?>

Опять же, это очень просто. Я использовал DQL запрос, чтобы сортировать в обратной хронологической последовательности.

Теперь все, что осталось — показать сообщение в нашем скрипте представления. Опять же, замените:

<!-- TODO: Show messages here -->

На:

<?php foreach ($this->messages as $message): ?>
    <h2><?=$message->name?> (<?=$message->posted?>)</h2>
    <?=$message->message?>
<?php endforeach; ?>

И мы сделали, результат должен выглядеть подобно этому:

Завершенное приложение

Завершенное приложение

Не используйте это приложение в реальной работе — чтобы сосредоточиться на важных частях, я оставил за кадром множество вещей, таких как, например, экранирование ввода. Вы должны добавить это самостоятельно.

Заключение

Итак, у вас теперь есть чистое и ясное приложение-пример интеграции Zend Framework и Doctrine. Как следование некоторой философии, эта возможность интеграции их очень понятным образом, превращает создание приложений в сплошное удовольствие.

Если у Вас есть какие-либо замечания, комментарии или вопросы, не стесняйтесь, пишите мне по электронной почте (ruben@savanne.be), или оставляйте комментарий в моем блоге.

Приложение: Полный код файлов

application/controllers/IndexController.php

<?php
class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $form = $this->getForm();
        $req = $this->getRequest();
        if ($req->getPost() && $form->isValid($req->getPost())) {
            $message = new Message();
            $message->fromArray($form->getValues(true));
            $message->posted = new Doctrine_Expression('NOW()');
            $message->save();
        }
        $this->view->form = $form;

        $messages = Doctrine_Query::create()
            ->from('Message m')
            ->orderBy('m.posted DESC')
            ->execute();
        $this->view->messages = $messages;
    }

    private function getForm()
    {
        $form = new Zend_Form();
        $form->addElement('text', 'name', array(
                'label' => 'Your name',
                'required' => true
        ));
        $form->addElement('textarea', 'message', array(
                'label' => 'Message',
                'required' => true,
                'rows' => 4
        ));
        $form->addElement('submit', 'send');
        return $form;
    }
}
?>

application/views/scripts/index/index.phtml

<html>
<head>
    <title>ZF & Doctrine example</title>
</head>

<body>
    <h1>Submit a message:</h1>
    <?=$this->form?>

    <hr />
    <h1>Messages posted:</h1>
    <?php foreach ($this->messages as $message): ?>
        <h2><?=$message->name?> (<?=$message->posted?>)</h2>
        <?=$message->message?>
    <?php endforeach; ?>
</body>
</html>
google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru

Популярность: 100%

Метки: , , , ,

11 комментариев to “Интеграция Zend Framework и Doctrine”

  1. Сергей Says:

    Всё это хорошо, но вот какой возникает вопрос: для нужд приложения мы используем Доктрин, но для своих внутренних нужд Zend Framework использует Zend_Db, который также создаёт коннектор к базе. Как сделать так, чтобы этот коннектор был один и для Доктрин и для Zend_Db?

  2. Лобач Олег Says:

    Э… это какие свои внутренние нужды у ZF?

  3. Лобач Олег Says:

    На работе было несколько проектов на связке доктрины и ЗФ. Ни разу не возникло ситуации, когда поднимался коннект Zend_Db.

  4. Сергей Says:

    Прошу прощения, я не слишком компетентен, вот например как у нас работает AuthController:

    private function getAuthAdapter() {
    $db = Zend_DB::factory(Zend_Registry::get(’config’)->database);
    $authAdapter = new Zend_Auth_Adapter_DbTable($db);
    $authAdapter->setTableName($this->basetable)
    ->setIdentityColumn(’login’)
    ->setCredentialColumn(’passw’)
    ->setCredentialTreatment(’MD5(?) AND enabled=1′);
    return $authAdapter;
    }
    разве класс Zend_Auth_Adapter_DbTable не должен в качестве параметра получать объект Zend_DB? и как это сделать с помощью Доктрины?

  5. Лобач Олег Says:

    Да. Тут соглашусь. Класс Zend_Auth_Adapter_DbTable использует коннект Zend_Db.
    Чтобы в данном случае воспользоваться доктриной, надо написать собственный адаптер, реализующий интерфейс Zend_Auth_Adapter_Interface. В принципе, повторить Zend_Auth_Adapter_DbTable для доктрины не должно составить особого труда - там все достаточно просто и прозрачно.

  6. Сергей Says:

    Спасибо, разобрался. Жаль, что никто не осветил эту проблему. Ведь получается что все используют два соединения с базой.

  7. Лобач Олег Says:

    Рад что чем-то помог :)

  8. haspadar Says:

    Могу обрадовать - ничего писать не надо даже для этого.
    Есть Zend_Auth_Adapter_Doctrine_Table официальный причем

    http://framework.zend.com/wiki/display/ZFPROP/Zend_Auth_Adapter_Doctrine_Table

  9. Лобач Олег Says:

    Отличная новость! Спасибо.
    Правда, это еще не официальный адаптер, это всего лишь предложение, но воспользоваться вполне возможно.
    Еще раз спасибо.

  10. nod Says:

    Внимание для корректного отображения результатов работы контролера (ZF 1.6) необходимо изменить bootstrap.php на следующий.
    Это строка некорректна — {{{Zend_Controller_Front::run(dirname(__FILE__).’/controllers’);}}}

    FILE: application/bootstrap.php

    setControllerDirectory(dirname(__FILE__).’/controllers’);
    ?>

  11. nod Says:

    Внимание для корректного отображения результатов работы контролера (ZF 1.6) необходимо изменить bootstrap.php на следующий.
    Это строка некорректна:
    {{{Zend_Controller_Front::run(dirname(__FILE__).’/controllers’);}}}

    -
    -
    -

    FILE: application/bootstrap.php

    <? php
    require dirname(__FILE__).’/global.php’;

    $frontController = Zend_Controller_Front::getInstance();
    $frontController->setControllerDirectory(dirname(__FILE__).’/controllers’);
    ?>

Leave a Reply


FireStats icon Работает с FireStats