DB.Schema

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

Для настройки и автоматической синхронизации структуры базы данных можно использовать модуль DB.Schema, с помощью которого можно задавать схему базы данных в виде массива и автоматически ее синхронизировать. Разработчику не нужно пользоваться SQL-менеджерами и т.п. Структура базы данных задается в PHP-коде, и при заливке на хостинг она автоматически синхронизируется.

Как пользоваться

Основной порядок действий:

1. Заводим файл с именем Schema.php (app/components/NameComponent/lib/NameComponent/Schema.php).

2. В нем загружаем модуль DB.Schema. Заводим класс, который будет называться Component_NameComponent_Schema, он стандартно реализует интерфейс Core_ModuleInterface.

3. Внутри него достаточно определить один статический метод run(), в котором вызывать DB_Schema::process().

Последний метод и осуществляет изменение базы в соответствии с передаваемым описанием схемы данных. При первом запуске он будет создавать, а при последующих, если потребуется — менять схему.

// components/Redirector/lib/Redirector/Schema.php
Core::load('DB.Schema');

class Component_Redirector_Schema implements Core_ModuleInterface
{
	static function run()
	{
		DB_Schema::process(array(
			'redirector' => array(
				'mysql_engine' => 'MyISAM',
				'columns' => array(
					array('name' => 'id', 'type' => 'serial'),
					array('name' => 'url_from', 'type' => 'varchar', 'length' => 255, 'default' => '', 'not null' => true),
					...
				),
				'indexes' => array(
					array('type' => 'primary key', 'columns' => array('id')),
					array('name' => 'idx_from', 'columns' => array('url_from')),
					...
				),
			),
		));
	}
}

Функция DB_Schema::process() имеет два параметра:

  • Первый параметр - массив таблиц, в котором ключами являются имена таблиц, а значениями - массивы, описывающие структуру таблиц. Т.е. одним вызовом мы можем создать (синхронизировать) сразу несколько таблиц.
  • Второй параметр - подключение к БД. Он не обязательный и имеет смысл только на проектах с несколькими подключениями.

После этого вызова:

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

Так же можно указывать действия для удаления:

'remove_columns' => array(
      array('name' => 'content'),
      array('name' => 'select')
)

Аналогичное описание для индексов.

Такой метод несколько устарел, но его можно применять при необходимости.

Описание структуры таблицы

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

  • description - описание таблицы (пока не поддерживается).
  • mysql_engine - параметры специфичные для MySQL (например, MyISAM, InnoDB).
  • columns - массив, каждый из которых описывает колонку (обязательные параметры name и type).
    • description: описание,
    • name: имя колонки,
    • mysql_definition, pgsql_definition ... : строка определения колонки, если задано, то все последующие опции игнорируются.
    • type' - тип. Может быть: 'char', 'varchar', 'text', 'blob', 'int', 'timestamp', 'datetime', 'date','float', 'numeric', 'serial'. Используйте 'serial' для auto incrementing колонок, для MySQL - это аналогично 'INT auto_increment'.
    • mysql_type, pgsql_type, sqlite_type, etc. - тип, специфичный для конкретной БД.
    • size - размер: 'tiny', 'small', 'medium', 'normal', 'big'. Аналогично TINYINT TINYTEXT и т.д. в MySQL.
    • not null - по умолчанию false.
    • default - значение по умолчанию для колонки.
    • default_quote - помещать ли значение по умолчанию в кавычки. По умолчанию, если default строка, то true.
    • length - длина для таких типов как 'char', 'varchar' or 'text'.
    • unsigned - по умолчанию false.
    • precision, scale - для типа 'numeric'.
  • indexes - массив массивов описывающие индексы. Каждый индекс может содержать:
    • name: имя индекса,
    • type: тип: null, primary key, unique.
    • columns: массив колонок входящих в индекс, каждая может быть строкой с именем колонки или массивом, первый элемент которого имя, а второй длина.

Кэшируемый запуск cached_run()

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

Для этого предусмотрен метод CMS::cached_run(). Как видно из названия, это - кешируемый запуск. В качестве параметра в эту функцию передается имя модуля.

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

Таким образом, после описания схемы в initialize() модуля Component.MyComponent произвести кешированный запуск:

CMS::cached_run('Component.MyComponent.Schema');

Использование событий

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

db.schema.execute($data) | db.schema.execute($data['name'], $data)
(../tao/lib/DB/Schema.php $data - массив DB.Schema)
Вызывается при обработке DB.Schema в методе execute() перед изменением данных. Позволяет добавить поля, столбцы в таблицу, внести какие-то изменения. Для конкретной таблицы необходимо указать ее имя в качестве аргумента ($data['name']).

  • параметры: $data['name'] - имя таблицы

db.schema.after_execute($data) | db.schema.after_execute($data['name'], $data)
(../tao/lib/DB/Schema.php $data - массив DB.Schema)
Вызывается при обработке DB.Schema в методе execute() после изменения данных. Позволяет добавить поля, столбцы в таблицу, внести какие-то изменения. Для конкретной таблицы необходимо указать ее имя в качестве аргумента ($data['name']).

  • параметры: $data['name'] - имя таблицы.

