Akrabat_Db_Schema_Manager Миграция базы данных в Zend Framework


Содержание

База данных: Миграции (Database: Migrations)

Введение (Introduction)

Миграции похожи на систему контроля версиий, но только для базы данных (БД). Они позволяют команде разработчиков легко изменять схему БД приложения и делиться этими изменениями. Миграции обычно связаны с построителем схем Laravel (Laravel’s schema builder) для облегчения создания схемы БД приложения.

Laravel Schema facade предоставляет независимую от БД поддержку создания и управления таблицами. Она предоставляет выразительный API для всех поддерживаемых Laravel БД.

Создание (Generating Migrations)

Для создания миграции используйте make:migration Artisan command:

Миграция будет помещена в папку database/migrations . Каждое имя файла миграции содержит метку времени, которая позволяет Laravel определять порядок применения миграций.

Опции —table и —create служат для задания имени таблицы и указания будет ли миграция создавать новую таблицу. Проще говоря, эти опции служат для предварительного заполнения генерируемого файла миграции:

Для определения иного пути для сохранения файла миграции служит опция —path . Путь должен задаваться относительно корня приложения.

Структура миграции (Migration Structure)

Класс миграции содержит 2 метода: up и down . up нужен для добавления новых таблиц, столбцов или индексов в БД, тогда как down просто отменяет операции, выполненные в методе up .

Внутри обоих методов вы можете использовать конструктор схем для создания и модификации таблиц. Для изучения всех методов, доступных в конструкторе, обратитесь к документации check out its documentation. Давайте взглянем на пример миграции, которая создает таблицу flights :

Запуск миграций (Running Migrations)

Для запуска всех еще незапускавшихся миграций используйте команду migrate . Если вы используете Homestead virtual machine, то вы должны запустить ее на вашей VM:

Если вы получили сообщение об ошибке «class not found» во время запуска, попробуйте выполнить команду composer dump-autoload и потом вновь выполнить миграцию.

Принудительные миграции на продакшене (Forcing Migrations To Run In Production)

Некоторые операции миграции могут привести к потере данных. В целях защиты от запуска таких команд у вас будет запрашиваться подтверждение перед их выполнением. Для выполнения команд без подтверждения укажите флаг force :

Откат миграций (Rolling Back Migrations)

Для отката последней выполненной миграции используйте команду rollback . Обратите внимание, что откат отменит операции, выполненные последним запущенным файлом миграции, который может подключать другие файлы миграции:

Команда migrate:reset отменит все миграции:

Откат/запуск миграции одной командой (Rollback / Migrate In Single Command)

Команда migrate:refresh выполняет откат всех миграций, а затем запускает команду migrate . Команда полезна для пересоздания всей БД:

Написание миграций (Writing Migrations)

Создание таблиц (Creating Tables)

Метод Schema::create служит для создания таблицы. Принимает 2 аргумента. Первый — имя таблицы, второй — замыкание, которое в качестве аргумента принимает объект Blueprint , используемый для определения новой таблицы:

Для определения столбцов таблицы вы можете использовать любые методы конструктора схем column methods.

Проверка существования таблицы/столбца (Checking For Table / Column Existence)

Проверить существует ли таблица или колонка в таблице можно методами hasTable и hasColumn :

Подключение и Движок Хранилища (Connection & Storage Engine)

Если требуется использовать подключение, отличное от дефолтного, используйте метод connection :

Для установки движка таблицы, задайте свойство engine :

Переименование/удаление таблиц (Renaming / Dropping Tables)

Создание столбцов (Creating Columns)

Для изменения существующей таблицы служит метод Schema::table . Подобно методу create метод table принимает два аргумента: имя таблицы и замыкание, которое получает в качестве аргумента экземпляр класса Blueprint . Добавление столбца в таблицу:

Доступные типы столбцов (Available Column Types)

Перечень доступных типов в классе конструкторе схемы:

Команда Описание
$table->bigIncrements(‘id’); Incrementing ID (primary key) using a «UNSIGNED BIG INTEGER» equivalent.
$table->bigInteger(‘votes’); BIGINT equivalent for the database.
$table->binary(‘data’); BLOB equivalent for the database.
$table->boolean(‘confirmed’); BOOLEAN equivalent for the database.
$table->char(‘name’, 4); CHAR equivalent with a length.
$table->date(‘created_at’); DATE equivalent for the database.
$table->dateTime(‘created_at’); DATETIME equivalent for the database.
$table->decimal(‘amount’, 5, 2); DECIMAL equivalent with a precision and scale.
$table->double(‘column’, 15, 8); DOUBLE equivalent with precision, 15 digits in total and 8 after the decimal point.
$table->enum(‘choices’, [‘foo’, ‘bar’]); ENUM equivalent for the database.
$table->float(‘amount’); FLOAT equivalent for the database.
$table->increments(‘id’); Incrementing ID (primary key) using a «UNSIGNED INTEGER» equivalent.
$table->integer(‘votes’); INTEGER equivalent for the database.
$table->json(‘options’); JSON equivalent for the database.
$table->jsonb(‘options’); JSONB equivalent for the database.
$table->longText(‘description’); LONGTEXT equivalent for the database.
$table->mediumInteger(‘numbers’); MEDIUMINT equivalent for the database.
$table->mediumText(‘description’); MEDIUMTEXT equivalent for the database.
$table->morphs(‘taggable’); Adds INTEGER taggable_id and STRING taggable_type .
$table->nullableTimestamps(); Same as timestamps() , except allows NULLs.
$table->rememberToken(); Adds remember_token as VARCHAR(100) NULL.
$table->smallInteger(‘votes’); SMALLINT equivalent for the database.
$table->softDeletes(); Adds deleted_at column for soft deletes.
$table->string(’email’); VARCHAR equivalent column.
$table->string(‘name’, 100); VARCHAR equivalent with a length.
$table->text(‘description’); TEXT equivalent for the database.
$table->time(‘sunrise’); TIME equivalent for the database.
$table->tinyInteger(‘numbers’); TINYINT equivalent for the database.
$table->timestamp(‘added_on’); TIMESTAMP equivalent for the database.
$table->timestamps(); Adds created_at and updated_at columns.
$table->uuid(‘id’); UUID equivalent for the database.

Модификация столбцов (Column Modifiers)

В дополнение к типам, перечисленным выше, доступны модификаторы столбцов, которые можно использовать при добавлении столбца. Добавим столбцу возможность принимать значения NULL:

Ниже приведен список доступных модификаторов. Список не включает индексные модификаторы index modifiers:

Модификатор Описание
->first() Place the column «first» in the table (MySQL Only)
->after(‘column’) Place the column «after» another column (MySQL Only)
->nullable() Allow NULL values to be inserted into the column
->default($value) Specify a «default» value for the column
->unsigned() Set integer columns to UNSIGNED

Изменение столбцов (Modifying Columns)

Необходимые условия (Prerequisites)

Перед изменением столбца убедитесь, что в файле composer.json есть зависимость doctrine/dbal . Библиотека Doctrine DBAL нужна для определения текущего статуса столбца и создания SQL запросов для выполнения заданных изменений столбца.

Обновление атрибутов столбца (Updating Column Attributes)

Метод change меняет тип столбца либо атрибуты. Например, увеличим размер строкового столбца name с 25 до 50:

Добавить вставку значений NULL:

Примечание: Изменение столбцов с типом enum на текущий момент не поддерживается.

Переименование столбцов (Renaming Columns)

Примечание: Переименование столбцов типа enum на текущий момент не поддерживается.

Удаление столбцов (Dropping Columns)

Удаление нескольких столбцов:

Примечание: Перед удалением столбцов из СУБД SQLite убедитесь, что у вас установлена doctrine/dbal . Если нет, то пропишите ее в файле composer.json и выполните команду composer update .

Примечание: Удаление/изменение нескольких столбцов внутри одной миграции для СУБД SQLite не поддерживается.

Создание индексов (Creating Indexes)

Создание индекса для существующего столбца:

Индекс на основе нескольких столбцов:

Laravel автоматически генерирует имя индекса, но можно указать его самому во втором параметре метода:

Доступные типа индексов (Available Index Types)

Команда Описание
$table->primary(‘id’); Add a primary key.
$table->primary([‘first’, ‘last’]); Add composite keys.
$table->unique(’email’); Add a unique index.
$table->unique(‘state’, ‘my_index_name’); Add a custom index name.
$table->index(‘state’); Add a basic index.

Удаление индексов (Dropping Indexes)

Для удаление индекса нужно указать имя индекса. По умолчанию, Laravel создает имя для индекса, соединяя имя таблицы, имя столбца и тип индекса. Несколько примеров:


Команда Описание
$table->dropPrimary(‘users_id_primary’); Удалить primary key из таблицы «users».
$table->dropUnique(‘users_email_unique’); Удалить уникальный индекс из таблицы «users».
$table->dropIndex(‘geo_state_index’); Удалить обычный индекс из таблицы «geo».

Если методу для удаления передать массив столбцов для удаления индекса, то имя индекса будет сформировано автоматически:

Ограничения внешнего ключа (Foreign Key Constraints)

Laravel поддерживает создание внешних ключей, которые служат для обеспечения целостности данных на уровне БД. Например, определим столбец user_id в таблице posts , который сылается на столбец id таблицы users :

Задать желаемое действие для свойств «on delete» и «on update» внешнего ключа:

При создании имени внешнего ключа используется то же соглашение о присвоении имен, что и для индексов. Laravel будет создавать имя на основе имени таблицы, столбца и суффикса «_foreign». Удаление внешнего ключа:

Удаление при задании столбца в массиве, что формирует имя удаляемого ключа автоматически:

Миграция базы данных

Во время разработки и поддержки приложения, управляемого базой данных, структура используемой базы данных развивается так же, как и исходный код. Например, при разработке приложения может потребоваться новая таблица; После развертывания приложения на производство, может быть обнаружено, что для повышения производительности запроса необходимо создать индекс; и так далее. Поскольку изменение структуры базы данных часто требует некоторых изменений исходного кода, Yii поддерживает так называемую функцию миграции базы данных, которая позволяет отслеживать изменения базы данных с точки зрения миграции баз данных, которые контролируются версией вместе с исходным кодом.

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

  1. Tim создает новую миграцию (например, создает новую таблицу, изменяет определение столбца и т. д.).
  2. Тим совершает новую миграцию в систему управления версиями (например, Git, Mercurial).
  3. Даг обновляет свой репозиторий из системы управления версиями и получает новую миграцию.
  4. Даг применяет миграцию к своей локальной базе данных разработки, тем самым синхронизируя свою базу данных, чтобы отразить изменения, которые сделал Тим.

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

  1. Скотт создает тег выпуска для репозитория проекта, который содержит некоторые новые миграции базы данных.
  2. Скотт обновляет исходный код на рабочем сервере до тега release.
  3. Скотт применяет любые накопленные миграции базы данных в производственную базу данных.

Yii предоставляет набор средств командной строки для миграции, которые позволяют:

  • Создавать новые миграции;
  • Применять миграции;
  • Отменить миграцию;
  • Повторно применять миграции;
  • Показать историю и статус миграции.

