[modExtra] Update таблиц своего компонента
Добрый день!
Все мы знаем, что MODX прекрасен мощью решений с помощью компонентов. Я, например, всегда отдаю заказчику сайт в виде компонента, содержащего нужные элементы, зависимости от других компонентов, инициализацию нужных опций и т.д. В этом мне всегда помогал modExtra.
Но вот в один прекрасный момент я задумался, как сделать пользовательские таблицы в своём компоненте таким образом, чтобы при апдейте компонента они были способны расширяться, да и вообще поддерживаться в актуальном состоянии.
Итак задача:
В этом нам помогут ресолверы таблиц, заготовка для которых есть в modExtra и хранится она тут: _build/resolvers/resolve.tables.php. Мы видим, что на каждую таблицу вызывается метод
Кроме этого можно посмотреть на решение Василия в Tickets, где он «руками» пытается недостающие поля таблиц добавить, а лишние удалить.
Порывшись в документации и коде MODX понял, что нас полностью бы устроило добавление строчки
Хочу предложить своё решение, которое должно подойти в подавляющем большинстве случаев:
Решение пусяковое, но оно делает разработку компонента несколько комфортнее.
Спасибо за внимание.
Все мы знаем, что MODX прекрасен мощью решений с помощью компонентов. Я, например, всегда отдаю заказчику сайт в виде компонента, содержащего нужные элементы, зависимости от других компонентов, инициализацию нужных опций и т.д. В этом мне всегда помогал modExtra.
Но вот в один прекрасный момент я задумался, как сделать пользовательские таблицы в своём компоненте таким образом, чтобы при апдейте компонента они были способны расширяться, да и вообще поддерживаться в актуальном состоянии.
Итак задача:
- У нас есть файл схемы mycomponent.mysql.schema.xml, который поставляется вместе с компонентом (или его новой версией) и содержит актуальную информацию о структуре пользовательских таблиц.
- У нас есть БД в которой может не быть наших таблиц (установка с нуля), а могут быть (как правильной, так и неправильной структуры).
В этом нам помогут ресолверы таблиц, заготовка для которых есть в modExtra и хранится она тут: _build/resolvers/resolve.tables.php. Мы видим, что на каждую таблицу вызывается метод
$manager->createObjectContainer($tmp);
Если залезть в его потроха, то можно увидеть, что он находит (соответствующую пользовательскому классу) таблицу в БД и, если её не существует, создаёт её, в противном случае ничего не делает. Что ж, неплохое начало. Но на этом решение modExtra заканчивается.Кроме этого можно посмотреть на решение Василия в Tickets, где он «руками» пытается недостающие поля таблиц добавить, а лишние удалить.
Порывшись в документации и коде MODX понял, что нас полностью бы устроило добавление строчки
$manager->alterObjectContainer($tmp);
, но у этой функции есть только прототип, а реализации для mySql пока нет.Хочу предложить своё решение, которое должно подойти в подавляющем большинстве случаев:
foreach ($objects as $tmp) {
$manager->createObjectContainer($tmp);
$exist = array();
$c = $modx->prepare("SHOW COLUMNS IN {$modx->getTableName($tmp)}");
$c->execute();
while ($cl = $c->fetch(PDO::FETCH_ASSOC)) {
$exist[$cl['Field']] = $cl['Field'];
}
$fieldMeta = $manager->xpdo->getFieldMeta($tmp, true);
while (list($key, $meta)= each($fieldMeta)) {
if (array_key_exists($key, $exist)) {
unset($exist[$key]);
$manager->alterField($tmp, $key);
} else {
$manager->addField($tmp, $key);
}
}
foreach ($exist as $key) {
$manager->removeField($tmp, $key);
}
}
break;
Несколько комментариев:- Сначала мы запоминаем название всех существующих колонок.
- Потом пробегаемся по схеме и выполняем одно из действий: добавить колонку, удалить или обновить.
- Если мы делаем апдейт, а схема не изменилась, то всегда будет вызываться alterField, который проверит изменился ли тип поля и исправит его ТОЛЬКО в том случае, если это необходимо.
- Неважно, есть в таблице данные или нет, скрипт правильно отработает и ничего не пострадает.
Решение пусяковое, но оно делает разработку компонента несколько комфортнее.
Спасибо за внимание.
Комментарии: 12
Классно. Может, создать пулл-реквест в modExtra Василия? Думаю, он примет. Удобно будет всем.
Василий уехал в отпуск. Пусть человек отдохнет.
П.С. А ключик от modExtra он мне оставил.
П.С. А ключик от modExtra он мне оставил.
Ну раз уж пошла такая пьянка, то я бы тогда добавил еще и это
$schemaFile = MODX_CORE_PATH . 'components/modextra/model/schema/modextra.mysql.schema.xml';
$objects = array();
if (is_file($schemaFile)) {
$schema = new SimpleXMLElement($schemaFile, 0, true);
if (isset($schema->object)) {
foreach ($schema->object as $object) {
$objects[] = (string)$object['class'];
}
}
unset($schema);
} else {
$modx->log(modX::LOG_LEVEL_ERROR, 'Could not get classes from schema file.');
}
Тады вааще все автоматом работать будет. А то я постоянно забываю добавлять классы.
А вот так еще и индексы добавляем автоматом, чтоб уж совсем голова не болела.
$schemaFile = MODX_CORE_PATH . 'components/modextra/model/schema/modextra.mysql.schema.xml';
if (is_file($schemaFile)) {
$schema = new SimpleXMLElement($schemaFile, 0, true);
if (isset($schema->object)) {
foreach ($schema->object as $object) {
$objName = (string)$object['class'];
$objects[] = $objName;
// Indexes
foreach ($object->index as $index) {
$indexes[$objName][] = (string) $index['name'];
}
}
}
unset($schema);
} else {
$modx->log(modX::LOG_LEVEL_ERROR, 'Could not get classes from schema file.');
}
//Работаем с таблицами
foreach ($objects as $tmp) {
$manager->createObjectContainer($tmp);
...
}
//Работаем с индексами
foreach ($ClassIndexes as $class=>$indexes) {
foreach($indexes as $index) {
$manager->addIndex($class,$index);
}
}
Сергей. В вашем закоммиченом коде ошибка.
Замените, пожалуйста unset($tableFields[$field)]); на unset($tableFields[array_search($field, $tableFields)]);
И в аналогичном месте с индексами unset($indexes[$name]); на unset($indexes[array_search($name, $indexes)]);
Массивы же теперь стали не ассоциативные. В вашем примере в списке филдов и индексов реально не происходит удаления, зато потом происходит реальное удаление оставшихся в списке филдов и индексов.
Спасибо.
Замените, пожалуйста unset($tableFields[$field)]); на unset($tableFields[array_search($field, $tableFields)]);
И в аналогичном месте с индексами unset($indexes[$name]); на unset($indexes[array_search($name, $indexes)]);
Массивы же теперь стали не ассоциативные. В вашем примере в списке филдов и индексов реально не происходит удаления, зато потом происходит реальное удаление оставшихся в списке филдов и индексов.
Спасибо.
Михаил, обновитесь, уже исправлено как несколько дней.
Отличный способ, я тоже к чему-то такому же пришел при работе над одним проектом. Только там нужно было держать в форме таблицы товаров при изменении плагинов miniShop2.
Думаю, можно это и добавить в modExtra, как образец. Вместе с предложениями Сергея по индексам.
Думаю, можно это и добавить в modExtra, как образец. Вместе с предложениями Сергея по индексам.
Позволил себе добавить это решение на Github.
Теперь в большинстве случаев вообще не нужно лезть в этот ресолвер. Спасибо Михаилу.
П.С. Также я добавил код удаления таблиц при деинсталяции. Он закомментирован. Если вдруг кому-то нужно будет удалять таблицы, нужно просто раскомментировать.
Теперь в большинстве случаев вообще не нужно лезть в этот ресолвер. Спасибо Михаилу.
П.С. Также я добавил код удаления таблиц при деинсталяции. Он закомментирован. Если вдруг кому-то нужно будет удалять таблицы, нужно просто раскомментировать.
Вот с удалением таблиц вопрос сложный. Неоднократно видел, что при возникновении проблем, люди удаляют и заново устанавливают компонент.
С этим изменением в подобных случаях будут удалены и все данные в таблицах, чему многие не обрадуются.
А, туплю, он же закомментирован по умолчанию. Тогда всё ок, вопросов нет.
С этим изменением в подобных случаях будут удалены и все данные в таблицах, чему многие не обрадуются.
А, туплю, он же закомментирован по умолчанию. Тогда всё ок, вопросов нет.
Поэтому он и закомментирован. При разработке и тестировании пакета периодически приходится удалять вручную таблицы. Для чистоты эксперимента, так сказать.
А разработчик должен понимать всю ответственность такого решения.
А разработчик должен понимать всю ответственность такого решения.
Да-да, не проснулся я еще =)
Спасибо за доработку и коммит в modExtra. Отлично получилось!
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.