Хранение схемы в конфигурационных настройках

Описание структуры таблиц можно хранить в конфигурационных настройках компонента в файле /components/MyComponent/config/schema.php. Формат описания возвращаемого массива такой же, как и описание в методе DB_Schema::process().

// components/Delivery/config/schema.php

return array(
	'subscribers' => array(
		'mysql_engine' => 'MyISAM',
		'columns' => array(
			'id' => array('type' => 'serial'),
			'email' => array('type' => 'varchar', 'length' => 255, 'default' => '', 'not null' => true),
			'idate_subscribe' => array('type' => 'int', 'default' => '0', 'not null' => true),
			'idate_confirm' => array('type' => 'int', 'default' => '0', 'not null' => true),
			'idate_off' => array('type' => 'int', 'default' => '0', 'not null' => true),
		),
		'indexes' => array(
			array('type' => 'primary key', 'columns' => array('id')),
			array('name' => 'idx_idate_confirm_off', 'columns' => array('idate_confirm', 'idate_off')),
		),
	),
);

Достаточно описать схему, дальше система автоматически будет обращаться к ней.

CMS.Fields sqltype

При описании полей в качестве параметра можно передать массив schema.

$fields = array(
    'coords' => array(
        'type' => 'maps',
        'schema' => array(
            'columns' => array(
                'lat' => array(
                    'type' => 'float',
                ),
                'lon' => array(
                    'type' => 'float',
                ),
            ),
            'indexes' => array(
                ...
            )
        )
    ),
);

Но такой подход часто не удобен и достаточно громоздкий.

Для упрощения был введен параметр 'sqltype', который представляет из себя просто строку. Формат строки:

type[(length)] [null] [index][/parms]
index: index[_name][(columns|length)]
columns: column_name|column_name:length
parms: parmname=value,parmname=value,...

Например,

'sqltype' => 'serial'

Здесь мы указали только тип, будет создано автоинкрементное поле и primary key. В качестве типа можно указать следующие:

Тип данныхОписание
intЦелое число. По умолчанию 0.
tinyintОчень маленькое целое число. Размер: tiny.
charСтрока фиксированной длины, которая справа дополняются пробелами до указанной длины, при хранении.
varcharСтрока переменной длины (конечные пробелы удаляются при сохранении).
textТекстовая строка. Может быть использована во всех 4 размерах: tinytext, text, mediumtext, longtext.
blobМассив двоичных данных. Может быть использован во всех 4 размерах: tinyblob, blob, mediumblob и longblob.
timeВремя. Хранит поле в виде «HH:MM:SS», но позволяет присваивать значения столбцам TIME с использованием строки или числа.
datetimeДата и время. По умолчанию формат: 0000-00-00 00:00:00.
dateДата. По умолчанию формат: 0000-00-00.
serialАвтоинкрементное поле. Для MySQL это аналогично INT auto_increment.
float/doubleЧисло с плавающей запятой. По умолчанию: 0.0.
timestampДата и время. Диапазон от 1970-01-01 00:00:00 до ~2037 г.
priceДля указания цены. Тип: numeric. По умолчанию: 0.00.
numericЧисла с точностью, указываемой пользователем.

Правила описания типов

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

'sqltype' => 'varchar(255)'

По умолчанию создаваемая колонка в базе всегда NOT NULL, если по какой-то причине необходимо разрешить нулевое значение, то достаточно добавить null в описание.

'sqltype' => 'varchar(255) null'

Через "/" можно указывать любые параметры допустимые в DB.Schema.

'sqltype' => 'int null/default=1'

В данном случае мы указали значение по умолчанию 1.

Для создания индекса по колонке достаточно добавить index в описание

'sqltype' => 'int index'

Имя индекса в таком случае формируется автоматически, если это не устраивает, то через знак _ можно указать свое имя.

'sqltype' => 'int index_my_index_name'

Тогда будет создан индекс с именем my_index_name.

В скобках можно указать длину индекса.

'sqltype' => 'text index(128)'

Если необходимо создать индекс по нескольким колонкам, то их можно перечислить в скобках.

'is_active' => array(
   ...
),
'status' => array(
   'sqltype' => 'int index(status,is_active)'
)

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

'title' => array(
   ...
),
'body' => array(
   'sqltype' => 'bigtext index(body:255,title:128)'
)

Если в качестве типа поля указать empty, то колонка в базе создана не будет, это можно использовать для создания индексов в сложных случаях. Например, когда primary key составной

'id' => array(
    'sqltype' => 'empty index_primary(user_id, group_id)',
)

Если в качестве имени индекса указать primary, то будет создан primary key.

В CMS.Fields также доступен параметр sqltypes, который представляет из себя массив sqltype-ов. Это удобно, если значения поля необходимо хранить в нескольких колонках.

'coords' => array(
    'sqltypes' => array(
        'lat' => 'float',
        'lon' => 'float',
    )
)
Метки: ORM, Schema
13.12.2016
Все статьи