Drupal → Создание простейшего модуля управления данными

20.10.2010

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

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

Список альбомов

Первым делом создаём файл album.install, в котором описываем схему таблицы и реализуем хуки для ёё создания/удаления:

/**
 * Реализация hook_schema()
 * Описание таблицы `albums` с тремя полями - идентификатор альбома, название, год выхода
 */
function album_schema()
{
    $schema['albums'] = array(
        'fields' => array(
            'aid' => array(
                'description' => 'Album ID',
                'type' => 'serial',
                'unsigned' => true,
                'not null' => true,
            ),
            'title' => array(
                'description' => 'Album title',
                'type' => 'varchar',
                'length' => 100,
                'not null' => true,
            ),
            'year' => array(
                'description' => 'Album year',
                'type' => 'int',
                'unsigned' => true,
                'not null' => true,
            ),
        ),
        'primary key' => array('aid'),
    );
    
    return $schema;
}

/**
 * Реализация hook_install()
 * Создание таблицы `albums` при первом включении модуля
 */
function album_install()
{
    drupal_install_schema('album');
}
 
/**
 * Реализация hook_uninstall()
 * Удаление таблицы `albums` при деинсталяции модуля
 */
function album_uninstall()
{
    drupal_uninstall_schema('album');
}

В файле album.module регистрируем адреса, по которым будет осуществляться управление данными:

/**
 * Реализация hook_menu()
 */
function album_menu()
{
    // список альбомов
    $items['album'] = array(
        'title' => 'Albums',
        'page callback' => 'album_list',
        'access arguments' => array('administer site configuration'),
    );
 
    $items['album/list'] = array(
        'title' => 'Albums list',
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'weight' => 1,
    );
 
    // форма добавления альбома
    $items['album/add'] = array(
        'title' => 'Add album',
        'page callback' => 'drupal_get_form',
        'page arguments' => array('album_form'),
        'access arguments' => array('administer site configuration'),
        'type' => MENU_LOCAL_TASK,
        'weight' => 2,
    );
    
    // редактирование альбома
    $items['album/%album/edit'] = array(
        'title' => 'Edit album',
        'page callback' => 'drupal_get_form',
        'page arguments' => array('album_form', 1),
        'access arguments' => array('administer site configuration'),
        'type' => MENU_CALLBACK,
    );
    
    // удаление альбома
    $items['album/%album/delete'] = array(
        'title' => 'Delete album',
        'page callback' => 'album_delete',
        'page arguments' => array(1),
        'access arguments' => array('administer site configuration'),
        'type' => MENU_CALLBACK,
    );
    
    return $items;
}

Список альбомов и форма добавления альбома будут доступны в виде вкладок, о чём говорит тип MENU_DEFAULT_LOCAL_TASK и MENU_LOCAL_TASK.

Создаём форму добавления/редактирования альбома и функцию, которая будет обрабатывать форму и записывать результат в базу:

/**
 * Форма добавления/редактирования альбома
 */
function album_form(&$form_state, $album = null)
{
    $form['title'] = array(
        '#title' => 'Название',
        '#description' => 'Название альбома',
        '#type' => 'textfield',
        '#default_value' => $album ? $album->title : '',
        '#required' => true,
    );
    
    $form['year'] = array(
        '#title' => 'Год',
        '#description' => 'Год выхода альбома',
        '#type' => 'textfield',
        '#default_value' => $album ? $album->year : '',
        '#required' => true,
        '#size' => 5,
    );
    
    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => $album ? 'Сохранить' : 'Добавить',
    );
    
    if ($album)
    {
        $form['aid'] = array(
            '#type' => 'value',
            '#value' => $album->aid,
        );
    }
    
    return $form;
}

/**
 * Добавление/сохранение альбома
 */