Все эти инструменты доступны через команду yii migrate. В этом разделе мы подробно опишем, как выполнять различные задачи с использованием этих инструментов. Вы также можете получить использование каждого инструмента с помощью команды help yii help migrate.

Создание миграций

Чтобы создать новую миграцию, выполните следующую команду:

Необходимый аргумент name дает краткое описание новой миграции. Например, если миграция связана с созданием новой таблицы с именем news, вы можете использовать имя create_news_table и выполнить следующую команду:

Вышеупомянутая команда создаст новый файл класса PHP с именем m150101_185401_create_news_table.php в каталоге @app/migrations . Файл содержит следующий код, который в основном объявляет класс миграции m150101_185401_create_news_table со скелетным кодом:

Каждая миграция базы данных определяется как PHP-класс, простирающийся от yii\db\Migration . Имя класса миграции генерируется автоматически в формате m _ , где

  • относится к дате времени UTC, в котором выполняется команда создания миграции.
  • — это то же самое, что и значение аргумента name, которое вы предоставляете команде.

В классе миграции вы должны писать код в методе up() , который вносит изменения в структуру базы данных. Вы также можете написать код в методе down() , чтобы отменить изменения, сделанные функцией up() . Метод up() вызывается при обновлении базы данных с помощью этой миграции, а метод down() вызывается при понижении базы данных. В следующем коде показано, как реализовать класс миграции для создания таблицы news:

Базовый класс миграции yii\db\Migration предоставляет соединение с базой данных через свойство db. Его можно использовать для управления схемой базы данных с использованием методов, описанных в разделе Работа с схемой базы данных.

Вместо использования физических типов при создании таблицы или столбца вы должны использовать абстрактные типы, чтобы ваши миграции не зависели от конкретной СУБД. Класс yii\db\Schema определяет набор констант для представления поддерживаемых абстрактных типов. Эти константы названы в формате TYPE_ . Например, TYPE_PK относится к автоинкрементному типу первичного ключа; TYPE_STRING относится к строчному типу. Когда перенос применяется к конкретной базе данных, абстрактные типы будут переведены в соответствующие физические типы. В случае MySQL, TYPE_PK будет преобразован в int (11) NOT NULL AUTO_INCREMENT PRIMARY KEY , а TYPE_STRING станет varchar (255) .

При использовании абстрактных типов вы можете добавить дополнительные ограничения. В приведенном выше примере NOT NULL добавляется к Schema :: TYPE_STRING , чтобы указать, что столбец не может быть null.

Создание миграций

Если имя миграции имеет специальную форму, например create_xxx_table или drop_xxx_table , тогда сгенерированный файл миграции будет содержать дополнительный код, в данном случае для создания / удаления таблиц. Ниже описаны все варианты этой функции.

Создание таблицы

Чтобы сразу создать поля таблицы, укажите их с помощью опции —fields.

Вы можете указать дополнительные параметры поля.

Внешние ключи

Генератор поддерживает внешние ключи, используя ключевое слово foreignKey.

Позиция ключевого слова foreignKey в описании столбца не изменяет сгенерированный код. Это значит:

  • author_id:integer:notNull:foreignKey(user)
  • author_id:integer:foreignKey(user):notNul l
  • author_id:foreignKey(user):integer:notNull

Все сгенерируют один и тот же код.

Ключевое слово foreignKey может принимать параметр между скобками, который будет именем связанной таблицы для сгенерированного внешнего ключа. Если параметр не передан, имя таблицы будет выводиться из имени столбца.

В приведенном выше примере author_id:integer:notNull:foreignKey(user) будет генерировать столбец с именем author_id с внешним ключом для таблицы user , а category_id:integer:defaultValue(1):foreignKey будет генерировать столбец category_id с внешним ключом к таблице category .

Ключевое слово foreignKey принимает второй параметр, разделенный пробелом. Он принимает имя связанного столбца для сгенерированного внешнего ключа. Если второй параметр не передан, имя столбца будет выбрано из схемы таблицы. Если схемы не существует, первичный ключ не установлен или является составным, будет использован id имени по умолчанию.

Удаление таблицы

Добавить столбец

Если имя миграции имеет форму add_xxx_column_to_yyy_table , содержимое файла будет содержать инструкции addColumn и dropColumn .

Чтобы добавить столбец:

Вы можете указать несколько столбцов следующим образом:

Удаление столбца

Если имя миграции имеет форму drop_xxx_column_from_yyy_table , содержимое файла будет содержать инструкции addColumn и dropColumn .

Добавить таблицу соединений

Если имя миграции имеет форму create_junction_table_for_xxx_and_yyy_tables или create_junction_xxx_and_yyy_tables , тогда будет создан код, необходимый для создания таблицы соединений.

Имена столбцов внешнего ключа для таблиц соединений извлекаются из схемы таблиц. Если таблица не определена в схеме или первичный ключ не установлен или составной, используется id имени по умолчанию.

Транзакционные миграции

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

Еще более простой способ реализации транзакционных переносов — поместить код миграции в методы safeUp() и safeDown() . Эти два метода отличаются от up() и down() тем, что они заключены в транзакцию неявно. В результате, если какая-либо операция в этих методах завершится неудачно, все предыдущие операции будут откат автоматически.

В следующем примере, кроме создания таблицы news, мы также вставляем начальную строку в эту таблицу.

Обратите внимание, что обычно при выполнении нескольких операций с БД в safeUp() вы должны отменить их порядок выполнения в safeDown() . В приведенном выше примере мы сначала создаем таблицу, а затем вставляем строку в safeUp() . В то время как в safeDown() мы сначала удаляем строку, а затем удаляем таблицу.

Методы доступа к базе данных

Базовый класс миграции yii\db\Migration предоставляет набор методов, позволяющих вам обращаться к базам данных и манипулировать ими. Вы можете обнаружить, что эти методы названы так же, как методы DAO, предоставляемые классом yii\db\Command . Например, метод yii\db\Migration::createTable() позволяет вам создать новую таблицу, как это делает yii\db\Command::createTable() .

Преимущество использования методов, предоставляемых yii\db\Migration , заключается в том, что вам не нужно явно создавать экземпляры yii\db\Command , и при выполнении каждого метода будут автоматически отображаться полезные сообщения о том, какие операции с базой данных выполнены.


Ниже приведен список всех этих методов доступа к базе данных:

  • execute() : выполнение инструкции SQL
  • insert() : вставка одной строки
  • batchInsert() : вставка нескольких строк
  • update() : обновление строк
  • delete() : удаление строк
  • createTable() : создание таблицы
  • renameTable() : переименование таблицы
  • dropTable() : удаление таблицы
  • truncateTable() : удаление всех строк в таблице
  • addColumn() : добавление столбца
  • renameColumn() : переименование столбца
  • dropColumn() : удаление столбца
  • alterColumn() : изменение столбца
  • addPrimaryKey() : добавление первичного ключа
  • dropPrimaryKey() : удаление первичного ключа
  • AddForeignKey() : добавление внешнего ключа
  • DropForeignKey() : удаление внешнего ключа
  • CreateIndex() : создание индекса
  • DropIndex() : удаление индекса
  • AddCommentOnColumn() : добавление комментария к столбцу
  • DropCommentFromColumn() : отбрасывание комментария из столбца
  • AddCommentOnTable() : добавление комментария к таблице
  • DropCommentFromTable() : удаление комментария из таблицы

Применение миграции

Чтобы обновить базу данных до ее последней структуры, вы должны применить все доступные новые миграции, используя следующую команду:

Эта команда отобразит все миграции, которые не были применены до сих пор. Если вы подтвердите, что хотите применить эти миграции, он будет запускать метод up() или safeUp() в каждом новом классе миграции один за другим в порядке их значений временной метки. Если какой-либо из миграций не удается, команда прекратит работу, не применяя остальные миграции.

Для каждой миграции, которая была успешно применена, команда вставляет строку в таблицу базы данных с именем migration для записи успешного применения миграции. Это позволит инструменту миграции определить, какие миграции были применены, а какие нет.

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

Вы также можете явно указать конкретную миграцию, в которую следует мигрировать базу данных, с помощью команды migrate/to в одном из следующих форматов:

Если какие-либо непримененные миграции выполняются раньше указанного, все они будут применены до того, как будет применена указанная миграция. Если указанная миграция уже была применена ранее, любые последующие миграции будут отменены.

Отмена переноса

Чтобы вернуть (отменить) одну или несколько миграций, которые были применены ранее, вы можете запустить следующую команду:

Повторная миграция

Повторная миграция означает сначала возврат указанных миграций, а затем повторное применение. Это можно сделать следующим образом:

Листинг миграции

Чтобы указать, какие переносы были применены, а какие нет, вы можете использовать следующие команды:

Изменение истории миграции

Вместо фактического применения или возврата миграций, иногда вы можете просто захотеть отметить, что ваша база данных была обновлена до определенной миграции. Это часто происходит, когда вы вручную меняете базу данных на конкретное состояние, и вы не хотите, чтобы миграция (и) для этого изменения повторно применялись позже. Вы можете достичь этой цели с помощью следующей команды:

Команда изменит таблицу migration , добавив или удалив определенные строки, чтобы указать, что в базе данных были применены миграции к указанному. Эта миграция не будет применена или отменена.

Персонализация миграции

Существует несколько способов настройки команды перенастройки.

Использование параметров командной строки

Команда перенастройки содержит несколько параметров командной строки, которые можно использовать для настройки ее поведения:

  • Interactive : boolean (по умолчанию true), указывает, следует ли выполнять миграции в интерактивном режиме. Если это true, пользователю будет предложено выполнить команду, прежде чем она выполнит определенные действия. Вы можете установить это значение false, если команда используется в фоновом процессе.
  • migrationPath : string (по умолчанию используется @app/migrations ), указывает каталог, в котором хранятся все файлы классов миграции. Это может быть указано как путь к каталогу или псевдоним пути. Обратите внимание, что каталог должен существовать, иначе команда может вызвать ошибку.
  • migrationTable : строка (по умолчанию для migration), задает имя таблицы базы данных для хранения информации истории миграции. Таблица будет автоматически создана командой, если она не существует. Вы также можете создать его вручную, используя первичный ключ структуры version varchar(255) primary key, apply_time integer .
  • db : string (по умолчанию db), указывает идентификатор компонента приложения базы данных. Он представляет базу данных, которая будет перенесена с помощью этой команды.
  • templateFile : string (по умолчанию @yii/views/migration.php ), указывает путь к файлу шаблона, который используется для создания файлов классов миграции скелета. Это может быть указано как путь к файлу или псевдоним пути. Файл шаблона представляет собой скрипт PHP, в котором вы можете использовать предопределенную переменную с именем $className , чтобы получить имя класса миграции.
  • generatorTemplateFiles : массив (по умолчанию используется):
  • fields : массив строк определения столбцов, используемых для создания кода миграции. По умолчанию установлено значение []. Формат каждого определения: COLUMN_NAME: COLUMN_TYPE: COLUMN_DECORATOR . Например, —fields=name:string(12):notNull выдает столбец строки размером 12, который не равен null.

