SlideShare a Scribd company logo
1 of 60
Download to read offline
ZFConf 2010




 Zend Framework и Doctrine
                  Степан Танасийчук
                   ceo@stfalcon.com
Чем я занимаюсь?

                  Web разработкой занялся в
                   2003 году
                  С Zend Framework начал
                   работать в 2008 году
                  Руковожу собственной веб-
                   студией с 2009 года
                  Активный участник сообщества
                   zendframework.ru
                  Люблю прикольные смайлы :]
Содержание доклада

   Подключение Doctrine к ZF проекту
   Скрипт для работы с Doctrine_Cli
   Генерация моделей по YAML схемам
   Механизм миграций
   Наследование в моделях
   Шаблоны расширений
   Адаптер для Zend_Auth
   Адаптер для Zend_Paginator
   ZFEngine и использование Doctrine в модульном ZF
    приложении
Несколько слов о Doctrine

   ORM библиотека для PHP 5.2.3+
   Использует паттерны Active Record, Data Mapper и Metadata
    Mapping
   Собственный язык запросов — DQL (по мотивам HQL)
   Связи один-к-одному, один-ко-многим и многие-к-многим
   Автогенерация моделей по yaml схемам
   Экспорт и импорт из/в yaml
   Механизм миграций
   Шаблоны поведений (l18n, Versionable, NestedSet, etc.)
Подключаем Doctrine к ZF проекту

   Размещаем Doctrine в library/Doctrine:
    $ svn export http://svn.doctrine-
    project.org/tags/1.2.1/lib/Doctrine/ ./library/Doctrine


   Прописываем следующие настройки в application.ini:
    autoloadernamespaces[] = "Doctrine"
Parables_Application_Resource_Doctrine


  Matthew Lurz добавил в Zend Framework proposal
  application-ресурс для подключения Doctrine.

  Его класс называется
  Parables_Application_Resource_Doctrine и лежит здесь
  http://github.com/mlurz71/parables
ZFEngine_Application_Resource_Doctrine


  Мы немного изменили код
  Parables_Application_Resource_Doctrine для работы с
  Doctrine 1.2.x и храним его в репозитории ZFEngine как
  ZFEngine_Application_Resource_Doctrine

  ZFEngine это сборная солянка классов, которые мы
  используем при разработке проектов на ZF. Лежит все
  здесь: http://zfengine.com

  В основном код наш. Также есть чужой, но с некоторыми
  изменениями. Надеюсь, что это все в рамках закона ^_~.
Подключаем ZFEngine к ZF проекту

   Размещаем ZFEngine в library/ZFEngine:
    $ svn export
    http://svn2.assembla.com/svn/zfengine/trunk/library/ZFEngine/
    ./library/ZFEngine


   Прописываем следующие настройки в application.ini:
    autoloadernamespaces[] = "ZFEngine"
    pluginPaths.ZFEngine_Application_Resource =
    "ZFEngine/Application/Resource"
Настраиваем подключение к БД

 resources.doctrine.connections.primary.dsn.adapter = "mysql"
 resources.doctrine.connections.primary.dsn.username = "root"
 resources.doctrine.connections.primary.dsn.password = "******"
 resources.doctrine.connections.primary.dsn.host = "localhost"
 resources.doctrine.connections.primary.dsn.dbname = "zfconf"
 resources.doctrine.connections.primary.options.charset = "utf8"
 resources.doctrine.connections.primary.options.collate =
 "utf8_unicode_ci"
Настраиваем Doctrine_Manager

 resources.doctrine.manager.attributes.attr_autoload_table_classes = 1
 resources.doctrine.manager.attributes.attr_use_native_enum = 1
 resources.doctrine.manager.attributes.attr_quote_identifier = 1
 resources.doctrine.manager.attributes.attr_auto_free_query_objects = 1
 resources.doctrine.manager.attributes.attr_auto_accessor_override = 1
 resources.doctrine.manager.attributes.attr_model_loading =
 "model_loading_conservative"
MODEL_LOADING_PEAR

В Doctrine 1.2 появился новый режим для автозагрузки
моделей — MODEL_LOADING_PEAR, но при
использовании этого режима не работает generate-
migration-diff :(.
Я заметил это уже в процессе подготовки доклада и пока
просто написал в багрепорт Doctrine.
Для проектов с НЕмодульной структурой

   Указываем путь к директории с моделями:
    resources.doctrine.manager.models_path = APPLICATION_PATH "/models"
Настраиваем кеширование

   resources.doctrine.manager.*
    .attributes.attr_result_cache.driver = "memcache"
    .attributes.attr_result_cache.lifespan = 3600
    .attributes.attr_result_cache.options.servers.host = "localhost"
    .attributes.attr_result_cache.options.servers.port = 11211
    .attributes.attr_result_cache.options.servers.persistent = 1
    .attributes.attr_result_cache.options.compression = 0
Настраиваем Doctrine_Cli

 doctrine_cli.data_fixtures_path = APPLICATION_PATH
 "/configs/doctrine/data/fixtures"
 doctrine_cli.models_path = APPLICATION_PATH "/models"
 doctrine_cli.migrations_path = APPLICATION_PATH
 "/configs/doctrine/migrations"
 doctrine_cli.sql_path = APPLICATION_PATH "/configs/doctrine/data/sql"
 doctrine_cli.yaml_schema_path = APPLICATION_PATH
 "/configs/doctrine/schema"

 doctrine_cli.generate_models_options.generateBaseClasses = 1
 doctrine_cli.generate_models_options.baseClassesDirectory = "Base"
 doctrine_cli.generate_models_options.generateTableClasses = 1
Cкрипт для работы с Doctrine_Cli

 ./application/sripts/common.php
 <?php
 define('APPLICATION_ENV', 'development');
 define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/..'));
 set_include_path(implode(PATH_SEPARATOR, array(
      realpath(APPLICATION_PATH . '/../library'),
      get_include_path(),
 )));
Cкрипт для работы с Doctrine_Cli

 ./application/sripts/doctrine
 #!/usr/bin/env php
 <?php
 require_once 'common.php';
 require_once 'Zend/Application.php';

 $application = new Zend_Application(
     APPLICATION_ENV,
     APPLICATION_PATH . '/configs/application.ini'
 );
 $application->getBootstrap()
     ->bootstrap();

 $cli = new Doctrine_Cli($application->getOption('doctrine_cli'));
 $cli->run($_SERVER['argv']);
Проверяем как работает

   Запускаем скрипт без параметров:
    $ ./application/sripts/doctrine
    Doctrine Command Line Interface

    ./application/sripts/doctrine   generate-sql
    ./application/sripts/doctrine   create-db
    ./application/sripts/doctrine   generate-yaml-models
    ./application/sripts/doctrine   dql
    ./application/sripts/doctrine   generate-migrations-models
    ./application/sripts/doctrine   generate-yaml-db
    ./application/sripts/doctrine   generate-models-yaml
    ./application/sripts/doctrine   generate-migrations-diff
    ./application/sripts/doctrine   generate-migration
    ./application/sripts/doctrine   create-tables
    ./application/sripts/doctrine   drop-db
    ./application/sripts/doctrine   generate-migrations-db
    ... и ещё 9ть команд, которые   не поместились на этом слайде (:
Создадим схему модели User

 ./application/configs/doctrine/schema/User.yml
 User:
    tableName: users
    options:
       type: INNODB
       collate: utf8_unicode_ci
       charset: utf8
    columns:
       id:
          type: integer(4)
          primary: true
          autoincrement: true
       login: string(32)
       email: string(255)
Генерируем модели по YAML схемам

   Запускаем скрипт с параметром generate-models-yaml:
    $ ./application/sripts/doctrine generate-models-yaml
    generate-models-yaml - Generated models successfully from YAML schema


   Получаем готовые модели:
    ./application/models
    |-- Base
    |   `-- BaseUser.php
    |-- User.php
    `-- UserTable.php


    Важная деталь: сами YAML схемы можно сгенерировать
    непосредственно с структуры БД используя команду
    generate-yaml-db.
Сгенерированный код базовой модели
User
./application/models/Base/BaseUser.php
<?php
abstract class BaseUser extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->setTableName('users');
        $this->hasColumn('id', 'integer', 4, array('type' => 'integer', 'unsigned' =>
true, 'primary' => true, 'autoincrement' => true, 'length' => '4'));
        // Здесь было описание полей login и email ...
        $this->option('type', 'INNODB');
        $this->option('collate', 'utf8_unicode_ci');
        $this->option('charset', 'utf8');
    }


    public function setUp()
    {
        parent::setUp();
    }
}
Сгенерированный код модели User и
маппера UserTable
 ./application/models/User.php
 <?php
 class User extends BaseUser
 {
 }


 ./application/models/UserTable.php
 <?php
 class UserTable extends Doctrine_Table
 {
 }
