Zend Framework и Doctrine
Upcoming SlideShare
Loading in...5
×
 

Zend Framework и Doctrine

on

  • 7,498 views

 

Statistics

Views

Total Views
7,498
Views on SlideShare
6,785
Embed Views
713

Actions

Likes
3
Downloads
49
Comments
0

6 Embeds 713

http://blog.stfalcon.com 667
http://www.slideshare.net 41
https://www.linkedin.com 2
http://feeds.feedburner.com 1
http://translate.googleusercontent.com 1
http://www.linkedin.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Zend Framework и Doctrine Zend Framework и Doctrine Presentation Transcript

    • 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