В следующем примере показано, как вы можете использовать эти параметры. Например, если мы хотим перенести модуль forum, файлы миграции которого находятся в каталоге migrations модуля, мы можем использовать следующую команду:

Конфигурация команды глобально

Вместо того, чтобы вводить одинаковые значения параметров каждый раз при запуске команды перенастройки, вы можете настроить ее один раз для всех в конфигурации приложения, как показано ниже:

При указанной конфигурации каждый раз, когда вы запускаете команду перенастройки, таблица backend_migration будет использоваться для записи истории миграции. Вам больше не нужно указывать его через параметр командной строки migrationTable .

Миграция имен

Вы можете использовать пространства имен для классов миграции. Вы можете указать список пространств имен миграции через migrationNamespaces . Использование пространств имен для классов миграции позволяет вам использовать несколько исходных местоположений для миграции. Например:

При работе с миграциями с именованным пространством: создание новых, возврат и т. д. Перед именем миграции необходимо указать полное пространство имен. Обратите внимание: символ обратной косой черты (\) обычно считается специальным символом в оболочке, так что вам нужно избежать его корректно, чтобы избежать ошибок оболочки или некорректного поведения. Например:

Разделенные миграции

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

Если вы хотите, чтобы несколько миграций применялись и отслеживались полностью отделенными друг от друга, вы можете настроить несколько команд миграции, которые будут использовать разные пространства имен и таблицы истории миграции:

Обратите внимание, что для синхронизации базы данных вам нужно запустить несколько команд вместо одного:

Миграция нескольких баз данных

По умолчанию миграция применяется к одной базе данных, указанной компонентом приложения db. Если вы хотите, чтобы они применялись к другой базе данных, вы можете указать опцию командной строки db, как показано ниже:

Приведенная выше команда применит перенос к базе данных db2.

Иногда бывает, что вы хотите применить некоторые миграции к одной базе данных, а некоторые другие к другой базе данных. Для достижения этой цели при реализации класса миграции вы должны явно указать идентификатор компонента базы данных, который будет использовать миграция, например:

Вышеупомянутая миграция будет применена к db2, даже если вы укажете другую базу данных через опцию командной строки db. Обратите внимание, что история миграции по-прежнему будет записываться в базу данных, указанную параметром командной строки db.

Если у вас несколько миграций, использующих одну и ту же базу данных, рекомендуется создать базовый класс миграции с помощью вышеуказанного кода init() . Тогда каждый класс миграции может распространяться от этого базового класса.

Еще одна стратегия, которую вы можете предпринять для миграции нескольких баз данных, — это миграция различных баз данных в разные пути миграции. Затем вы можете перенести эти базы данных в отдельные команды, например:

Первая команда применит миграции в @app/migrations/db1 к базе данных db1, вторая — применит миграции в @app/migrations/db2 к db2 и т. д.

как обрабатывать изменения базы данных/таблицы в PHP?

Хорошо, так что теперь у меня есть эта проблема, мне нужно, чтобы добавить столбец «баланс» для моей таблицы пользователей. Я использую Zend Framework, и я надеялся использовать этот здесь:

К сожалению, я предполагаю, что это никогда не попадал в стадии реализации, и есть только заголовок файла, файл imeplementation нигде не будет загружен.

Бы вы, ребята, есть какие-либо предложения? Я не хочу идти в базу данных MySQL 5 и вручную добавить столбец мне нужно — в то же время я не хочу делать это таким образом каждый раз, когда мне нужно делать какие-либо изменения в БД. Мне нужно что-то вроде компонента менеджера Зенд схемы.

1 ответ

Возможно, для Вашего проекта будут необходимы бесплатные векторные карты. На нашем сайте представлены карты для всех стран.

zend database migration scripts

I would like to know how do you guys work in Zend Framework environment especially how to keep the database table up to date with others team (database change management) . I have small team who works at their home and we need to have latest database table everytime. We works at home so sometimes we work after finishing our main job at office.

ps: just like Rails migration

3 Answers 3

We’re using the db deploy feature of phing to keep track on database changes over time and to update the database between our developers. We’re also using it to deploy new versions of our application, which usually include database updates.

Basically each time someone need to update the database scheme, he adds a database migration script. Each developer can then run phing to execute these db migration scripts automatically.

There is also a ZF-specific migration tool developed by Rob Allen (@akrabat):

In principle, it is similar to the dbdeploy tool in Phing . In Rob’s Schema Manager , each delta file contains a class with methods up() and down() that perform, respectively, the upgrade and the downgrade of the db schema.


It comes with a command-line tool built on Zend_Tool that can perform specific migrations, report the current schema version, etc.

Update (as suggested by @markus-tharkun)

Schema Migration for Databases

You should use change management tools to maintain your database schema. There are great open-source SCM tools available to make your life easier, especially when you’re using CI.

Join the DZone community and get the full member experience.

It is generally accepted that you should always use proper version control when developing software. Why wouldn’t you do the same with your database changes?

The question is, how do you manage changes to the database? The quick answer is, “write .sql scripts and check them into the version control system.” As usual, things aren’t always as simple as they first seem. Maintaining “build from scratch” scripts in version control is easy enough, but what do you do when you need to modify an existing database object?

The Hard Way

One method I’ve used in the past is to maintain two sets of scripts, each set in a different directory: one for new installations and one for upgrades.

New Installation Scripts

In this directory, I have scripts for creating database objects. I prefer to have a single script handle a single object such as a table, view, or stored procedure. I also include separate scripts that will populate the default data in the master tables.

Upgrade From Version X to Y

In this directory are the scripts that will alter the database objects, upgrading them from one version to the next.When needed, this will include any data migration scripts.

In both directories, there is a master script that runs the change scripts in the correct order. Each script should contain validation logic to ensure they only run when they are supposed to and they raise any errors if something goes wrong.

While this approach can work, it’s not perfect and there are some problems that are difficult to address.

How sure can we be that everything ran?

If there’s a problem somewhere in the process, how do we rollback the changes?

Can I run some scripts only for testing and not in production?

Each of these problems has the same answer: Write more scripts.

Use a Schema Change Management (SCM) Tool Instead

Typically, an SCM (sometimes called a migration tool) will function like a combination of “the hard way” directories above. Every change you make is defined in a change log. You’ll start with the very first “create table” changelog and add a new changelog for all changes made from then on, such as adding or dropping a column, index, or constraint. The SCM tool will keep track of what has or hasn’t been run and implement only the new changes. If you’re doing a brand-new install, it will start at the beginning and run them all.

There are a lot of great SCM tools available, and many of them are open source.

If you’re using a programming framework in your application, such as Python’s Django or Ruby on Rails, it may have built-in SCM tools. My first real experience with an SCM tool was a few years ago. I was writing a small Ruby on Rails application using Active Record, and since then, I have been a strong advocate for SCM tools.

I love exploring many different programming languages — which can make it difficult to get very deep in any language-specific SCM tool. Fortunately, there are also standalone SCM tools you can use such as Flyway and Liquibase. Both of these tools are open-source and well-supported.

Most SCM tools work with many different databases — most of which implement slightly different versions of SQL. The tools will use their own object definitions in a common format such as JSON, XML, YAML, and others. The SCM tool will translate these definitions into the correct SQL for the database you’re deploying your changes to. For my examples, I will be using an Oracle Database, but the same definitions should work with many different databases.

Examples

I’ve used a few of these tools, some more than others, but the one I’m most familiar with is Liquibase, so I’ll use it for the following examples. Usually, the Liquibase examples you’ll find online are written in XML, but I find XML to be a bit too pointy so I’ll be using JSON.

Installation and Configuration

In my database, I’ve created a schema called lb_demo to use with the examples. If you run these examples, make sure you use a schema that is safe to experiment in. Locate (or download) the JDBC driver for your database. I’ll be using the Oracle driver ojdb7.jar .

Liquibase refers to the database changes as “changesets” and the files that contain them as “changelog files.” A changelog master lists the changelogs in the order that they will be run in.

Create the following directories and files:

Liquibase is “just” a Java application, so you don’t actually need to do anything more than download the ZIP file, extract it, and run it. (Make sure your Java is version 1.6 or higher.) You can run it directly from Java:

The Liquibase download includes a shell script and batch file that can be used in place of running the .jar file. You can simplify your process by including the extracted Liquibase directory in your path. For these examples, I will be using the Shell script. To make it a little easier, I will include the parameters from above in a properties file.

Open liquibase.properties and add the following text substituting the values for your configuration:

Example 1: Create Two Tables

Open db.changelog-master.json and add the following JSON:

The first changeset creates a table called lb_groups with three columns: id , name , and description . Open db.changelog-1.0.json and add the following JSON:

The second change set creates a table called lb_people with four columns: id , firstname , lastname , and group_id . A foreign key to the lb_groups table is created using group_id .

Open db.changelog-2.0.json and add the following JSON:

Most of the properties here are fairly self-explanatory and similar to what you’d use in an SQL script to create a table. Let’s take a look at some of the extra properties.

Liquibase uses Id and author combined with the file name and package to uniquely identify a changeset. The Id and author values can be any string you’d like, but you should use values that will have meaning in your process.

The preConditions array is a list of conditions that Liquibase will check before running the changes. In this example, I’m making sure that I’m connected to the correct schema lb_demo before adding the tables. The linked documentation has a long list of other conditions you can use.

And finally, the comment value will be included in the Liquibase database changelog table (discussed below) and in the documentation generated by Liquibase.

Run It

Warning: When you run Liquibase, it executes the changes you defined. There is no “Are you sure?” prompt and there are not separate commits.

Run this command: liquibase update .

Connect to your database with your favorite SQL tool and inspect the changes. If this is the first time Liquibase has been run, you should have two new tables that Liquibase uses to control and log the changes: databasechangeloglock and databasechangelog . Take a look at the databasechangelog table to see what dataLiquibase tracks for the changes. You should also see the two new tables, lb_groups and lb_people , defined in the changesets above.

Example 2: Roll Back Changes

Liquibase includes the ability to automatically roll back certain changes. Let’s add a column and roll it back.

First, add a new file db.changelog-3.0.json to the changelog directory.

Include the new file at the end of db.changelog-master.json .

Run the update: liquibase update .

Connect to your database and verify that the column rules has been added to the lb_groups table. Look in the databasechangelog table to see that the new change has been logged.

Liquibase has the option to roll back to a date, a tag, or a certain number of changes. When you roll back changes, they are rolled back in the reverse order that they were run. Run the following command to roll back the last change: liquibase rollbackCount 1 .

Connect to your database and verify that the column rules has been removed from the lb_groups table. Look in the databasechangelog table to see that the record for changelog 3 has been removed. Liquibase only tracks changes thathave been applied to the database — when you roll back a change, it is no longer in the log.

Other Schema Change Management Features

Branching and merging.

Reverse-engineer your current database into changelogs.

Compare differences between schema.

Load data from a changeset or CSV file.

Auto-generate schema documentation.

Use labels and contexts to conditionally include orexclude specific changes.


Schema Change Management vs. «The Hard Way»

These examples were just a small taste of what you can do with a good SCM tool. Let’s compare them to the way I used to do it.