Напишем свой сеттер для поля email

 ./application/models/User.php
 <?php
 /**
   * User model
   */
 class User extends BaseUser
 {
       /**
         * Set email adress into lowercase
         *
         * @param string $email
         * @return void
         */
       public function setEmail($email)
       {
             $this->_set('email', strtolower($email));
       }
 }
Пишем экшн для проверки работы

 ./application/controllers/IndexController.php
 <?php
 class IndexController extends Zend_Controller_Action
 {
     /**
       * Simple action
       *
       * @return void
       */
     public function indexAction()
     {
          $user = new User();
          $user->login = 'stfalcon';
          $user->email = 'CEO@STFalcon.COM';
          Zend_Debug::dump($user->toArray());
     }
 }
Запускаем в браузере

array
  'id' => null
  'login' => string 'stfalcon' (length=8)
  'email' => string 'ceo@stfalcon.com' (length=16)
Миграции

   Сгенерируем первый класс миграций. Его можно
    генерировать из классов моделей или БД (см. мануал к
    Doctrine).
    $ ./application/sripts/doctrine generate-migrations-models
    generate-migrations-models - Generated migration classes successfully
    from models


   Получаем готовую модель миграций:
    ./application/configs/doctrine/
    |-- data
    |   |-- fixtures
    |   `-- sql
    |-- migrations
    |   `-- 1268942153_adduser.php
    `-- schema
        `-- User.yml
Сгенерированный код первой модели
миграций

 ./application/configs/doctrine/migrations/1268942153_adduser.php
 <?php
 class Adduser extends Doctrine_Migration_Base
 {
     public function up()
     {
         $this->createTable('user', array('id' => array('type' =>
 'integer', 'unsigned' => true, 'primary' => true, 'autoincrement' =>
 true, 'length' => 4),
         // Здесь были параметры для создания полей login и email ...
              ), array('type' => 'INNODB', 'indexes' => array(),
 'primary' => array(0 => 'id'), 'collate' => 'utf8_unicode_ci',
 'charset' => 'utf8'));
     }
     public function down()
     {
         $this->dropTable('user');
     }
 }
Создадим БД и накатим на неё наши
изменения

   Создаем БД (например на production сервере):
    mysql> CREATE DATABASE `zfconf`;
    Query OK, 1 row affected (0,00 sec)


   Накатываем на неё миграцию:
    $ ./application/sripts/doctrine migrate
    migrate - migrated successfully to version #1


   Выведем список таблиц:
    mysql> SHOW TABLES;
    migration_version
    users
Проверяем работу скрипта

   Структура таблицы в которой хранится номер миграции:
    mysql> SHOW CREATE TABLE `migration_version`;

    CREATE TABLE `migration_version` (
      `version` int(11) DEFAULT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1


   Структура таблицы пользователей:
    mysql> SHOW CREATE TABLE `users`;

    CREATE TABLE `users` (
      `id` int(10) NOT NULL AUTO_INCREMENT,
      `login` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
      `email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Наследование в YAML схемах

 ./application/configs/doctrine/schema/Administrator.yml
 ## Administrator schema
 Administrator:
   tableName: administrators
   inheritance:
     extends: User
     type: concrete
   columns:
     password_hash: string(32)
     password_salt: string(8)
   actAs: [Timestampable]
Работаем с Doctrine_Cli

   В первую очередь делаем migration-diff — он генерирует
    классы миграций на основе различий между кодом моделей
    и YAML схемами:
    $ ./application/sripts/doctrine generate-migrations-diff
    generate-migrations-diff - Generated migration classes successfully
    from difference

    ./application/configs/doctrine
    |-- data
    |   |-- fixtures
    |   `-- sql
    |-- migrations
    |   |-- 1268942153_adduser.php
    |   `-- 1268942505_version2.php
    `-- schema
        |-- Administrator.yml
        `-- User.yml
Работаем с Doctrine_Cli

   Генерируем код моделей:
    $ ./application/sripts/doctrine generate-models-yaml
    generate-models-yaml - Generated models successfully from YAML schema
   Накатываем изменения на БД:
    $ ./application/sripts/doctrine migrate
    migrate - migrated successfully to version #2
Работаем с Doctrine_Cli

   Смотрим, что получилось:
    mysql> SHOW CREATE TABLE `administrators`;

    CREATE TABLE `administrators` (
      `id` int(10) NOT NULL AUTO_INCREMENT,
      `login` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
      `email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
      `password_hash` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL,
      `password_salt` varchar(8) COLLATE utf8_unicode_ci DEFAULT NULL,
      `created_at` datetime NOT NULL,
      `updated_at` datetime NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
По-моему пора сделать авторизацию

   Сначала напишем сеттер для password:
    ./application/models/Administrator.php
    <?php
    class Administrator extends BaseAdministrator
    {
          // Здесь был phpDoc блок ...
          public function setPassword($password)
          {
                if (strlen($password)) {
                    $passwordSalt = substr(md5(mktime()), 0, rand(5,8));
                    $passwordHash = md5($password . $passwordSalt);
                    $this->_set('password_hash', $passwordHash);
                    $this->_set('password_salt', $passwordSalt);
                }
          }
    }
Сгенерируем аккаунт для админа и
сохраним его в БД
 ./application/controllers/IndexController.php
 <?php
 class IndexController extends Zend_Controller_Action
 {
       // Здесь был phpDoc блок ...
       public function indexAction()
       {
             $administrator = new Administrator();
             $administrator->email = 'CEO@STFalcon.COM';
             $administrator->login = 'stfalcon';
             $administrator->password = 'qwerty';

         $administrator->save();

         Zend_Debug::dump($administrator->toArray());
     }
 }
Проверяем содержимое таблицы
administrators
 mysql> SELECT * FROM `administrators`;
 +----+----------+------------------+----------------------------------
 +---------------+---------------------+---------------------+
 | id | login    | email            | password_hash
 | password_salt | created_at          | updated_at          |
 +----+----------+------------------+----------------------------------
 +---------------+---------------------+---------------------+
 | 1 | stfalcon | ceo@stfalcon.com | bcd3987603a947d54480285c16f06fde
 | fc1ed         | 2010-03-18 23:04:11 | 2010-03-18 23:04:11 |
ZendX_Doctrine_Auth_Adapter

 ./application/controllers/IndexController.php
       public function indexAction()
       {
             $authAdapter = new ZendX_Doctrine_Auth_Adapter(
                  Doctrine_Core::getConnectionByTableName('Administrator'));
             $authAdapter->setTableName('Administrator a')
                  ->setIdentityColumn('a.login')
                  ->setCredentialColumn('a.password_hash')
                  ->setCredentialTreatment('MD5(CONCAT(?,a.password_salt))')
                  ->setIdentity('stfalcon')->setCredential('qwerty');
             $auth = Zend_Auth::getInstance();
             $result = $auth->authenticate($authAdapter);
             if ($result->isValid()) {
                  echo '<h1>OK</h1>';
             } else {
                  echo '<h1>FAIL</h1>';
             }
       }
Открываем страницу в браузере




   Все ОК :)
   И не забудьте сохранить данные авторзации в хранилище:
    $data = $authAdapter->getResultRowObject(null, array('password_hash',
    'password_salt'));
    $auth->getStorage()->write($data);
Увековечим учетную запись
администратора
 ./application/configs/doctrine/data/fixtures/users.yml

 Administrator:
   Admin_1:
     login: stfalcon
     email: mymail@gmail.com
     password_hash: bcd3987603a947d54480285c16f06fde
     password_salt: fc1ed
 # Admin_2:
 #    login: stfalcon
 #    ...
Сделаем глобальный reload

 $ ./application/sripts/doctrine build-all-reload
 build-all-reload - Are you sure you wish to drop   your databases? (y/n)
 y
 build-all-reload - Successfully dropped database   for connection named
 'primary'
 build-all-reload - Generated models successfully   from YAML schema
 build-all-reload - Successfully created database   for connection named
 'primary'
 build-all-reload - Created tables successfully
 build-all-reload - Data was successfully loaded

 mysql> SELECT * FROM `administrators`;
 | id | login    | email            | password_hash
 | password_salt | created_at          | updated_at          |
 +----+----------+------------------+----------------------------------
 +---------------+---------------------+---------------------+
 | 1 | stfalcon | ceo@stfalcon.com | bcd3987603a947d54480285c16f06fde
 | fc1ed         | 2010-03-18 23:04:11 | 2010-03-18 23:04:11 |
Адаптер для Zend_Paginator

 Мы используем ZFEngine_Paginator_Adapter_Doctrine, это
 немного переработанный с учетом наших потребностей и
 изменений в Doctrine 1.2
 SmartL_Zend_Paginator_Adapter_Doctrine
 http://code.google.com/p/smart-framework/

 Ещё раз пропиарю наш ZFEngine :)
 http://zfengine.com