function album_form_submit($form, &$form_state)
{
    $album = array(
        'title' => $form_state['values']['title'],
        'year'  => $form_state['values']['year'],
    );
    
    // сохранение
    if (isset($form_state['values']['aid']))
    {
        $album['aid'] = $form_state['values']['aid'];
        drupal_write_record('albums', $album, 'aid');
        drupal_set_message('Альбом сохранён');
    }
    // добавление
    else
    {
        drupal_write_record('albums', $album);
        drupal_set_message('Альбом добавлен');
    }
    
    drupal_goto('album');
}

Форма добавления альбома Форма редактирования альбома

Добавляем page callback для просмотра списка альбомов в виде таблицы:

/**
 * Список альбомов
 */
function album_list()
{
    $tableHeader = array('Название', 'Год', 'Действия');
    $tableData = array();
    $albums = db_query("SELECT * FROM {albums}");
    
    while ($album = db_fetch_object($albums))
    {
        $actions = array(
            l('редактировать', 'album/' . $album->aid . '/edit'),
            l('удалить',       'album/' . $album->aid . '/delete', array('query' => 'token=' . drupal_get_token('album-' . $album->aid))),
        );
        
        $tableData[] = array($album->title, $album->year, implode(' | ', $actions));
    }
    
    return theme('table', $tableHeader, $tableData);
}

Список альбомов

Добавляем функцию удаления альбома, не забывая про защиту от CSRF:

/**
 * Удаление альбома
 */
function album_delete($album)
{
    if (!drupal_valid_token($_GET['token'], 'album-' . $album->aid))
    {
        return drupal_access_denied();
    }
    
    db_query("DELETE FROM {albums} WHERE aid = %d", $album->aid);
    drupal_set_message('Альбом удалён');
    drupal_goto('album');
}

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

/**
 * Возвращает информацию о альбоме
 */
function album_load($aid)
{
    return db_fetch_object(db_query("SELECT * FROM {albums} WHERE aid = %d", $aid));
}

Благодаря ей, в hook_menu() можно использовать wildcard loader %album, который передаст в page callback не id альбома, а объект с заполненными свойствами.

Исходники модуля.

После написания статьи понял, что в друпале не хватает какого-нибудь кодо-генератора/круд-генератора или скафолдинга. Например описываешь модель по подобию Schema DB, а друпал сам генерит формы, таблицы, функции, на основе этой модели. Жизнь была бы немного проще.

Создание простейшего модуля управления данными в Drupal 7.

Написанное актуально для
Drupal 6.x
Похожие записи

Комментарии

Views увидит таким образом созданную таблицу?

Спасибо!!!
Если у меня есть уже своя таблица как мне её подключить к схеме?

drupal_write_record - ведь со схемой работает, а как свою готовую таблицу с готовыми данными приаттачить к schema друпала?

скорей всего всё тоже самое. если не получится то попробуйте просто не инсталить схему

есть ещё Data, для создания таблиц из админки, с автоматической интеграцией в views

спасибо, я так понял что для TW и DATA надо ручками писать формы через FormAPI, пару вопросов
1) как лучше всего сделать форму если связь в бд - многие ко многим?

ну тут особых вариантов и нет - промежуточная таблица

почему album_form вызывается 3раза (если деббагить проект).
1) при первой загрузки формы, после перехода с album/%album/edit
2) потом почему-то еще раз вызывается эта функция album_form
потом когда ввожу данные, и нажимаю сохранить, то и
3й раз вызывается функция album_form

как быть, у меня когда пользователь кликает редактировать запись, функция формы берет записи из бд (если album/%album/edit) .
Получается таким образом (как я писал выше) , функция формы три раза подключается к бд, чтобы взять одни и теже данные.
Что посоветуете?

ну у меня функция mymod_form() загружает довольно таки большую таблицу в формы, причем несколько запросов: select, select один ко многим и select многие ко многим (итого пока 3-запроса).

а тут еще архитектура вызывает мою функцию 3-раза , и того 3*3 = 9 запросов. А когда у меня в форма увеличится и будет 20 запросов для формы, то 20 будет умножаться на 3 (благодаря архитектуре) = 60запросов (40 запросов лишние).