Both methods produce a similar number of files. The SCM method only requires maintaining the changes in one place instead of files in two different directories.

The JSON files are a bit more verbose than the SQL scripts but if you’re like me, I find JSON to be more readable.

The SCM method automatically keeps track of what has run and only runs the new changes.

The SCM method gives you the ability to automatically rollback most changes.

Perhaps the biggest difference is the ease of using an SCM tool in a continuous development pipeline versus trying to manage a large directory of SQL files. This alone should be worth exploring a schema change management option.

README

Akrabat_Db_Schema_Manager is a tool for use with ZF1. If you need a standalone tool, then have a look at South for the Winter.

To get the Zend_Tool provider working:

Place a copy of ZF1 in /usr/local/include/zf1/ , so that the Zend folder is in zf1/library/Zend

/.bash_profile to set up an alias to the zf.sh script

Restart your terminal or source

If you haven’t already done so, setup the storage directory and config file:

Place a copy of the Akrabat Tools in /usr/local/include/Akrabat :

Edit the created

/.zf.ini . Change path so that it includes ZF1 and Akrabat/library, allow for auotoloading Akrabat and set up the provider:

zf should provide a help screen with the DatabaseSchema provider at the bottom.

Akrabat_Db_Schema_Manager

Create scripts/migrations folder in your ZF application

Create migration files within migrations with the file name format of nnn-Xxxx.php. e.g. 001-Users.php where:
nnn => any number. The lower numbered files are executed first
Xxx => any name. This is the class name within the file.

In up(), database changes are applied, in down(), changes are reverted. If changes cannot be reverted, then down() should throw a Akrabat_Db_Schema_Exception.

Create a class in your migrations file. Example for 001-Users.php:

If you want a table prefix, add this to your application.ini :

Phing Task

Define the phing task in the build.xml

Setup a phing target with the database options in the build.xml

Run phing using the command line, by phing database-migration

Zend framework 2/Работа с базой данных и моделями

Содержание

Базы данных [ править ]

Мы уже создали модуль Album, необходимые контроллеры, методы и шаблоны. Пришло время рассмотреть модели(model). В классической теории баз данных, модель данных есть формальная теория представления и обработки данных в системе управления базами данных (СУБД), которая включает, по меньшей мере, три аспекта:

1) аспект структуры: методы описания типов и логических структур данных в базе данных;

2) аспект манипуляции: методы манипулирования данными;

3) аспект целостности: методы описания и поддержки целостности базы данных.

Мы будем использовать класс ядра фреймворка Zend\Db\Gateway\TableGateway, который используется для таких операций с БД как:find(найти), insert(вставка), update(обновление) and delete(удаление) строк.

Используем MySQL через PHP драйвер PDO. Создайте БД zf2tutorial, и создайте таблицу «album» . Для этого можно использовать следующий код:

Теперь, когда у нас есть БД, таблица с данными можем приступить к созданию простой модели.

Модель [ править ]

Zend Framework 2 не предоставляет компонент Zend\Model, поэтому разработчику нужно самому решать, как он организует свою бизнес логику работы с БД. Существует большое количество различных решений этой проблемы. Одним из решений является использование сущностей и мапера(mapper) для работы с БД. Другим выходом является использование ORM(технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования, создавая «виртуальную объектную базу данных») таких как Doctrine или Propel.

Будем использовать сущности. Создайте класс «Album» в папке «Model» (на одном уровне с Controller).

Наш объект сущности Album представляет собой обычный класс PHP. Для согласованной работы с Zend\Db\TableGateway\AbstractTableGateway мы создали метод exchangeArray(), который просто копирует данные, пришедшие в виде массива в свойства сущности. Также добавим входной фильтр(inputfilter) для дальнейшего использования с формами(form).

Далее расширим класс Zend\Db\TableGateway\AbstractTableGateway создав собственный класс AlbumTable в той же директории(Model):

Давайте разберемся с кодом. Сначала мы устанавливаем защищенное свойство(переменная) $table, в которую заносим имя таблицы «album». Потом создаем конструктор(__construct), который принимает адаптер БД(Adapter $adapter) и делает доступным для использования его в нашем классе. Далее ставим в известность шлюз, что при каждом новом создании строки нужно использовать объект Album. Класс TableGateway использует паттерн prototype для создания выходных данных и сущностей. Это означает, что при необходимости создания экземпляра берется клон ранее созданного экземпляра класса. Более подробно можете почитать тут: PHP Constructor Best Practices and the Prototype Pattern

Далее мы создали еще несколько дополнительных методов для связи с таблицей в БД. fetchAll() — вытаскивает все строки из таблицы и возвращает их как ResultSet, getAlbum() – возвращет одну строку как объект Album, saveAlbum() – создает/обновляет строку в БД, deleteAlbum() – удаляет строку.

Использование ServiceManager для настройки доступа к базе данных и инъекции в контроллер [ править ]

Для использования экземпляра класса AlbumTable воспользуемся ServiceManager. Самый простой способ данной реализации это создание метода getServiceConfig() в файле Module.php, так как ModuleManager будет вызывать его автоматически и передавать в ServiceManager. И у нас будет возможность вызвать его с любого нашего класса.

Для настройки ServiceManager необходимо указать имя класса, которое будет создано, либо фабрику, которая создаст необходимый объект при вызове. Мы создадим фабрику для AlbumTable. Добавьте следующий код в конец класса Module:

Этот метод возвращает массив фабрик, которые в итоге объединяются в ModuleManager перед отправкой в ServiceManager. Теперь необходимо настроить ServiceManager так, что б он знал как получить адаптер Zend\Db\Adapter\Adapter. Это обеспечивается вызовом фабрики Zend\Db\Adapter\AdapterServiceFactory, которую мы можем настроить в конфигурационном файле приложения.

В Zend Framework 2 ModuleManager сначала забирает все настройки с файлов конфигурации модулей(module.config.php), потом с cofig/autoload(сначала с *.global.php, потом с *.local.php, и потом остальных которые в этой директории ). Мы добавим настройки для подключения к БД в global.php. Так же можете использовать файл local.php для хранения настроек (за пределами VCS) если необходимо.

Теперь, когда ServiceManager при необходимости может создать для нас экземпляр AlbumTable, добавим код в контроллер для его получения. Добавьте метод getAlbumTable() в AlbumController:

Еще нужно добавить в начало класса:

Теперь getAlbumTable() доступен с любого места нашего класса для взаимодействия с моделью(model). Построим список альбомов при вызове действия index.

Список альбомов [ править ]

Для вывода списка альбомов необходимо извлечь данные из модели и передать в шаблон вида. Для этого допишем следующий код в действие indexAction контроллера AlbumController:

В ZendFramework 2 для передачи переменных в шаблон вида необходимо вернуть экземпляр ViewModel, где первый параметр будет массив с данными, переданными из контроллера. Тогда они автоматически попадут в шаблон вида. Объект ViewModel позволяет изменить скрипт вида, куда будет передана информация, но по-умолчанию передается в <имя контроллера(controllername)>/<имя дейстия(actionname)>. Теперь приступим к заполнению шаблона вида(скрипт вида, view script):

Для начала установим заголовки страниц в теге используя метод помощника вида headTitle(). Эти заголовки будут показаны в строке заголовка браузера. Потом добавим ссылку для добавления нового альбома.

Помощник вида url() используется для создания необходимых нам ссылок. Первый параметр указывает на имя роута(route), который мы хотим использовать для генерации ссылки, второй – является массивом, содержащим переменные для подстановки в заполнитель(placeholder). В данном случае имя роута «album», а переменные для заполнители будут: «action» и «id».

Делаем перебор переменной $albums, которую отправили из действия в вид. В Zend Framework 2 нет необходимости добавлять к переменным префикс $this, так как об этом заботится сам фреймворк, и гарантировано получаем в шаблоне вида все переменные, отправленные из соответствующего контроллера. Хотя можно и добавить, если это необходимо.

Создаем таблицу для вывода названия альбома(title) и имени артиста(artist), а так же добавляем ссылки на редактирование и удаление альбома. Оператор цикла foreach используется для перебора всех значений. В данном примере мы используем альтернативную его форму с двоеточием и endforeach потому, что так визуально легче написать правильный код и не нужно искать совпадающие скобки для открытия/закрытия…

Важно: В примере мы везде используем помощник вида escapeHtml() для обеспечения безопасности и защиты от XSSатак.

Использование миграций базы данных


Три программиста пишут код проекта. И с кодом у них нет проблем. Есть три рабочих машины, центральный репозиторий и главный рабочий сервер, берущий файлы из этого же репозитория. Работа кипит. у каждого на своём компьютере установлен PhpMyAdmin, что им позволяет время от времени вносить изменения в свою базу данных.

Программный код спокойно держится в VCS. Любой из троих разработчиков делает pull и сливает к себе из центрального репозитория последнюю версию файлов, что-то дописывает, коммитит и отправляет обратно через push. Теперь остальные два разработчика сливают эти изменения к себе, дорабатывают свои дела и отправляют назад. Такими темпами движется разработка. В репозитории видна история изменения каждого файла и общая цепь правок, оформленных в коммиты. И всё хорошо.

Там же среди файлов лежит SQL файл с дампом исходной базы. Любой новый программист свободно может склонировать проект на свой компьютер и присоединиться к разработке. Актуальную базу данных он может попросить и кого-то ещё или сдампить с тестового сервера. Ну или если после каждого изменения вручную правится и коммитится дамп со схемой, то взять его последнюю версию.

И вот новый программист для выполнения своей задачи добавил в таблицу базы постов блога новое поле public и изменил выборку постов на сайте так, чтобы отображались только открытые записи. Изменение кода модели Post он сохранил в новую ревизию и отправил в центральный репозиторий:

Теперь ему надо открыть скайп, зайти в конференцию и попросить всех добавить в свои базы это поле. Иначе после загрузки изменений у них просто-напросто не обнаружится поле public и блог работать не будет:

Теперь каждый должен зайти в свою базу и вручную добавить туда поле public .

Ну а старший программист при очередной выкладке изменений на рабочий сервер откроет историю переписки в скайпе и сделает то же самое на рабочем сайте.

Хранение изменений в системе контроля версий

Но вдруг менеджер проекта говорит, что в скайпе искать изменения неинтересно, что вручную переименовывать десятки колонок и переносить в них значения слишком муторно, и лучше записывать правки базы SQL-запросами где-то внутри проекта.

Тогда все договариваются либо записывать SQL-запросы с изменениями прямо в сообщения коммитов:

либо создать текстовый файлик и записывать все правки в него.

Потом кто-то ещё захочет переименовать пару полей в какой-то таблице и снова запишет там что он сделал. В конце недели старший разработчик через diff в системе контроля версий посмотрит, какие запросы добавились и одним махом выполнит их на рабочем сервере. Это же теперь будут делать и сами разработчики при приёме свежей версии репозитория.