ZFEngine_Paginator_Adapter_Doctrine

   Давайте выведем список администраторов с постраничной
    навигацией. Для этого создадим в таблице administrators 10
    случайных записей:
Расширяем функционал
AdministratorTable

   Создадим метод getQueryToFetchAll(), который будет
    возвращать запрос на выборку всех администраторов:
    ./application/models/AdministratorTable.php
    <?php
    class AdministratorTable extends UserTable
    {
          /**
            * Query to fetch all administrators
            * @return Doctrine_Query
            */
          public function getQueryToFetchAll()
          {
                return $this->createQuery('a')
                    ->orderBy('a.created_at');
          }
    }
Работаем с пагинатором

 ./application/controllers/IndexController.php
 <?php
 class IndexController extends Zend_Controller_Action
 {
       public function indexAction()
       {
             $query = Doctrine_Core::getTable('Administrator')
                  ->getQueryToFetchAll();

         $paginator = new Zend_Paginator(
             new ZFEngine_Paginator_Adapter_Doctrine($query));
         $paginator->setCurrentPageNumber($this->_getParam('page', 1));
         $paginator->setItemCountPerPage(4);

         $this->view->paginator = $paginator;
     }
 }
Оформляем вывод списка в view
шаблоне
 ./application/views/scripts/index/index.phtml
 <h1>
       <?php echo $this->translate('Администраторы'); ?>:
 </h1>

 <?php if (count($this->paginator)): ?>
 <ul>
      <?php foreach ($this->paginator as $administrator): ?>
      <li>
           <?php echo $administrator->login; ?>&nbsp;
           &lt;<?php echo $administrator->email; ?>&gt;
      </li>
      <?php endforeach; ?>
 </ul>
 <?php endif; ?>

 <?php echo $this->paginationControl($this->paginator, 'Sliding',
 'digg.phtml'); ?>
digg.phtml

   digg.phtml я выложил здесь — http://pastie.org/832023 (за
    основу взят шаблон с ZendPaginationHelper)
Открываем страницу в браузере

                     И наслаждаемся
                      результатом :)
ZFEngine и использование Doctrine в
модульном ZF приложении

   Мы написали несколько тасков (собственно таски написал
    Валерий Рабиевский, а я только немного порефакторил)
    для Doctrine, которые позволяют генерировать модели и
    использовать механизм миграций в ZF проектах с
    модульной архитектурой.
   При этом между моделями разных модулей работает
    связывание и наследование.
   Также работает механизм миграций для проекта в целом.
Пример структуры модульного ZF
проекта
 ./application/
 |-- Bootstrap.php
 |-- configs
 |   `-- application.ini
 |-- layouts
 |   `-- scripts
 |       |-- admin.phtml
 |       `-- index.html
 `-- modules
     |-- products
     `-- users
Настройки для модульной структуры

   Прописываем следующие настройки в application.ini:
    ; Указываем, где находятся наши модули для Zend
    resources.frontController.moduleDirectory =
    APPLICATION_PATH "/modules"
    ; и для Doctrine_Cli
    doctrine_cli.modules_path = APPLICATION_PATH "/modules/"
    ; а также прописываем путь к папке, где будут хранится yaml-схемы
    предыдущих версий (old), и новые (temp), собранные с модулей в одну
    папку. Именно по различиям между ними и будут генерироваться миграции.
    doctrine_cli.old_schema_path = APPLICATION_PATH
    "/configs/doctrine/schema/old/"
    doctrine_cli.temp_schema_path = APPLICATION_PATH "/../tmp/schema/"
    resources.modules[] = "" ; подгружаем ресурс для подержки модулей

    ;   И убираем строки, где задавали расположение моделей:
    ;   resources.doctrine.manager.models_path = APPLICATION_PATH "/models"
    ;   doctrine_cli.models_path = APPLICATION_PATH "/models"
    ;   так как теперь модели подгружаются самим Zend'ом
Cтруктура модуля users

 ./application/modules/users/
 |-- Bootstrap.php
 |-- configs
 |   `-- doctrine
 |       |-- data
 |       |   |-- fixtures
 |       |   `-- sql
 |       `-- schema
 |           `-- User.yml
 |-- controllers
 |   `-- IndexController.php
 |-- models
 `-- views
     `-- scripts
         `-- index
             `-- index.phtml
Схема User.yml
## User schema
Users_Model_User:
  tableName: users
  options:
    type: INNODB
    collate: utf8_unicode_ci
    charset: utf8
  columns:
    id:
      type: integer(4)
      unsigned: true
      primary: true
      autoincrement: true
    login: string(32)
    email: string(255)
Cтруктура модуля products

 ./application/modules/products/
 |-- Bootstrap.php
 |-- configs
 |   |-- acl.php
 |   |-- doctrine
 |   |   `-- schema
 |   |       `-- Product.yml
 |   `-- routes.xml
 |-- controllers
 |   `-- IndexController.php
 |-- forms
 |-- models
 `-- views
     |-- helpers
     `-- scripts
         `-- index
             `-- index.phtml
Схема Product.yml

 ## Product schema
 Products_Model_Product:
   tableName: products
   options:
     ...
   columns:
     id:
       type: integer(4)
       unsigned: true
       primary: true
       autoincrement: true
     user_id:
       type: integer(4)
       unsigned: true
     name: string(255)
     description: string
   actAs: [Timestampable]
 ...
Схема Product.yml (продолжение)

 ...
 # Прописываем связь один-ко-многим
 # User и Products – алиасы, через которые мы сможем обращаться
 # из одной модели к другой
 relations:
     User:
       class: Users_Model_User
       foreign: id
       local: user_id
       foreignAlias: Products
       onUpdate: CASCADE
       onDelete: CASCADE