Может быть я не так инициализирую данные для форм, может нельзя брать данные по умолчанию в функции mymodule_form?

ф-я срабатывает по одному разу на каждый запрос к движку друпала:

1. при генерации формы
2. при сабмите формы
3. при генерации новой формы после редиректа из второго пункта (на самом деле это пункт 1)

для сохранения данных между запросами можете использовать кэш друпала, memcached или любое другое key-value хранилище

Какие-то костыли получаются. ИМХО.

Если не против ссылка на вопрос с кодом модуля Мой модуль

А можно ли как-то данные для формы один раз загружать?
К примеру пускай хоть 10 раз вызывается ункция генерации формы, а данные для неё уже были бы где-то инициализированны (подготовленны), кроме как кеша, может где-то в другом месте их можно один раз инициализировать?

Кроме кэша это нигде сделать нельзя. Жизнь php скрипта ограничена временем его выполнения.

Спасибо большое, уважаемый xandeadx, мечём света рассекаете тьму моего незнания.

Vodolazsky
24.03.2011, 18:48

У Вас ачепятка. Описывается схема 'albums', а инсталлится и деинсталлится схема 'album'

описывается схема album с таблицей albums. в коде всё верно

я только начал познавать Друпал. Ваш модуль очень помог но есть пару вопросов если не затруднит :

1. Почему то ни как не могу дать доступ к модулю для пользователей.
2. Как сделать чтоб модуль грузился сразу на главную страницу?
3. Есть ли какой то способ прикрутить кнопку к списку альбомов вместо таба сверху ?

Спасибо.

Петров Николай
27.05.2011, 15:52

После написания статьи понял, что в друпале не хватает какого-нибудь кодо-генератора/круд-генератора или скафолдинга. Например описываешь модель по подобию Schema DB, а друпал сам генерит формы, таблицы, функции, на основе этой модели. Жизнь была бы немного проще.

Ничего живого не нашел, но поползновения есть

http://drupal.org/project/crud
http://drupal.org/project/code_gen
http://drupal.org/node/533192
http://www.dueyesterday.net/node/26

Вот ещё любопытные модуль появился - Model Entities. Можно из админки создавать сущности и добавлять к ним поля. Модуль сам будет генерить все формы, таблицы, функции удалить/клонировать/просмотреть. Есть интеграция с Views. Об этом собственно и мечтал. Если модуль будет развиваться, то должна получиться хорошая альтернатива Entity API.

Андрей
09.06.2011, 21:53

Подскажите, пожалуйста, как в форму в примере добавить поле типа nodereference с автодополнением. Если будет пример как в автодополнение конкретный тип нод прописать, то вставьте, пжл

никак, cck поля работают только с нодами

Андрей
20.06.2011, 10:47

нодереференсе програмно создаётся, вот как его в форму добавить ещё ищу

Я думаю будет полезным добавить (можно как альтернативу) удаление альбома с подтверждением (confirm_form) и написать, почему в этом случае токены необязательны.

Прошёлся по ссылкам, у тебя это уже описано, можно просто вставить линки, для тех, кто пришёл из гугла :)

не понял в чем разница между Model Entities и стандартным CCK? )))))

CCK создаёт поля, Model Entities сущности

Я не правильно выразился, в чем тогда отличие "типы содержимого" от "сущностей"?

Хорошая статья, возник вопрос. Сам относительно знаю PHP, да и Drupal собственно тоже. В Вашем примере если перейти на любую страницу отличную от первой, после редактирования строки попадаешь снова на первую. Как сделать переход на ту же страницу на которой было произведено редактирование?

И заодно задам второй вопрос. Если я хочу настраивать на этой же странице вывод не по 10, а по N строк, это придется делать через формы?

1. в drupal_goto указывайте что угодно
2. да

Добавить комментарий