В Виллабаджио всё ещё пишут SQL-запросы в коммитах, а в Вилларибо уже замучились бегать по спискам и придумали сделать в проекте папку changes и просто добалять туда отдельные файлики с SQL-запросами. Да не просто по одному файлику, а по два! У них уже несколько раз был случай, когда нужно было на тестовом сервере откатить изменения на предыдущее состояние, и в связи с этим возвращать базу данных назад вручную. Теперь они решили для любой операции создавать по два симметричных SQL-файла: для применения изменения (up) и для отката его назад (down).

Переименование поля таблицы у них выглядит так:

Теперь для применения изменений на любом сервере нужно всего лишь выполнить нужный запрос:

Также отпала необходимость хранить свежую версию базы в репозитории. Теперь любому новому разработчику достаточно установить исходную схему и применить все изменения:

и база данных у него станет актуальной версии.

А если нечаянно появилась ошибка, то для её поиска кто-то сможет откатить пару последних изменений базы и вернуть состояние кода на вчерашнюю версию, а когда найдёт ошибку применить изменения и вернуть всё назад.

Все изменения базы данных хранятся в файлах вместе с кодом в репозитории. Внутри SQL файлов теперь можно записывать запросы любой сложности. Теперь ошибки при ручном внесении изменений фактически исключены.

Мы называем файлы по порядковому номеру как 00015_up.sql . Следующая миграция будет под номером 00016 . Но если два разработчика будут делать одновременно две задачи и добавят свои новые с номером 00016 , то возникнет конфликт. Поэтому удобнее называть миграции на основе даты и времени по UTC с точностью до секунды, например m130627_124312 для изменения от 27-го июня 2013 года в 12:43:12.

Теперь всё работало как надо, базу можно было откатывать на любое число шагов назад и возвращать вперёд. Стоило лишь запоминать, сколько так называемых «миграций» было выполнено и в каком состоянии база сейчас находится.

Определение текущего состояния БД

Если в любой момент посмотреть на отдельно взятую от кода базу, то довольно сложно оценить, свежая она или нет, какие изменения произведены и какие пропущены. Сейчас этого не видно.

Каждому разработчику нужно каждый раз смотреть на историю добавленных файлов и следить, чтобы все новые миграции были применены, чтобы ни одну не пропустить и чтобы не применить случайно любую старую второй раз.

Как это можно упростить? Как определить состояние любой попавшей к нам базы не глядя в код файлов? Для этого достаточно хранить список применённых миграций в самой базе!

Итак, добавим в базу отдельную таблицу migration :

Теперь при применении любой миграции будем добавлять её идентификатор в данную таблицу. А при откате аналогично удалять. Для автоматизации этого процесса достаточно добавить по одному соответствующему запросу в каждый файл:

Теперь можно открыть базу и посмотреть последнюю строку в таблице migration .

Автоматизация процесса

Вот тимлид вернулся из отпуска и начал смотреть репозиторий, чтобы узнать, какие файлики изменений добавились. После этого начал их применять, импортируя в консоли одну за одной. А потом пришёл домой, прочёл о консольных командах и сказал: «Чёрт его побери! Мне же можно написать консольный скрипт, который делал бы всё сам!»

Итак, посмотрим, что тут можно сделать. Попробуем сделать скрипт миграций для PHP проектов.

Система миграций для PHP проектов

Первым делом заменим нативные SQL-файлы на продвинутые PHP-классы. Создадим базовый шаблон с методами up и down :

и будем наследовать наши файлы с миграциями от него.

Файл migrations/m130627_124312.php может выглядеть так:

Использование классов вместо SQL-файлов даёт преимущество, так как в них мы можем выполнять любые преобразования и организовывать любую логику с помощью PHP.

Проекты могут быть разными: на разных базах и с разными префиксами таблиц.

Всё изменчивое и касающееся различий баз лучше инкапсулировать в что-то более независимое. Можно взять тот же PDO или любой другой объектный инструмент для доступа к базе данных.

Для примера не будем брать PDO, «свелосипедим» свой класс на основе MySQL:

Здесь мы позаботились о выводе исключений при ошибках в запросах. Теперь если что-то пойдёт не так с запросом, то мы явно увидим ошибку.

Обратим также внимание на то, что мы добавили метод для автоподстановки префиксов к именам таблиц в поступаюших SQL запросов:

Это позволит нам вместо ручного указания префикса:

переложить эту обязанность на систему и использовать условную запись имён таблиц в фигурных скобках:

Это декоративный, но всё же приятный момент.

Результат выполнения mysql_query обрамляется в объект класса QueryResult , который реализует интерфейс итератора:

Это позволяет помимо обычного подхода с условным циклом while :

использовать обход с помощью цикла foreach :

В любом случае, внутри соответствующих методов этого класса скрывается метод mysql_fetch_assoc .

Итак, теперь у нас есть классы Migration , DB , MySQL_DB , QueryResult и первая миграция migrations/m130627_124312.php :

Сделаем теперь компонент, который будет запускать миграции и вести их историю.

Он должен позволять «накатывать» и «откатывать» произвольное число миграций. То есть иметь методы up($count) и down($count) . Причём метод up по умолчанию (если не передано количество миграций) должен применять все имеющиеся, а down – отменять только одну последнюю. Это позволит работать с миграциями более удобным образом.

Аналогично не плохо бы иметь метод create , который бы на основе текущего времени создавал пустой класс для новой миграции.

Итак, реализация может быть такой:

В конструкторе данный класс принимает объект доступа к базе данных и вызовом checkEnvironment сам создаёт папку и таблицу миграций, если они отсутствуют.

Код вспомогательных методов здесь не приведён для сокращения сути.

Полный комплект файлов: migrations.zip.

В методе create создаётся новый файл миграции с пустым классом. В качестве имени задаётся текущая дата и время по UTC. Так что даже если разработчики находятся в разных часовых поясах их миграции будут всегда следовать в правильном алфавитном порядке.

Мы реализовали только минимальный набор методов up , down и create . Также полезно было бы добавить status для простого показа новых миграций без применения, to для преобразования базы к указанному конкретному состоянию и ещё модифицировать up и down для работы только с одной указанной миграцией.

Рассмотрим подробнее метод up :

Ему можно передать количество файлов, которые нужно применить. Внутренним методом getNewMigrations он получает список неприменённых миграций (посредством сравнения списка файлов и записей в таблице migration базы), отсортированный по дате. Затем с помощью array_slice из массива берутся либо несколько первых значений (если указано число $count ), либо все.

Если новых миграций не нашлось, то выведется сообщение об их отсутствии. Иначе их список выведется на экран и появится запрос подтверждения действия. Если пользователь ответит утвердительно, то для каждого элемента запустится метод migrateUp :


Он подключит нужный файл, создаст экземпляр класса и запустит его метод up . Если это не вернёт значение false , то запустится writeUp и добавит запись об успешно применённой версии в таблицу migration .

В методах up и, что чаще, в down миграции можно прервать выполнение очереди посредством return false . Но данную возможность следует использовать только если это крайне необходимо.

Метод down работает аналогично, но только по умолчанию откатывает только одну последнюю применённую миграцию и удаляет пометку о её версии из базы.

Теперь осталось только написать консольный скрипт-контроллер, который бы принимал пользовательские параметры, конфигурировал все компоненты и запускал нужное действие.

Теперь достаточно создать тестовую базу с таблицей tbl_post :

зайти в консоли в нашу папку и попробовать «погонять» миграции туда-сюда:

Теперь любой разработчик может создать новую миграцию, выполнив:

записать в созданный класс свой функционал и попробовать применить и откатить пару раз у себя. Теперь нужно добавить этот файл в репозиторий проекта и отправить на центральный сервер.

Другие разработчики после скачивания изменений должны запустить у себя:

и все новые изменения произведутся в их базах. Сразу после применения можно продолжать работу.

Таким образом, мы тепеть можем написать вполне самодостаточный компонент для поддержки миграций для любой самописной или готовой CMS.

А теперь посмотрим устройство команды migrate в Yii, а также рассмотрим некоторые примеры применения и дадим несколько общих советов.

Миграции в Yii Framework

Недавно мы разбирали консольные команды в Yii и создавали минимизатор файлов стилей. Так вот, в списке стандартных команд при запуске:

мы видели команду migrate . Это ни что иное как встроенная в Yii возможность работать с миграциями, реализованная консольной командой MigrateCommand. Рассмотрим её подробнее.

Отличие в командах

В отличие от реализованного нами функционала с добавлением времени:

для миграци в Yii нужно указывать любое текстовое имя, например:

Его нужно передавать при создании миграции:

При поиске новых миграций, применениях, откатах и других операциях это имя не учитывается, поэтому при необходимости (при опечатках или не очень подходящем названии) миграции можно переименовывать, сохраняя неизменным номер версии.

Также в Yii имеются несколько не только разобранные нами, но и ещё несколько дополнительных действий. Общий их список таков:

  • up – применить все (или заданное количество) свежие миграции
  • down – откатить одну (или заданное количество)
  • redo – откатить и заново применить одну (или заданное количество)
  • to – перевести базу в указанное состояние по идентификатору нужной миграции
  • mark – вручную пометить указанную миграцию только что применённой
  • history – вывести список применённых миграций
  • new – вывести список свежих ещё не применённых
  • create – создать новую с указанным именем

Поддержка транзакций

Предположим, что в одной миграции выполняется несколько последовательных запросов переносом данных. На случай, если в течение выполнения миграции что-то пойдёт не так, лучше бы было иметь возможность отменять изменения.

Если ваша база данных поддерживает транзакции (например, таблицы в MySQL вместо MyISAM созданы c движком InnoDB), то для защиты от неполностью завершённой серии запросов достаточно просто переименовать методы up() и down() в safeUp() и safeDown() соответственно:

Если мы заглянем в класс CDbMigration , то увидим, что они оборачиваются в транзакцию автоматически.

Обратите внимание, что в MySQL транзакции работают только с данными (изменения данных командами INSERT, UPDATE, DELETE). Для операций добавления, переименования и удаления таблиц и их полей командами ALTER TABLE, BEGIN, CREATE INDEX, DROP DATABASE, DROP TABLE, RENAME TABLE и TRUNCATE транзакции не отменяются. Применение методов safeUp с safeDown для переименования колонки бесполезно.

Вспомогательные методы

В миграциях можно использовать свои модели ActiveRecord или работать напрямую с базой через DAO. Вместо Yii::app()->db эффективнее обращаться к соединению вызвав метод getDbConnection самой транзакции, так как в конфигурационном файле config/console.php может быть настроено какое-угодно соединение.

Получив текущее соединение, мы можем добавить колонку прямо его методом:

Но давайте заглянем внутрь класса CDbMigration . Там мы найдём одноимённый метод:

Это своеобразный декоратор над методом CDbCommand::addColumn , дополненный выводом сообщения о текущей операции и времени на экран. Для удобства и информирования целесообразно применять именно его:

Такие же декораторы имеются и для других методов. Их удобно использовать вместо простых SQL-запросов.

Также плюсом классов-миграций против SQL-файлов является возможность программировать в них любую логику.

Это может пригодиться, например, для миграции, добавляющей поддержку нескольких категорий для товара в магазине (связи многие-ко многим) вместо одной (один-ко многим) с сохранением всех предыдущих связей:

Ещё для миграций можно задавать свои настройки, запускать их так, чтобы они не требовали от нас подтверждения, хотим ли мы что-то сделать.