Новый скрипт для Doctrine_Cli

   Скрипт для работы с Doctrine_Cli в модульном ZF
    приложении лежит в репозитории ZFEngine.
   Единственное его отличие от обычного скрипта, это
    наличие кода для подключения тасков с ZFEngine и справка
    по командам ZFEngine при запуске скрипта с ключем info:
    $ ./application/scripts/doctrine info
    zfengine-generate-migrations-models -> для генерации новой миграций
    zfengine-generate-migrations-diff -> для генерации изменений миграций
    zfengine-generate-models-yaml -> для генерация моделей из yaml-файлов
    zfengine-prepare-schema-files-for-migrations -> для копирования shema-
    файлов для сравнения при генерации миграций
    Очередность действий:
     При создании новой миграции:
     zfengine-generate-models-yaml
     zfengine-generate-migrations-models
     migrate
     При создании изменений миграции: ...
Генерируем модели по YAML схемам

   Все также как в предыдущих примерах, только команда с
    префиксом zfengine:
    $ ./application/sripts/doctrine zfengine-generate-models-yaml
    Generated models for module "Products" successfully
    Generated models for module "Users" successfully
    Generated models finished
   Получаем готовые модели:
    ./application/modules/users/
    |-- models
        |-- Base
        |   `-- User.php
        |-- User.php
        `-- UserTable.php
    Только теперь модели именуются согласно стандартам ZF и подгружаются родным
    автозагрузчиком:
    BaseUser → Users_Model_Base_User
    User → Users_Model_User
Сгенерированые модели
 Между моделями из разных модулей сгенерировались связи:
 ./application/modules/users/models/Base/User.php
 <?php ...
       public function setUp() {
             $this->hasMany('Products_Model_Product as Products', array(
                  'local' => 'id', 'foreign' => 'user_id'));
       }
 ./application/modules/products/models/Base/Product.php
 <?php ...
       public function setUp() {
             $this->hasOne('Users_Model_User as User', array(
                  'local' => 'user_id','foreign' => 'id',
                  'onDelete' => 'CASCADE', 'onUpdate' => 'CASCADE'));
       }


 При работе с моделью пользователя коллекция моделей продуктов будет подгружена
 только при необходимости. Например при получении всех продуктов пользователя:
 $products = $user->Products;
Миграции

   Сгенерируем миграции:
    Первую миграцию (на новом проекте) делаем через:
    $ ./application/sripts/doctrine zfengine-generate-migrations-models
    Так миграции генерируются на основании существующих классов моделей, а
    последующие — уже на основании изменений в yaml-схемах командой:
    $ ./application/sripts/doctrine zfengine-generate-migrations-diff


   И накатываем миграции на базу:
    $ ./application/sripts/doctrine migrate
    migrate - migrated successfully to version #3