Достаточно подробно о других возможностях написано в статье о миграциях официального руководства.

Рекомендации к использованию

Теперь мы знаем, что миграции служат не только для обмена изменениями, но и для двустороннего перевода базы данных в состояние на любую дату. То есть если нам надо откатиться на вчерашнюю версию нашего сайта, то мы отменяем сегодняшние миграции и для возврата вчерашнего состояния файлов переключаемся на любую вчерашнюю резервную точку системы контроля версий. При этом должна быть возможность беспрепятственно передвигаться по истории в любую сторону на любое число шагов.

В связи с этим желательно соблюдать главные рекомендации:

  • все изменения структуры базы производить в миграциях;
  • сохранять только изменения схемы, а не данных;
  • не исправлять уже добавленные миграции.

По первому пункту: Можете, конечно, во время разработки потренироваться вручную в PhpMyAdmin, но всё равно потом верните всё «как было» и запишите все необходимые изменения в программный код.

По второму: На боевом сервере свои данные, на тестовом вторые, на рабочем – третьи. На большом сайте новые комментарии добавляются каждую секунду. Не обращайте внимания на пользовательские данные.

Если в базе данных хранятся системные настройки модулей сайта, то можете включить их изменение в миграцию. Остальные же данные (пользователи, комментарии или записи блога) не фиксируйте.

И по третьему: Создавайте всегда новую миграцию для исправления недочётов, а не исправляйте старую. За исключением случаев, когда старая содержит очень вредную ошибку. Миграции всегда должны уметь выполняться последовательно.

Пример усложнения жизни

В понедельник вы добавили миграцию переименования таблицы tbl_posts в tbl_post, во вторник передумали и переименовали tbl_post в tbl_entry. Сейчас среда. Если при этом для двух переименований созданы две миграции, то можно легко переключаться на среду/вторник/понедельник/вторник/среду всего лишь запуская по одному разу down и up .

А теперь представим, что в понедельник мы добавили первую миграцию tbl_posts→tbl_post, а во вторник откатили, переименовали и переделали эту же в tbl_posts→tbl_entry и применили заново. То есть теперь у нас в истории имеется два экземпляра одной миграции с разным функционалом. Теперь не зная таких особенностей сложно угадать, что чтобы попасть из среды во вторник нужно откатить миграцию вторника с кодом среды (попав на понедельник) → переключить файлы на состояние вторника → применить вроде бы эту же миграцию, но уже с кодом вторника. То есть надо как-то всем объяснить, что со среды на вторник можно попасть только двумя шагами через понедельник. Такую игру разума даже прочесть сложно, не то что запомнить.

Пример халатного поведения

Кто-то добавил создание таблицы. Через месяц при разгребании завалов он понял, что там осталась «висеть» колонка rating , которая в этой таблице оказалась не нужна. Но вместо создания новой миграции он просто удалил её вручную и закомментировал строку в старой. Никто об этом не узнал.

Через полгода новый разработчик клонирует себе репозиторий, применяет миграции и получает свежую рабочую базу. Заказчик заново задумался внедрить систему рейтингов. Программист пишет новую миграцию:

тестирует её у себя и всё происходит нормально. На следующий день эту миграцию применяют на рабочем сервере и. она не применяется со словами «Колонка ‘rating’ уже существует». А наш разработчик даже не знал, что в его личную базу это поле не добавилось, так как в коде одной из миграций оно было заботливо закомментировано полгода назад.

Это просто неприятный безобидный пример. Но что было бы, если б кто-то забыл убрать или лишний раз убрал что-то более важное? Была бы остановка сервера на час с аварийным восстановлением из резервной копии.

Пользовательские настройки

Любую команду в Yii можно настроить в секции commandMap конфигурационного файла console.php . Достаточно открыть класс MigrateCommand и посмотреть, какие у него есть публичные свойства и сеттеры. Присвоить им любые значения можно как и другим компонентам:

Миграции для модулей

По умолчанию команда MigrateCommand ищет и применяет миграции из папки protected/migrations . Но так как мы можем её конфигурировать как любой компонент, то мы можем отнаследоваться и директивой class подменить используемый класс команды.

В публичном доступе уже имеется доработанный класс EMigrateCommand, который позволяет работать с папками migrations внутри каждого модуля любого проекта на Yii. В минимальном варианте достаточно сконфигурировать модули и заменить им класс команды:

для создания миграции в папке migrations модуля блога следует вызывать:

То есть в командах просто указывать имя модуля. Это изменение касается и некоторых других команд.


При этом можно и дальше пользоваться командами:

как и раньше. Не забудьте только создать папки для миграций в модулях.

Борьба с кэшированием

Предположим, что при работе приложения используется кэширование. Например, в настройках соединения указано кэширование схемы базы 3600 секунд опцией schemaCachingDuration или как-то ещё. В этом случае после каждого изменения желательно очищать кэш. Для этого можно добавить новую команду:

после применения миграций.

Но рассмотрим другую ситуацию. В один прекрасный момент мы применяем миграцию с добавлением новой колонки alias для перевода блога на ЧПУ. В модели мы добавили заполнение этого поля транслитом наименования в методе beforeSave :

Здесь могли бы быть другие преобразования, но это сейчас не важно.

Итак, теперь мы должны добавить саму миграцию и заполнить новое поле alias . Для этого проще всего пересохранить все модели:

Что у нас может произойти? При применении этой миграции после вызова другой операции с моделью Post (в предыдущей миграции или где-то ещё в приложении) или при включенном кэшировании схемы может вывалиться ошибка, что поле alias не найдено в базе данных. Действительно, ActiveRecord же не знает, что мы изменили таблицу с того момента, как было произведено предыдущее обращение к этой же модели. Да и даже без кэширования схема после чтения из базы сохраняется в приватном поле. Поэтому поле $post->alias при втором обращении к той же модели может не существовать.

Для избежания таких недоразумений нужно не только отключить кэширование для консольного режима (не указывая schemaCachingDuration и используя заглушку CDummyCache ), но и в теле самой миграции выполнить при необходимости очистку кэша (если он включен) и перзагрузить схему:

Теперь какое бы кэширование ни было включено, проблем с изменёнными полями не возникнет. Можно коммитить миграцию с новой моделью Post и отправлять на центральный сервер.

Другие инструменты и возможности

Вот так мы ознакомились с назначением миграций базы данных, вместе написали свою систему и перешли к управлению изменениями базы в Yii Framework. Но мы не рассмотрели другие готовые инструменты.

Тот же компонент Doctrine Migration, используемый обычно как компонент Doctrine в Symfony Framework, немного отличается по своим доступным операциям для управления миграциями. Все модели в Doctrine (а именно название таблиц, имена и типы полей) дополнительно описываются в конфигурациюнных файлах, что дополнительно даёт нам возможность автоматически генерировать запросы в миграциях. Например, мы добавили новое поле в нашу модель (или сделали индексным существующее) и вызываем команду diff :

Данная консольная команда сравнит схему из конфигурационного файла каждой модели с текущими таблицами в самой базе и сгенерирует миграцию, в которой сама запишет запросы по изменению/добавлению/удалению поля или индекса. Это полезная возможность, но полагаться полностью на неё не стоит, так как вместо переименования одной колонки может сгенерироваться два запроса: на удаление старой и добавление новой, что приведёт к потере значений.

В других языках используются свои инструменты, например rake в Ruby on Rails. Каждый имеет свой синтаксис и спроетирован под свой фреймворк, но общие принципы действия с ними остаются прежними.

На этом пока всё. Если есть какие-либо вопросы или предложения, то напишите их в комментариях.

Не пропускайте новые статьи, бонусы и мастер-классы:

Zend_Db_Adapter

Zend_Db и его родственные классы предоставляют простой интерфейс к базам данных SQL в Zend Framework. Zend_Db_Adapter является базовым классом, который должен использоваться для подключения приложения PHP к СУРБД. Существуют различные классы адаптеров для наиболее часто используемых СУРБД.

Адаптеры Zend_Db создают мост между общим интерфейсом и расширениями PHP от конкретных поставщиков для того, чтобы можно было единовременно писать приложения на PHP и затем переключаться между различными СУРБД с наименьшими усилиями.

Интерфейс класса адаптера подобен интерфейсу расширения » PHP Data Objects (PDO). Zend_Db предоставляет классы адаптеров к драйверам PDO для следующих популярных СУРБД:

IBM DB2 и Informix Dynamic Server (IDS), с использованием расширения » pdo_ibm

MySQL, с использованием расширения » pdo_mysql

Microsoft SQL Server, с использованием расширения » pdo_mssql

Oracle, с использованием расширения » pdo_oci

PostgreSQL, с использованием расширения » pdo_pgsql

SQLite, с использованием расширения » pdo_sqlite

Кроме этого, Zend_Db предоставляет классы адаптеров, использующие расширения PHP для следующих распространенных СУРБД:

MySQL, с использованием расширения » mysqli

Oracle, с использованием расширения » oci8

IBM DB2, с использованием расширения » ibm_db2

Firebird/Interbase, с использованием расширения » php_interbase

Note: Все адаптеры Zend_Db используют расширения PHP. Вы должны иметь включенным соответствующее расширение в вашей среде PHP для использования адаптера Zend_Db. Например, если вы используете какой-либо из адаптеров PDO Zend_Db, то нужно включить как расширение PDO, так и драйвер PDO для используемой вами СУРБД.

Соединение с БД с использованием адаптера

Этот раздел описывает, как создавать экземпляр адаптера БД. Это соответствует созданию соединения с сервером СУРБД из вашего приложения PHP.

Использование конструктора адаптера Zend_Db

Вы можете создавать экземпляр адаптера с помощью его конструктора. Конструктор адаптера принимает единственный аргумент, который является массивом параметров, использующихся для описания соединения.

Example #1 Использование конструктора адаптера

Использование фабрики Zend_Db

Вместо непосредственного использования конструктора адаптера можно создавать экземпляры адаптера, применяя статический метод Zend_Db::factory() . Этот метод динамически загружает файл класса адаптера, используя Zend_Loader::loadClass().

Первым аргументом является строка с базовым именем класса адаптера. Например, строка ‘Pdo_Mysql’ соответствует классу Zend_Db_Adapter_Pdo_Mysql. Вторым аргументом является тот же массив параметров, который вы должны были бы передать конструктору адаптера.

Example #2 Использование метода-фабрики адаптеров

Если вы создали собственный класс, расширяющий Zend_Db_Adapter_Abstract, но не дали ему имя, начинающееся с префикса «Zend_Db_Adapter», то можете использовать метод factory() для загрузки своего адаптера, указав ведущую часть имени класса адаптера с помощью ключа ‘adapterNamespace’ в массиве параметров.

Example #3 Использование метода-фабрики для пользовательского класса адаптера

Использование Zend_Config с фабрикой Zend_Db

Опционально вы можете заменить оба аргумента метода factory() объектом типа Zend_Config.

Если первым аргументом является объект конфигурации, то ожидается, что он имеет свойство с именем adapter , содержащее строку с базовой частью имени класса адаптера. Опционально объект может содержать свойство с именем params и «подсвойствами», соответствующими параметрам адаптера.

Example #4 Использование метода-фабрики адаптеров с объектом Zend_Config

В примере ниже объект Zend_Config создан из массива. Вы можете также загружать данные из внешнего файла с помощью Zend_Config_Ini или Zend_Config_Xml.

Второй аргумент метода factory() может быть ассоциативным массивом, содержащим элементы, которые соответствуют параметрам адаптера. Этот аргумент является опциональным. Если первым аргументом является объект типа Zend_Config, то предполагается, что он содержит все необходимые параметры, и второй аргумент игнорируется.

Параметры адаптера

Список ниже описывает общие параметры, которые распознаются классами адаптеров Zend_Db.

host: строка, содержащая имя хоста или IP сервера БД. Если база данных размещается на том же хосте, что и приложение PHP, то вы можете использовать ‘localhost’ или ‘127.0.0.1’.

username: идентификатор учетной записи для аутентификации подключения к серверу СУРБД.

password: пароль учетной записи для аутентификации подключения к серверу СУРБД.

dbname: имя экземпляра БД на сервере СУРБД.

port: некоторые сервера СУРБД поддерживают сетевые соединения через указанный администратором порт. Данный параметр дает возможность задать порт, с которым приложение PHP будет устанавливать соединение, он должен соответствовать порту, установленному в сервере СУРБД.

options: этот параметр является ассоциативным массивом опций, общих для всех классов Zend_Db_Adapter.

driver_options: этот параметр является ассоциативным массивом дополнительных опций, специфических для данного расширения. Одним из типичных случаев использования этого параметра является установка атрибутов для драйвера PDO.

adapterNamespace: имя начальной части имени класса для адаптера вместо ‘Zend_Db_Adapter’. Используйте его, если нужно использовать метод factory() для загрузки «неZend’овского» класса адаптера БД.


Example #5 Передача фабрике опции перевода регистра (case-folding)

Вы можете установить эту опцию посредством константы Zend_Db::CASE_FOLDING . Она соответствует атрибуту ATTR_CASE в драйверах PDO и IBM DB2, и переводит строковые ключи в результатах запроса в требуемый регистр. Эта опция принимает значения Zend_Db::CASE_NATURAL (значение по умолчанию), Zend_Db::CASE_UPPER и Zend_Db::CASE_LOWER .

Example #6 Передача фабрике опции автоматического заключения в кавычки

Вы можете задавать эту опцию через константу Zend_Db::AUTO_QUOTE_IDENTIFIERS . Если ее значение установлено в TRUE (по умолчанию), то идентификаторы, такие, как имена таблиц, имена столбцов и даже псевдонимы, разграничиваются во всем генерируемом объектом адаптера синтаксисе SQL. Это делает возможным использование идентификаторов, содержащих ключевые слова SQL и специальные символы. Если его значение равно FALSE, то автоматическое заключение в кавычки не производится. Если требуется заключение идентификаторов в кавычки, то оно должно производиться самостоятельно с использованием метода quoteIdentifier() .

Example #7 Передача фабрике опций драйвера PDO

Управление отложенными соединениями

Создание экземпляра класса адаптера не приведет к немедленному соединению с сервером СУРБД. Адаптер сохраняет параметры соединения и производит подключение, когда нужно произвести первый запрос к БД. Это значит, что само по себе создание объекта адаптера производится быстро и занимает мало ресурсов. Вы можете создавать экземпляр адаптера даже в том случае, если не уверены в том, что текущий запрос к вашему приложению требует каких-либо действий с БД.

Если нужно принудительно создать соединение с СУРБД, то используйте метод getConnection() . Этот метод возвращает объект соединения в представлении соответствующего расширения PHP для баз данных. Например, если вы используете какой-либо класс адаптера для драйверов PDO, то getConnection() возвращает объект PDO после того, как он будет инициирован им в качестве «живого» соединения с определенной БД.

Принудительное создание соединения может быть полезным, когда вы хотите отлавливать все исключения, которые бросаются из-за неправильных параметров доступа или других ошибок соединения с сервером СУРБД. Эти исключения не бросаются до тех пор, пока не создается соединение, поэтому можно упростить код приложения, обрабатывая исключения в одном месте вместо того, чтобы делать это каждый раз, когда производится первый запрос к БД.

Example #8 Обработка исключений при соединении

Пример базы данных

В документации к классам Zend_Db мы использовали набор простых таблиц для того, чтобы проиллюстрировать использование классов и их методов. Эти таблицы должны были хранить информацию для отслеживания ошибок в проекте разработки ПО. База данных содержит четыре таблицы:

accounts (учетные записи) хранит информацию о всех пользователях системы отслеживания ошибок.

products (продукты) хранит информацию обо всех программных продуктах, для которых могут отслеживаться ошибки.

bugs (ошибки) хранит информацию об ошибках, включая текущее состояние ошибки, лицо, сообщившее об ошибке, лицо, которому назначено устранение ошибки и лицо, которому назначена проверка устранения ошибки.

bugs_products хранит связи между ошибками и продуктами. Она реализует связь «многие-ко-многим», потому что одна ошибка может относиться к нескольким продуктам, и один продукт может иметь множество ошибок.

Следующий псевдокод для определения данных SQL описывает таблицы в этой базе данных. Это таблицы интенсивно используются в unit-тестах для Zend_Db.

Также обратите внимание, что таблица bugs содержит несколько внешних ключей, ссылающихся на таблицу accounts . Для одной ошибки эти внешние ключи могут ссылаться на разные строки в таблице accounts .

Диаграмма ниже иллюстрирует физическую модель данных для этой базы данных.

Чтение результатов запроса

Этот раздел описывает методы класса адаптера, с помощью которых вы можете производить запросы SELECT и извлекать их результаты.

Извлечение полного набора результатов

Вы можете запустить запрос SELECT и извлечь его результаты за один шаг, используя метод fetchAll() .

Первым аргументом этого метода должна быть строка, содержащая оператор SELECT. Также первым аргументом может быть объект класса Zend_Db_Select. Адаптер автоматически преобразует этот объект в строковое представление оператора SELECT.

Вторым аргументом fetchAll() должен быть массив значений для подстановки вместо меток заполнения (placeholders) в операторе SQL.

Example #9 Использование fetchAll()

Изменение режима извлечения

По умолчанию fetchAll() возвращает массив строк, каждая из которых представлена ассоциативным массивом. Ключами ассоциативных массивов являются имена столбцов или псевдонимы столбцов, определенные в данном запросе на выборку.

Вы можете задать другой стиль извлечения результатов, используя метод setFetchMode() . Поддерживаемые режимы идентифицируются константами:

Zend_Db::FETCH_ASSOC: возвращает данные в массиве ассоциативных массивов. Ключами массива являются имена столбцов в виде строк. Это режим извлечения, используемый по умолчанию в классах Zend_Db_Adapter.

Обратите внимание, что если ваш список выборки содержит столбцы с одинаковыми именами, например, если они из разных таблиц в JOIN-е, то в ассоциативном массиве может быть только одна запись для этого имени. Если вы используете режим FETCH_ASSOC, то должны задавать псевдонимы столбцов в своем запросе SELECT для того, чтобы для всех столбцов были свои уникальные ключи.

По умолчанию эти строки возвращаются так же, как если бы они были возвращены драйвером БД. Как правило, это синтаксис столбцов для данного сервера СУРБД. Вы можете задать регистр для этих строк, используя опцию. Zend_Db::CASE_FOLDING . Задавайте его во время инстанцирования адаптера. См. Передача фабрике опции перевода регистра (case-folding).

Zend_Db::FETCH_NUM: возвращает данные в массиве массивов. Массив индексируется целочисленными значениями в соответствии с позицией данного поля в списке выборки запроса.

Zend_Db::FETCH_BOTH: возвращает данные в массиве массивов. Ключами массива являются как строки, так и целочисленные значения. Число элементов в массиве получается в два раза больше, чем если бы использовались FETCH_ASSOC или FETCH_NUM.

Zend_Db::FETCH_COLUMN: возвращает данные в массиве значений. Значение в каждом массиве является значением, возвращенным из одного столбца результата выборки. По умолчанию это первый столбец, индексированный нулем.

Zend_Db::FETCH_OBJ: возвращает данные в массиве объектов. По умолчанию используется встроенный в PHP класс stdClass. Столбцы результата выборки доступны в качестве открытых свойств этого объекта.

Example #10 Использование setFetchMode()

Извлечение результатов выборки в виде ассоциативного массива

Метод fetchAssoc() возвращает данные в массиве ассоциативных массивов безотносительно того, какое значение вы установили для режима извлечения.

Example #11 Использование fetchAssoc()

Извлечение единственного столбца из результатов выборки

Метод fetchCol() возвращает данные в массиве значений безотносительно того, какое значение вы установили для режима извлечения. Он возвращает только первый столбец из возвращенных запросом. Все остальные столбцы, возвращенные запросом, не учитываются. Если вам нужно извлечь столбец, отличный от первого, то см. Извлечение одного столбца из набора результатов.

Example #12 Использование fetchCol()

Извлечение пар ключ-значение из результатов выборки

Метод fetchPairs() возвращает данные в массиве пар ключ-значение, Ключ ассоциативного массива берется из первого столбца, возвращенного запросом SELECT. Значение берется из второго столбца, возвращенного запросом SELECT. Все остальные столбцы, возвращенные запросом, не учитываются.

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

Example #13 Использование fetchPairs()

Извлечение единственной строки из результатов выборки

Метод fetchRow() возвращает данные с использованием текущего режима извлечения, но возвращает только первую строку из результатов выборки.

Example #14 Использование fetchRow()

Извлечение единственного скалярного значения из результатов выборки

Метод fetchOne() является как бы комбинацией методов fetchRow() и fetchCol() — он возвращает значение первого столбца в первой строке из результатов выборки. Таким образом, он возвращает одно скалярное значение, а не массив или объект.

Example #15 Использование fetchOne()

Изменение данных в БД

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

Добавление данных

Вы можете добавлять новые строки в таблицы в своей базе данных, используя метод insert() . Первым аргументом этого метода является строка с именем таблицы, а вторым аргументом — ассоциативный массив с именами столбцов и соответствующими им значениями.

Example #16 Добавление в таблицу

Те столбцы, которые не были включены в массив данных, не передаются базе данных. Таким образом, они следуют тем же правилам, что и SQL-оператор INSERT: если столбец имеет предложение DEFAULT, то он принимает это значение в созданной строке, иначе остается в состоянии NULL.

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

Иногда бывает необходимо, чтобы часть значений в массиве данных трактовалась как SQL-выражения, в этом случае они не должны заключаться в кавычки. По умолчанию все данные, переданные в виде строк, трактуются как строковые литералы. Для того, чтобы указать, что данное значение является SQL-выражением (а значит, не должно заключаться в кавычки), передавайте его в массиве данных в виде объекта типа Zend_Db_Expr вместо простой строки.

Example #17 Добавление выражений в таблицу


Получение сгенерированного значения