Структура таблицы `products`

   Смотрим, что получилось в БД:
    mysql> SHOW CREATE TABLE `products`;

    CREATE TABLE `products` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `user_id` int(10) unsigned DEFAULT NULL,
      `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
      `description` text COLLATE utf8_unicode_ci,
      `created_at` datetime NOT NULL,
      `updated_at` datetime NOT NULL,
      PRIMARY KEY (`id`),
      KEY `products_user_id_users_id` (`user_id`),
      CONSTRAINT `products_user_id_users_id` FOREIGN KEY (`user_id`)
    REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
На этом все ;)


 Благодарю за внимание! Задавайте
 вопросы.




                            Степан Танасийчук
                             ceo@stfalcon.com

More Related Content

What's hot

Введение в hibernate
Введение в hibernateВведение в hibernate
Введение в hibernateUnguryan Vitaliy
 
Meet Magento Belarus debug Pavel Novitsky (rus)
Meet Magento Belarus debug Pavel Novitsky (rus)Meet Magento Belarus debug Pavel Novitsky (rus)
Meet Magento Belarus debug Pavel Novitsky (rus)Pavel Novitsky
 
Лекция #6. Введение в Django web-framework
Лекция #6. Введение в Django web-frameworkЛекция #6. Введение в Django web-framework
Лекция #6. Введение в Django web-frameworkЯковенко Кирилл
 
Введение в Spring
Введение в SpringВведение в Spring
Введение в SpringUnguryan Vitaliy
 
Что нового в Visual Studio 2010 и .Net 4.0
Что нового в Visual Studio 2010 и .Net 4.0Что нового в Visual Studio 2010 и .Net 4.0
Что нового в Visual Studio 2010 и .Net 4.0akrakovetsky
 
Web осень 2013 лекция 4
Web осень 2013 лекция 4Web осень 2013 лекция 4
Web осень 2013 лекция 4Technopark
 
Web весна 2012 лекция 7
Web весна 2012 лекция 7Web весна 2012 лекция 7
Web весна 2012 лекция 7Technopark
 
Web осень 2013 лекция 9
Web осень 2013 лекция 9Web осень 2013 лекция 9
Web осень 2013 лекция 9Technopark
 
Архитектура. Доступноять программных систем.
Архитектура. Доступноять программных систем.Архитектура. Доступноять программных систем.
Архитектура. Доступноять программных систем.Dima Dzuba
 
Разработка расширяемых приложений на Django
Разработка расширяемых приложений на DjangoРазработка расширяемых приложений на Django
Разработка расширяемых приложений на DjangoMoscowDjango
 
Тестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиТестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиZestranec
 
Web осень 2013 лекция 2
Web осень 2013 лекция 2Web осень 2013 лекция 2
Web осень 2013 лекция 2Technopark
 
C++ Базовый. Занятие 15.
C++ Базовый. Занятие 15.C++ Базовый. Занятие 15.
C++ Базовый. Занятие 15.Igor Shkulipa
 
Pycon Russia 2013 - Разработка через тестирование в Python и Django
Pycon Russia 2013 - Разработка через тестирование в Python и DjangoPycon Russia 2013 - Разработка через тестирование в Python и Django
Pycon Russia 2013 - Разработка через тестирование в Python и DjangoIlya Shalyapin
 
Тестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиТестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиSQALab
 
C++ Базовый. Занятие 13.
C++ Базовый. Занятие 13.C++ Базовый. Занятие 13.
C++ Базовый. Занятие 13.Igor Shkulipa
 
Эффективное программирование на NodeJS
Эффективное программирование на NodeJSЭффективное программирование на NodeJS
Эффективное программирование на NodeJSYura Bogdanov
 

What's hot (17)

Введение в hibernate
Введение в hibernateВведение в hibernate
Введение в hibernate
 
Meet Magento Belarus debug Pavel Novitsky (rus)
Meet Magento Belarus debug Pavel Novitsky (rus)Meet Magento Belarus debug Pavel Novitsky (rus)
Meet Magento Belarus debug Pavel Novitsky (rus)
 
Лекция #6. Введение в Django web-framework
Лекция #6. Введение в Django web-frameworkЛекция #6. Введение в Django web-framework
Лекция #6. Введение в Django web-framework
 
Введение в Spring
Введение в SpringВведение в Spring
Введение в Spring
 
Что нового в Visual Studio 2010 и .Net 4.0
Что нового в Visual Studio 2010 и .Net 4.0Что нового в Visual Studio 2010 и .Net 4.0
Что нового в Visual Studio 2010 и .Net 4.0
 
Web осень 2013 лекция 4
Web осень 2013 лекция 4Web осень 2013 лекция 4
Web осень 2013 лекция 4
 
Web весна 2012 лекция 7
Web весна 2012 лекция 7Web весна 2012 лекция 7
Web весна 2012 лекция 7
 
Web осень 2013 лекция 9
Web осень 2013 лекция 9Web осень 2013 лекция 9
Web осень 2013 лекция 9
 
Архитектура. Доступноять программных систем.
Архитектура. Доступноять программных систем.Архитектура. Доступноять программных систем.
Архитектура. Доступноять программных систем.
 
Разработка расширяемых приложений на Django
Разработка расширяемых приложений на DjangoРазработка расширяемых приложений на Django
Разработка расширяемых приложений на Django
 
Тестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиТестирование программных фильтров безопасности
Тестирование программных фильтров безопасности
 
Web осень 2013 лекция 2
Web осень 2013 лекция 2Web осень 2013 лекция 2
Web осень 2013 лекция 2
 
C++ Базовый. Занятие 15.
C++ Базовый. Занятие 15.C++ Базовый. Занятие 15.
C++ Базовый. Занятие 15.
 
Pycon Russia 2013 - Разработка через тестирование в Python и Django
Pycon Russia 2013 - Разработка через тестирование в Python и DjangoPycon Russia 2013 - Разработка через тестирование в Python и Django
Pycon Russia 2013 - Разработка через тестирование в Python и Django
 
Тестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиТестирование программных фильтров безопасности
Тестирование программных фильтров безопасности
 
C++ Базовый. Занятие 13.
C++ Базовый. Занятие 13.C++ Базовый. Занятие 13.
C++ Базовый. Занятие 13.
 
Эффективное программирование на NodeJS
Эффективное программирование на NodeJSЭффективное программирование на NodeJS
Эффективное программирование на NodeJS
 

Similar to Zend Framework и Doctrine

Валерий Чугреев, ИСЭРТ РАН — Архитектура MVC в контексте web-разработки — про...
Валерий Чугреев, ИСЭРТ РАН — Архитектура MVC в контексте web-разработки — про...Валерий Чугреев, ИСЭРТ РАН — Архитектура MVC в контексте web-разработки — про...
Валерий Чугреев, ИСЭРТ РАН — Архитектура MVC в контексте web-разработки — про...Dev_Party
 
#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...
#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...
#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...JSib
 
Устройство фреймворка symfony 2 (http://frontend-dev.ru)
Устройство фреймворка symfony 2 (http://frontend-dev.ru)Устройство фреймворка symfony 2 (http://frontend-dev.ru)
Устройство фреймворка symfony 2 (http://frontend-dev.ru)Александр Егурцов
 
Как мы делаем модули PHP в Badoo – Антон Довгаль
Как мы делаем модули PHP в Badoo – Антон ДовгальКак мы делаем модули PHP в Badoo – Антон Довгаль
Как мы делаем модули PHP в Badoo – Антон ДовгальBadoo Development
 
Master class bars group ext js4
Master class bars group   ext js4Master class bars group   ext js4
Master class bars group ext js4Radik Fattakhov
 
Zend Framework и мультиязычность
Zend Framework и мультиязычностьZend Framework и мультиязычность
Zend Framework и мультиязычностьStepan Tanasiychuk
 
Django South. Миграция баз данных.
Django South. Миграция баз данных.  Django South. Миграция баз данных.
Django South. Миграция баз данных. MoscowDjango
 
Метапрограммирование с примерами на JavaScript
Метапрограммирование с примерами на JavaScriptМетапрограммирование с примерами на JavaScript
Метапрограммирование с примерами на JavaScriptTimur Shemsedinov
 
JavaScript-модули "из прошлого в будущее"
JavaScript-модули "из прошлого в будущее"JavaScript-модули "из прошлого в будущее"
JavaScript-модули "из прошлого в будущее"oelifantiev
 
Yii2
Yii2Yii2
Yii2Noveo
 
The Old New ASP.NET
The Old New ASP.NETThe Old New ASP.NET
The Old New ASP.NETVitaly Baum
 
django cheBit'11
django cheBit'11django cheBit'11
django cheBit'11dva
 
Чуть сложнее чем Singleton: аннотации, IOC, АОП
Чуть сложнее чем Singleton: аннотации, IOC, АОПЧуть сложнее чем Singleton: аннотации, IOC, АОП
Чуть сложнее чем Singleton: аннотации, IOC, АОПKirill Chebunin
 
Презентация «Bundle Transformer – инструмент для клиентской оптимизации в сре...
Презентация «Bundle Transformer – инструмент для клиентской оптимизации в сре...Презентация «Bundle Transformer – инструмент для клиентской оптимизации в сре...
Презентация «Bundle Transformer – инструмент для клиентской оптимизации в сре...Andrey Taritsyn
 
ASP.NET MVC - как построить по-настоящему гибкое веб-приложение
ASP.NET MVC - как построить по-настоящему гибкое веб-приложениеASP.NET MVC - как построить по-настоящему гибкое веб-приложение
ASP.NET MVC - как построить по-настоящему гибкое веб-приложениеAlexander Byndyu
 
Инсталляционные профили, создание сборок
Инсталляционные профили, создание сборокИнсталляционные профили, создание сборок
Инсталляционные профили, создание сборокAndrii Podanenko
 

Similar to Zend Framework и Doctrine (20)

Валерий Чугреев, ИСЭРТ РАН — Архитектура MVC в контексте web-разработки — про...
Валерий Чугреев, ИСЭРТ РАН — Архитектура MVC в контексте web-разработки — про...Валерий Чугреев, ИСЭРТ РАН — Архитектура MVC в контексте web-разработки — про...
Валерий Чугреев, ИСЭРТ РАН — Архитектура MVC в контексте web-разработки — про...
 
#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...
#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...
#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...
 
Устройство фреймворка symfony 2 (http://frontend-dev.ru)
Устройство фреймворка symfony 2 (http://frontend-dev.ru)Устройство фреймворка symfony 2 (http://frontend-dev.ru)
Устройство фреймворка symfony 2 (http://frontend-dev.ru)
 
Как мы делаем модули PHP в Badoo – Антон Довгаль
Как мы делаем модули PHP в Badoo – Антон ДовгальКак мы делаем модули PHP в Badoo – Антон Довгаль
Как мы делаем модули PHP в Badoo – Антон Довгаль
 
Master class bars group ext js4
Master class bars group   ext js4Master class bars group   ext js4
Master class bars group ext js4
 
Zend Framework и мультиязычность
Zend Framework и мультиязычностьZend Framework и мультиязычность
Zend Framework и мультиязычность
 
UWDC 2013, Yii2
UWDC 2013, Yii2UWDC 2013, Yii2
UWDC 2013, Yii2
 
Django South. Миграция баз данных.
Django South. Миграция баз данных.  Django South. Миграция баз данных.
Django South. Миграция баз данных.
 
Метапрограммирование с примерами на JavaScript
Метапрограммирование с примерами на JavaScriptМетапрограммирование с примерами на JavaScript
Метапрограммирование с примерами на JavaScript
 
JavaScript-модули "из прошлого в будущее"
JavaScript-модули "из прошлого в будущее"JavaScript-модули "из прошлого в будущее"
JavaScript-модули "из прошлого в будущее"
 
Yii2
Yii2Yii2
Yii2
 
The Old New ASP.NET
The Old New ASP.NETThe Old New ASP.NET
The Old New ASP.NET
 
django cheBit'11
django cheBit'11django cheBit'11
django cheBit'11
 
Чуть сложнее чем Singleton: аннотации, IOC, АОП
Чуть сложнее чем Singleton: аннотации, IOC, АОПЧуть сложнее чем Singleton: аннотации, IOC, АОП
Чуть сложнее чем Singleton: аннотации, IOC, АОП
 
Презентация «Bundle Transformer – инструмент для клиентской оптимизации в сре...
Презентация «Bundle Transformer – инструмент для клиентской оптимизации в сре...Презентация «Bundle Transformer – инструмент для клиентской оптимизации в сре...
Презентация «Bundle Transformer – инструмент для клиентской оптимизации в сре...
 
ASP.NET MVC - как построить по-настоящему гибкое веб-приложение
ASP.NET MVC - как построить по-настоящему гибкое веб-приложениеASP.NET MVC - как построить по-настоящему гибкое веб-приложение
ASP.NET MVC - как построить по-настоящему гибкое веб-приложение
 
php frameworks
php frameworksphp frameworks
php frameworks
 
Symfony 3
Symfony 3Symfony 3
Symfony 3
 
бегун
бегунбегун
бегун
 
Инсталляционные профили, создание сборок
Инсталляционные профили, создание сборокИнсталляционные профили, создание сборок
Инсталляционные профили, создание сборок
 

Zend Framework и Doctrine

  • 1. ZFConf 2010 Zend Framework и Doctrine Степан Танасийчук ceo@stfalcon.com
  • 2. Чем я занимаюсь?  Web разработкой занялся в 2003 году  С Zend Framework начал работать в 2008 году  Руковожу собственной веб- студией с 2009 года  Активный участник сообщества zendframework.ru  Люблю прикольные смайлы :]
  • 3. Содержание доклада  Подключение Doctrine к ZF проекту  Скрипт для работы с Doctrine_Cli  Генерация моделей по YAML схемам  Механизм миграций  Наследование в моделях  Шаблоны расширений  Адаптер для Zend_Auth  Адаптер для Zend_Paginator  ZFEngine и использование Doctrine в модульном ZF приложении
  • 4. Несколько слов о Doctrine  ORM библиотека для PHP 5.2.3+  Использует паттерны Active Record, Data Mapper и Metadata Mapping  Собственный язык запросов — DQL (по мотивам HQL)  Связи один-к-одному, один-ко-многим и многие-к-многим  Автогенерация моделей по yaml схемам  Экспорт и импорт из/в yaml  Механизм миграций  Шаблоны поведений (l18n, Versionable, NestedSet, etc.)
  • 5. Подключаем Doctrine к ZF проекту  Размещаем Doctrine в library/Doctrine: $ svn export http://svn.doctrine- project.org/tags/1.2.1/lib/Doctrine/ ./library/Doctrine  Прописываем следующие настройки в application.ini: autoloadernamespaces[] = "Doctrine"
  • 6. Parables_Application_Resource_Doctrine Matthew Lurz добавил в Zend Framework proposal application-ресурс для подключения Doctrine. Его класс называется Parables_Application_Resource_Doctrine и лежит здесь http://github.com/mlurz71/parables
  • 7. ZFEngine_Application_Resource_Doctrine Мы немного изменили код Parables_Application_Resource_Doctrine для работы с Doctrine 1.2.x и храним его в репозитории ZFEngine как ZFEngine_Application_Resource_Doctrine ZFEngine это сборная солянка классов, которые мы используем при разработке проектов на ZF. Лежит все здесь: http://zfengine.com В основном код наш. Также есть чужой, но с некоторыми изменениями. Надеюсь, что это все в рамках закона ^_~.
  • 8. Подключаем ZFEngine к ZF проекту  Размещаем ZFEngine в library/ZFEngine: $ svn export http://svn2.assembla.com/svn/zfengine/trunk/library/ZFEngine/ ./library/ZFEngine  Прописываем следующие настройки в application.ini: autoloadernamespaces[] = "ZFEngine" pluginPaths.ZFEngine_Application_Resource = "ZFEngine/Application/Resource"
  • 9. Настраиваем подключение к БД resources.doctrine.connections.primary.dsn.adapter = "mysql" resources.doctrine.connections.primary.dsn.username = "root" resources.doctrine.connections.primary.dsn.password = "******" resources.doctrine.connections.primary.dsn.host = "localhost" resources.doctrine.connections.primary.dsn.dbname = "zfconf" resources.doctrine.connections.primary.options.charset = "utf8" resources.doctrine.connections.primary.options.collate = "utf8_unicode_ci"
  • 10. Настраиваем Doctrine_Manager resources.doctrine.manager.attributes.attr_autoload_table_classes = 1 resources.doctrine.manager.attributes.attr_use_native_enum = 1 resources.doctrine.manager.attributes.attr_quote_identifier = 1 resources.doctrine.manager.attributes.attr_auto_free_query_objects = 1 resources.doctrine.manager.attributes.attr_auto_accessor_override = 1 resources.doctrine.manager.attributes.attr_model_loading = "model_loading_conservative"
  • 11. MODEL_LOADING_PEAR В Doctrine 1.2 появился новый режим для автозагрузки моделей — MODEL_LOADING_PEAR, но при использовании этого режима не работает generate- migration-diff :(. Я заметил это уже в процессе подготовки доклада и пока просто написал в багрепорт Doctrine.
  • 12. Для проектов с НЕмодульной структурой  Указываем путь к директории с моделями: resources.doctrine.manager.models_path = APPLICATION_PATH "/models"
  • 13. Настраиваем кеширование  resources.doctrine.manager.* .attributes.attr_result_cache.driver = "memcache" .attributes.attr_result_cache.lifespan = 3600 .attributes.attr_result_cache.options.servers.host = "localhost" .attributes.attr_result_cache.options.servers.port = 11211 .attributes.attr_result_cache.options.servers.persistent = 1 .attributes.attr_result_cache.options.compression = 0
  • 14. Настраиваем Doctrine_Cli doctrine_cli.data_fixtures_path = APPLICATION_PATH "/configs/doctrine/data/fixtures" doctrine_cli.models_path = APPLICATION_PATH "/models" doctrine_cli.migrations_path = APPLICATION_PATH "/configs/doctrine/migrations" doctrine_cli.sql_path = APPLICATION_PATH "/configs/doctrine/data/sql" doctrine_cli.yaml_schema_path = APPLICATION_PATH "/configs/doctrine/schema" doctrine_cli.generate_models_options.generateBaseClasses = 1 doctrine_cli.generate_models_options.baseClassesDirectory = "Base" doctrine_cli.generate_models_options.generateTableClasses = 1
  • 15. Cкрипт для работы с Doctrine_Cli ./application/sripts/common.php <?php define('APPLICATION_ENV', 'development'); define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/..')); set_include_path(implode(PATH_SEPARATOR, array( realpath(APPLICATION_PATH . '/../library'), get_include_path(), )));
  • 16. Cкрипт для работы с Doctrine_Cli ./application/sripts/doctrine #!/usr/bin/env php <?php require_once 'common.php'; require_once 'Zend/Application.php'; $application = new Zend_Application( APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini' ); $application->getBootstrap() ->bootstrap(); $cli = new Doctrine_Cli($application->getOption('doctrine_cli')); $cli->run($_SERVER['argv']);
  • 17. Проверяем как работает  Запускаем скрипт без параметров: $ ./application/sripts/doctrine Doctrine Command Line Interface ./application/sripts/doctrine generate-sql ./application/sripts/doctrine create-db ./application/sripts/doctrine generate-yaml-models ./application/sripts/doctrine dql ./application/sripts/doctrine generate-migrations-models ./application/sripts/doctrine generate-yaml-db ./application/sripts/doctrine generate-models-yaml ./application/sripts/doctrine generate-migrations-diff ./application/sripts/doctrine generate-migration ./application/sripts/doctrine create-tables ./application/sripts/doctrine drop-db ./application/sripts/doctrine generate-migrations-db ... и ещё 9ть команд, которые не поместились на этом слайде (:
  • 18. Создадим схему модели User ./application/configs/doctrine/schema/User.yml User: tableName: users options: type: INNODB collate: utf8_unicode_ci charset: utf8 columns: id: type: integer(4) primary: true autoincrement: true login: string(32) email: string(255)
  • 19. Генерируем модели по YAML схемам  Запускаем скрипт с параметром generate-models-yaml: $ ./application/sripts/doctrine generate-models-yaml generate-models-yaml - Generated models successfully from YAML schema  Получаем готовые модели: ./application/models |-- Base | `-- BaseUser.php |-- User.php `-- UserTable.php Важная деталь: сами YAML схемы можно сгенерировать непосредственно с структуры БД используя команду generate-yaml-db.
  • 20. Сгенерированный код базовой модели User ./application/models/Base/BaseUser.php <?php abstract class BaseUser extends Doctrine_Record { public function setTableDefinition() { $this->setTableName('users'); $this->hasColumn('id', 'integer', 4, array('type' => 'integer', 'unsigned' => true, 'primary' => true, 'autoincrement' => true, 'length' => '4')); // Здесь было описание полей login и email ... $this->option('type', 'INNODB'); $this->option('collate', 'utf8_unicode_ci'); $this->option('charset', 'utf8'); } public function setUp() { parent::setUp(); } }
  • 21. Сгенерированный код модели User и маппера UserTable ./application/models/User.php <?php class User extends BaseUser { } ./application/models/UserTable.php <?php class UserTable extends Doctrine_Table { }
  • 22. Напишем свой сеттер для поля email ./application/models/User.php <?php /** * User model */ class User extends BaseUser { /** * Set email adress into lowercase * * @param string $email * @return void */ public function setEmail($email) { $this->_set('email', strtolower($email)); } }
  • 23. Пишем экшн для проверки работы ./application/controllers/IndexController.php <?php class IndexController extends Zend_Controller_Action { /** * Simple action * * @return void */ public function indexAction() { $user = new User(); $user->login = 'stfalcon'; $user->email = 'CEO@STFalcon.COM'; Zend_Debug::dump($user->toArray()); } }
  • 24. Запускаем в браузере array 'id' => null 'login' => string 'stfalcon' (length=8) 'email' => string 'ceo@stfalcon.com' (length=16)
  • 25. Миграции  Сгенерируем первый класс миграций. Его можно генерировать из классов моделей или БД (см. мануал к Doctrine). $ ./application/sripts/doctrine generate-migrations-models generate-migrations-models - Generated migration classes successfully from models  Получаем готовую модель миграций: ./application/configs/doctrine/ |-- data | |-- fixtures | `-- sql |-- migrations | `-- 1268942153_adduser.php `-- schema `-- User.yml
  • 26. Сгенерированный код первой модели миграций ./application/configs/doctrine/migrations/1268942153_adduser.php <?php class Adduser extends Doctrine_Migration_Base { public function up() { $this->createTable('user', array('id' => array('type' => 'integer', 'unsigned' => true, 'primary' => true, 'autoincrement' => true, 'length' => 4), // Здесь были параметры для создания полей login и email ... ), array('type' => 'INNODB', 'indexes' => array(), 'primary' => array(0 => 'id'), 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8')); } public function down() { $this->dropTable('user'); } }
  • 27. Создадим БД и накатим на неё наши изменения  Создаем БД (например на production сервере): mysql> CREATE DATABASE `zfconf`; Query OK, 1 row affected (0,00 sec)  Накатываем на неё миграцию: $ ./application/sripts/doctrine migrate migrate - migrated successfully to version #1  Выведем список таблиц: mysql> SHOW TABLES; migration_version users
  • 28. Проверяем работу скрипта  Структура таблицы в которой хранится номер миграции: mysql> SHOW CREATE TABLE `migration_version`; CREATE TABLE `migration_version` ( `version` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1  Структура таблицы пользователей: mysql> SHOW CREATE TABLE `users`; CREATE TABLE `users` ( `id` int(10) NOT NULL AUTO_INCREMENT, `login` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL, `email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
  • 29. Наследование в YAML схемах ./application/configs/doctrine/schema/Administrator.yml ## Administrator schema Administrator: tableName: administrators inheritance: extends: User type: concrete columns: password_hash: string(32) password_salt: string(8) actAs: [Timestampable]
  • 30. Работаем с Doctrine_Cli  В первую очередь делаем migration-diff — он генерирует классы миграций на основе различий между кодом моделей и YAML схемами: $ ./application/sripts/doctrine generate-migrations-diff generate-migrations-diff - Generated migration classes successfully from difference ./application/configs/doctrine |-- data | |-- fixtures | `-- sql |-- migrations | |-- 1268942153_adduser.php | `-- 1268942505_version2.php `-- schema |-- Administrator.yml `-- User.yml
  • 31. Работаем с Doctrine_Cli  Генерируем код моделей: $ ./application/sripts/doctrine generate-models-yaml generate-models-yaml - Generated models successfully from YAML schema  Накатываем изменения на БД: $ ./application/sripts/doctrine migrate migrate - migrated successfully to version #2
  • 32. Работаем с Doctrine_Cli  Смотрим, что получилось: mysql> SHOW CREATE TABLE `administrators`; CREATE TABLE `administrators` ( `id` int(10) NOT NULL AUTO_INCREMENT, `login` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL, `email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `password_hash` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL, `password_salt` varchar(8) COLLATE utf8_unicode_ci DEFAULT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
  • 33. По-моему пора сделать авторизацию  Сначала напишем сеттер для password: ./application/models/Administrator.php <?php class Administrator extends BaseAdministrator { // Здесь был phpDoc блок ... public function setPassword($password) { if (strlen($password)) { $passwordSalt = substr(md5(mktime()), 0, rand(5,8)); $passwordHash = md5($password . $passwordSalt); $this->_set('password_hash', $passwordHash); $this->_set('password_salt', $passwordSalt); } } }
  • 34. Сгенерируем аккаунт для админа и сохраним его в БД ./application/controllers/IndexController.php <?php class IndexController extends Zend_Controller_Action { // Здесь был phpDoc блок ... public function indexAction() { $administrator = new Administrator(); $administrator->email = 'CEO@STFalcon.COM'; $administrator->login = 'stfalcon'; $administrator->password = 'qwerty'; $administrator->save(); Zend_Debug::dump($administrator->toArray()); } }
  • 35. Проверяем содержимое таблицы administrators mysql> SELECT * FROM `administrators`; +----+----------+------------------+---------------------------------- +---------------+---------------------+---------------------+ | id | login | email | password_hash | password_salt | created_at | updated_at | +----+----------+------------------+---------------------------------- +---------------+---------------------+---------------------+ | 1 | stfalcon | ceo@stfalcon.com | bcd3987603a947d54480285c16f06fde | fc1ed | 2010-03-18 23:04:11 | 2010-03-18 23:04:11 |
  • 36. ZendX_Doctrine_Auth_Adapter ./application/controllers/IndexController.php public function indexAction() { $authAdapter = new ZendX_Doctrine_Auth_Adapter( Doctrine_Core::getConnectionByTableName('Administrator')); $authAdapter->setTableName('Administrator a') ->setIdentityColumn('a.login') ->setCredentialColumn('a.password_hash') ->setCredentialTreatment('MD5(CONCAT(?,a.password_salt))') ->setIdentity('stfalcon')->setCredential('qwerty'); $auth = Zend_Auth::getInstance(); $result = $auth->authenticate($authAdapter); if ($result->isValid()) { echo '<h1>OK</h1>'; } else { echo '<h1>FAIL</h1>'; } }
  • 37. Открываем страницу в браузере  Все ОК :)  И не забудьте сохранить данные авторзации в хранилище: $data = $authAdapter->getResultRowObject(null, array('password_hash', 'password_salt')); $auth->getStorage()->write($data);
  • 38. Увековечим учетную запись администратора ./application/configs/doctrine/data/fixtures/users.yml Administrator: Admin_1: login: stfalcon email: mymail@gmail.com password_hash: bcd3987603a947d54480285c16f06fde password_salt: fc1ed # Admin_2: # login: stfalcon # ...
  • 39. Сделаем глобальный reload $ ./application/sripts/doctrine build-all-reload build-all-reload - Are you sure you wish to drop your databases? (y/n) y build-all-reload - Successfully dropped database for connection named 'primary' build-all-reload - Generated models successfully from YAML schema build-all-reload - Successfully created database for connection named 'primary' build-all-reload - Created tables successfully build-all-reload - Data was successfully loaded mysql> SELECT * FROM `administrators`; | id | login | email | password_hash | password_salt | created_at | updated_at | +----+----------+------------------+---------------------------------- +---------------+---------------------+---------------------+ | 1 | stfalcon | ceo@stfalcon.com | bcd3987603a947d54480285c16f06fde | fc1ed | 2010-03-18 23:04:11 | 2010-03-18 23:04:11 |
  • 40. Адаптер для Zend_Paginator Мы используем ZFEngine_Paginator_Adapter_Doctrine, это немного переработанный с учетом наших потребностей и изменений в Doctrine 1.2 SmartL_Zend_Paginator_Adapter_Doctrine http://code.google.com/p/smart-framework/ Ещё раз пропиарю наш ZFEngine :) http://zfengine.com
  • 41. ZFEngine_Paginator_Adapter_Doctrine  Давайте выведем список администраторов с постраничной навигацией. Для этого создадим в таблице administrators 10 случайных записей:
  • 42. Расширяем функционал AdministratorTable  Создадим метод getQueryToFetchAll(), который будет возвращать запрос на выборку всех администраторов: ./application/models/AdministratorTable.php <?php class AdministratorTable extends UserTable { /** * Query to fetch all administrators * @return Doctrine_Query */ public function getQueryToFetchAll() { return $this->createQuery('a') ->orderBy('a.created_at'); } }
  • 43. Работаем с пагинатором ./application/controllers/IndexController.php <?php class IndexController extends Zend_Controller_Action { public function indexAction() { $query = Doctrine_Core::getTable('Administrator') ->getQueryToFetchAll(); $paginator = new Zend_Paginator( new ZFEngine_Paginator_Adapter_Doctrine($query)); $paginator->setCurrentPageNumber($this->_getParam('page', 1)); $paginator->setItemCountPerPage(4); $this->view->paginator = $paginator; } }
  • 44. Оформляем вывод списка в view шаблоне ./application/views/scripts/index/index.phtml <h1> <?php echo $this->translate('Администраторы'); ?>: </h1> <?php if (count($this->paginator)): ?> <ul> <?php foreach ($this->paginator as $administrator): ?> <li> <?php echo $administrator->login; ?>&nbsp; &lt;<?php echo $administrator->email; ?>&gt; </li> <?php endforeach; ?> </ul> <?php endif; ?> <?php echo $this->paginationControl($this->paginator, 'Sliding', 'digg.phtml'); ?>
  • 45. digg.phtml  digg.phtml я выложил здесь — http://pastie.org/832023 (за основу взят шаблон с ZendPaginationHelper)
  • 46. Открываем страницу в браузере  И наслаждаемся результатом :)
  • 47. ZFEngine и использование Doctrine в модульном ZF приложении  Мы написали несколько тасков (собственно таски написал Валерий Рабиевский, а я только немного порефакторил) для Doctrine, которые позволяют генерировать модели и использовать механизм миграций в ZF проектах с модульной архитектурой.  При этом между моделями разных модулей работает связывание и наследование.  Также работает механизм миграций для проекта в целом.
  • 48. Пример структуры модульного ZF проекта ./application/ |-- Bootstrap.php |-- configs | `-- application.ini |-- layouts | `-- scripts | |-- admin.phtml | `-- index.html `-- modules |-- products `-- users
  • 49. Настройки для модульной структуры  Прописываем следующие настройки в application.ini: ; Указываем, где находятся наши модули для Zend resources.frontController.moduleDirectory = APPLICATION_PATH "/modules" ; и для Doctrine_Cli doctrine_cli.modules_path = APPLICATION_PATH "/modules/" ; а также прописываем путь к папке, где будут хранится yaml-схемы предыдущих версий (old), и новые (temp), собранные с модулей в одну папку. Именно по различиям между ними и будут генерироваться миграции. doctrine_cli.old_schema_path = APPLICATION_PATH "/configs/doctrine/schema/old/" doctrine_cli.temp_schema_path = APPLICATION_PATH "/../tmp/schema/" resources.modules[] = "" ; подгружаем ресурс для подержки модулей ; И убираем строки, где задавали расположение моделей: ; resources.doctrine.manager.models_path = APPLICATION_PATH "/models" ; doctrine_cli.models_path = APPLICATION_PATH "/models" ; так как теперь модели подгружаются самим Zend'ом
  • 50. Cтруктура модуля users ./application/modules/users/ |-- Bootstrap.php |-- configs | `-- doctrine | |-- data | | |-- fixtures | | `-- sql | `-- schema | `-- User.yml |-- controllers | `-- IndexController.php |-- models `-- views `-- scripts `-- index `-- index.phtml
  • 51. Схема User.yml ## User schema Users_Model_User: tableName: users options: type: INNODB collate: utf8_unicode_ci charset: utf8 columns: id: type: integer(4) unsigned: true primary: true autoincrement: true login: string(32) email: string(255)
  • 52. Cтруктура модуля products ./application/modules/products/ |-- Bootstrap.php |-- configs | |-- acl.php | |-- doctrine | | `-- schema | | `-- Product.yml | `-- routes.xml |-- controllers | `-- IndexController.php |-- forms |-- models `-- views |-- helpers `-- scripts `-- index `-- index.phtml
  • 53. Схема Product.yml ## Product schema Products_Model_Product: tableName: products options: ... columns: id: type: integer(4) unsigned: true primary: true autoincrement: true user_id: type: integer(4) unsigned: true name: string(255) description: string actAs: [Timestampable] ...
  • 54. Схема Product.yml (продолжение) ... # Прописываем связь один-ко-многим # User и Products – алиасы, через которые мы сможем обращаться # из одной модели к другой relations: User: class: Users_Model_User foreign: id local: user_id foreignAlias: Products onUpdate: CASCADE onDelete: CASCADE
  • 55. Новый скрипт для Doctrine_Cli  Скрипт для работы с Doctrine_Cli в модульном ZF приложении лежит в репозитории ZFEngine.  Единственное его отличие от обычного скрипта, это наличие кода для подключения тасков с ZFEngine и справка по командам ZFEngine при запуске скрипта с ключем info: $ ./application/scripts/doctrine info zfengine-generate-migrations-models -> для генерации новой миграций zfengine-generate-migrations-diff -> для генерации изменений миграций zfengine-generate-models-yaml -> для генерация моделей из yaml-файлов zfengine-prepare-schema-files-for-migrations -> для копирования shema- файлов для сравнения при генерации миграций Очередность действий: При создании новой миграции: zfengine-generate-models-yaml zfengine-generate-migrations-models migrate При создании изменений миграции: ...
  • 56. Генерируем модели по YAML схемам  Все также как в предыдущих примерах, только команда с префиксом zfengine: $ ./application/sripts/doctrine zfengine-generate-models-yaml Generated models for module "Products" successfully Generated models for module "Users" successfully Generated models finished  Получаем готовые модели: ./application/modules/users/ |-- models |-- Base | `-- User.php |-- User.php `-- UserTable.php Только теперь модели именуются согласно стандартам ZF и подгружаются родным автозагрузчиком: BaseUser → Users_Model_Base_User User → Users_Model_User
  • 57. Сгенерированые модели Между моделями из разных модулей сгенерировались связи: ./application/modules/users/models/Base/User.php <?php ... public function setUp() { $this->hasMany('Products_Model_Product as Products', array( 'local' => 'id', 'foreign' => 'user_id')); } ./application/modules/products/models/Base/Product.php <?php ... public function setUp() { $this->hasOne('Users_Model_User as User', array( 'local' => 'user_id','foreign' => 'id', 'onDelete' => 'CASCADE', 'onUpdate' => 'CASCADE')); } При работе с моделью пользователя коллекция моделей продуктов будет подгружена только при необходимости. Например при получении всех продуктов пользователя: $products = $user->Products;
  • 58. Миграции  Сгенерируем миграции: Первую миграцию (на новом проекте) делаем через: $ ./application/sripts/doctrine zfengine-generate-migrations-models Так миграции генерируются на основании существующих классов моделей, а последующие — уже на основании изменений в yaml-схемах командой: $ ./application/sripts/doctrine zfengine-generate-migrations-diff  И накатываем миграции на базу: $ ./application/sripts/doctrine migrate migrate - migrated successfully to version #3
  • 59. Структура таблицы `products`  Смотрим, что получилось в БД: mysql> SHOW CREATE TABLE `products`; CREATE TABLE `products` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned DEFAULT NULL, `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `description` text COLLATE utf8_unicode_ci, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (`id`), KEY `products_user_id_users_id` (`user_id`), CONSTRAINT `products_user_id_users_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
  • 60. На этом все ;) Благодарю за внимание! Задавайте вопросы. Степан Танасийчук ceo@stfalcon.com