Некоторые СУРБД поддерживают автоинкремент первичных ключей. Таблица, описанная определенным образом, автоматически генерирует значение первичного ключа во время добавления новой строки. Возвращаемое методом insert() значение не является последним добавленным идентификатором, потому что таблица может не иметь автоинкрементных столбцов. Вместо этого возвращаемое значение является количеством затронутых строк (обычно 1).

Если ваша таблица определена с автоинкрементным первичным ключом, то вы можете вызывать метод lastInsertId() после добавления. Этот метод возвращает последнее значение, сгенерированное в области видимости текущего соединения с БД.

Example #18 Использование lastInsertId() для автоинкрементного ключа

Некоторые СУРБД поддерживают объекты последовательностей (sequence object), которые генерируют уникальные значения для использования в качестве значений первичных ключей. Для поддержки последовательностей lastInsertId() принимает два необязательных строковых аргумента. Эти аргументы служат для передачи имен таблицы и столбца, при этом предполагается, что вы следуете соглашению, по которому имя последовательности состоит из имен таблицы и столбца, для которых эта последовательность генерирует значения, и суффикса «_seq». Это соглашение основано на используемом системой PostgreSQL при именовании последовательностей для столбцов SERIAL. Например, таблица «bugs» с первичным ключом «bug_id» должна использовать последовательность с именем «bugs_bug_id_seq».

Example #19 Использование lastInsertId() для последовательности

Если имя вашего объекта последовательности не следует этому соглашению по именованию, то используйте метод lastSequenceId() . Этот метод принимает один строковой аргумент, через который передается точное имя последовательности

Example #20 Использование lastSequenceId()

Для тех СУРБД, которые не поддерживают последовательности, включая MySQL, Microsoft SQL Server и SQLite, аргументы метода lastInsertId() игнорируются, и возвращается самое последнее значение, сгенерированное для любой таблицы через оператор INSERT в течение данного соединения. Для этих типов СУРБД метод lastSequenceId() всегда будет возвращать NULL.

Note: Почему не используется «SELECT MAX(id) FROM table»?
Иногда этот запрос возвращает последнее значение первичного ключа, добавленное в таблицу. Однако этот способ небезопасен в условиях, когда несколько клиентов добавляют записи в базу данных. Может случиться (и должно происходить в конечном итоге) так, что другой клиент добавляет другую строку в короткий промежуток времени между добавлением строки, производимым вашим приложением-клиентом БД, и вашим запросом для получения значения MAX(id). Таким образом, это возвращаемое значение не будет соответствовать добавленной вами строке, вместо этого оно будет соответствовать строке, добавленной другим клиентом. Нет способа определить, когда это происходит.
Использование высокого уровня изоляции транзакций, такого, как «repeatable read», может уменьшить этот риск, но некоторые СУРБД не поддерживают требуемую для этого изоляцию транзакций, либо намеренно используется более низкий уровень изоляции транзакций в приложении.
Использование выражения наподобие «MAX(id)+1» для генерации нового значения первичного ключа тоже небезопасно, так как два клиента могут сделать этот запрос одновременно, и оба будут использовать одно и то же полученное значение для своей последующей операции INSERT.
Все СУРБД предоставляют механизмы для генерации уникальных значений и возвращения последних сгенерированных значений. Эти механизмы работают вне области видимости транзакций, поэтому нет вероятности того, что оба клиента сгенерируют одно и то же значение, или что значение, сгенерированное другим клиентом, будет возвращено вашему клиенту как последнее сгенерированное им в его соединении.

Обновление данных

Вы можете обновлять строки в таблице БД, используя метод update() адаптера. Этот метод принимает три аргумента: первый является имением таблицы, второй — ассоциативным массивом столбцов, которые требуется изменить, и значений, которые требуется присвоить этим столбцам.

Значения в массиве данных интерпретируются как строковые константы. Информацию об использовании выражений SQL в массиве данных см. в разделе Добавление данных.

Третий аргумент является строкой, содержащей выражение SQL, которое используется в качестве условия, при выполнении которого строка должна изменяться. Значения и идентификаторы в этом аргументе не заключаются в кавычки и не экранируются. Вы ответственны за то, чтобы все динамическое содержимое было безопасным образом включено в эту строку. Информацию о методах, которые помогут вам в этом, см. в разделе Заключение в кавычки значений и идентификаторов.

Возвращаемое значение является числом строк, затронутых в операции обновления.

Базы данных и Doctrine ORM¶

Symfony не предоставляет компонента для работы с БД, но предоставляет тесную интеграцию со сторонней библиотекой под названием Doctrine.

Эта статья полностью описывает использование Doctrine ORM. сли вы предпочитаете использовать чистые запросы БД, см. статью «How to Use Doctrine DBAL«.

Вы также можете сохранять данные в MongoDB, используя библиотеку Doctrine ODM. См. документацию «DoctrineMongoDBBundle».

Установка Doctrine¶

Для начала, установите Doctrine, а также MakerBundle, который поможет сгенерировать код:

Конфигурация базы данных¶

Информация соединения БД хранится в виде переменной окружения под названием DATABASE_URL . Для разработки вы можете найти и настроить её внутри .env :

Если имя пользователя, пароль или имя БД содержат любой символ, считающийся особенным в URI (например, ! , @ , $ , # ), то вы должны зашифровать его. См. RFC 3986, чтобы увидеть весь перечень зарезервированных символов, или используйте функцию urlencode для их шифрования.

Теперь, когда ваши параметры соединения установлены, Doctrine может создать для вас БД db_name :

Существует больше опций в config/packages/doctrine.yaml , которые вы можете сконфигурировать, включая вашу server_version (например, 5.7, если вы используете MySQL 5.7), которые могут повлиять на то, как функционирует Doctrine.

Существует множество других команд Doctrine. Запустите php bin/console list doctrine , чтобы увидеть полный список.

Создание класса сущности (Entity)¶

Предположим, что вы создаёте приложение, в котором необходимо отображать товары. Даже не задумываясь о Doctrine или базах данных, вы уже знаете, что вам необходим объект Product , чтобы представить эти товары. Используйте команду make:entity , чтобы создать этот класс:

Теперь у вас есть новый файл src/Entity/Product.php :

Этот класс называется «сущность», И вскоре вы сможете сохранять и запрашивать объекты Product в таблице product в вашей базе данных.

Отображение большего количества полей / колонок¶

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

Давайте дадим классу сущности Product ещё три свойства и свяжем их с колоками в БД. Это обычно делается с помощью аннотаций:

  • Annotations

Doctrine поддерживает широкий спектр различных типов полей, каждый с собственными опциями. Чтобы увидеть полный перечень типов и опций, см. документацию типов отображения Doctrine. Если вы хотите использовать XML вместо аннотаций, добавьте type: xml и dir: ‘%kernel.project_dir%/config/doctrine’ в отображения сущностей в вашем файле config/packages/doctrine.yaml .

Будьте осторожны, чтобы не использовать зарезервированные ключевые слова SQL в качестве названий колонок вашей таблицы (например, GROUP или USER ). См. документацию зарезериврованных ключевых слов SQL Doctrine, чтобы узнать детали о том, как их избежать. Или, сконфигурируйте имя таблицы с @ORM\Table(name=»groups») над классом, или сконфигурируйте названия колонок с опцией name=»group_name» .

Миграции: Создание таблиц / схемы базы данных¶

Класс Product полностью сконфигурирован и готов к сохранению таблицы product . Конечно, ваша БД на самом деле ещё не имеет таблицы product . Чтобы добавить её, вы можете использовать DoctrineMigrationsBundle, который уже установлен:

Если всё сработало, то вы должны увидеть что-то вроде:

Если вы откроете этот файл, то увидите, что он содержит SQL, необходимый для обновленя вашей БД! Чтобы запустить этот SQL, выполните ваши миграции:

Эта команда выполняет все файлы миграции, которые ещё не были запущены в вашей БД.

Миграции и добавление полей¶

Но что, если вам нужно добавить новое свойство поля в Product , например, description ?

Новое свойство отображено, но ещё не существует в таблице product . Не проблема! Просто сгенерируйте миграцию:

В этот раз, SQL в сгенерированном файле будет выглядеть так:

Система миграции умна. Она сравнивает все ваши сущности с текущим состоянием БД и генерирует необходимый для их синхронизации SQL! Как и раньше, выполните ваши миграции:

Это выполнит только один новый файл миграции, так как DoctrineMigrationsBundle знает, что первая миграция уже была выполнена ранее. За кулисами, она автоматически управляет таблицей migration_versions , чтобы следить за этим.

Каждый раз, когда вы вносите изменения в свою схему, запускайте эти две команды, чтобы сгенерировать миграцию и потом выполнить её. Не забудьте фиксировать файлы миграции и запускать их выполнение при развёртывании.

Создание геттеров и сеттеров¶

Doctrine теперь знает, как сохранять объект Product в БД. Но сам класс ещё бесполезен. Все свойства — private , поэтому увидеть данные в них невозможно!

По этой причине, вам нужно создать публичные геттеры и сеттеры для всех полей, которые вам нужно изменить снаружи класса. Если вы используете IDE вроде PhpStorm, то он может сгенерировать их за вас. В PhpStorm, поместите ваш курсор где-либо в классе, а потом перейдите в Код -> Сгенерировать меню и выберите «Геттеры и сеттеры»:

Обычано вам не нужен метод setId() : Doctrine установит его за вас автоматически.

Сохранение объектов в базе данных¶

Пора сохранить объект Product в базе данных! Давайте создадим новый контроллер для экспериментов:

Внутри контроллера, вы можете создать новый объект Product , установить данные в нём и сохранить его!:

Поздравляем! Вы только что создали ваш первый ряд в таблице product . Чтобы доказать это, вы можете запросить БД напрямую:

Рассмотрите предыдущий пример более детально:

  • Строчка 16 Метод $this->getDoctrine()->getManager() получает объект Doctrine менеджер сущностей (entity manager), который является самым важным объектом Doctrine. Он отвечает за сохранение в базу данных, и получение объектов из базы данных.
  • Строxки 18-21 В этой части вы создаёте объект $product и работаете с ним, как и с любым другим обычным PHP-объектом.
  • Строчка 24 Вызов persist($product) сообщает Doctrine, чтобы он «управлял» объектом $product . Это не создает запрос в базу данных.
  • Строка 27 Когда вызывается метод flush() , Doctrine просматривает все объекты, которыми он управляет, чтобы узнать, нужно ли их сохранять в базу данных. В этом примере, объект $product не существует в базе данных, так что менеджер сущностей выполняет запрос INSERT , создавая новую строку в таблице product .

Если вызов flush() не удается, то вызывается исключение Doctrine\ORM\ORMException . См. Транзакции и параллелизм (Concurrency).

Процесс одинаков и для создания, и для обновления объектов, рабочий процесс всегда одинаков: Doctrine достаточно умна для того, чтобы знать, стоит ей INSERT или UPDATE вашу сущность.

Извлечение объектов из базы данных¶

Извлечение объекта обратно из БД ещё проще. Представьте, что вы хотите перейти в /product/1 , чтобы увидеть ваш новый товар:

Цукерберг рекомендует:  Как настроить комп соседу
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих