Добавляем в форму sfGuardUser связь many-to-many

В данной заметке я расскажу об использованном мной способе добавления к sfGuardUser связи many-to-many.

Начальные условия.

Имеется symfony 1.4 проект с установленным sfDoctrineGuardPlugin.

Цели.

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

Решение.

Схема.

Создадим в БД следующие таблицы:

# таблица ежедневных задач
CREATE TABLE IF NOT EXISTS `daily_task` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `daily_task_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;

# таблица связей many-to-many задач и sfGuardUser
CREATE TABLE IF NOT EXISTS `user_has_daily_task` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `daily_task_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id_idx` (`user_id`),
  KEY `daily_task_id_idx` (`daily_task_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;

# внешние ключи для связок
ALTER TABLE `user_has_daily_task`
  ADD CONSTRAINT `daily_task_id_idx` FOREIGN KEY (`daily_task_id`) REFERENCES `daily_task` (`id`),
  ADD CONSTRAINT `user_id_idx` FOREIGN KEY (`user_id`) REFERENCES `sf_guard_user` (`id`);

Этой структуре соответствует следующая схема Doctrine:

# схема для таблицы daily_task
DailyTask:
  connection: doctrine
  tableName: daily_task
  columns:
    id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: true
      autoincrement: true
    daily_task_name:
      type: string(255)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
  relations:
    UserHasDailyTask:
      local: id
      foreign: daily_task_id
      type: many
      onDelete: NO ACTION
      onUpdate: NO ACTION
# схема для таблицы user_has_daily_task
UserHasDailyTask:
  connection: doctrine
  tableName: user_has_daily_task
  columns:
    id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: true
      autoincrement: true
    user_id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
    daily_task_id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
  relations:
    sfGuardUser:
      local: user_id
      foreign: id
      type: one
      onDelete: NO ACTION
      onUpdate: NO ACTION
    DailyTask:
      local: daily_task_id
      foreign: id
      type: one
      onDelete: NO ACTION
      onUpdate: NO ACTION

Выполняем пересоздание классов Doctrine (модели, формы, фильтры) и очищаем кэш:

$ ./symfony doctrine:build --all-classes
$ ./symfony cache:clear

Подготовительную часть на этом можно считать завершенной.

Админ-генератор sfGuardUser

Теперь на уровне БД у нас есть привязка ежедневных задач к пользователю sfGuardUser. Для редактирования пользователя sfGuard предусматривает одноименный модуль sfGuardUser с админ-генератором. По-умолчанию этот модуль позволяет редактировать имя, пароль пользователя, а также группы и список прав доступа. Вот они то нас и интересуют. Почему? Потому что эти параметры также представляют из себя связки many-to-many, а значит, если мы хотим добавить ежедневные задачи в редактирование пользователя, то сделать это логично по образу и подобию.

Модель sfGuardUser

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

# lib/model/doctrine/sfDoctrineGuardPlugin/sfGuardUser.class.php
class sfGuardUser extends PluginsfGuardUser
{
  public function setUp()
  {
    parent::setUp();
    $this->hasMany('DailyTask', array(
      'refClass'  => 'UserHasDailyTask',
      'local'     => 'user_id',
      'foreign'   => 'daily_task_id'
    ));
  }
}

Отлично, теперь модель пользователя знает о новой связке ) Теперь можно заняться формой

Форма sfGuardUserForm

Теперь мы можем модифицировать форму sfGuardUserForm. Для начала добавим виджет для нашего нового поля (виджет назовемdaily_tasks_list):

# lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends BasesfGuardUserAdminForm
{
  public function configure()
  {
    $this->widgetSchema['daily_tasks_list'] = new sfWidgetFormDoctrineChoice(array(
      'multiple'  => true,
      'model'     => 'DailyTask'
    ));

    $this->validatorSchema['daily_tasks_list'] = new sfValidatorDoctrineChoice(array(
      'multiple'  => true,
      'model'     => 'DailyTask',
      'required'  => false
    ));
}

Тут желательно еще раз пересобрать все классы и очистить кэш.

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

Сохранение новой связки

Небольшое лирическое отступление, написанное по здравому размышлению. Если у вас виджет называется точно также как таблица + _list (в нашем случае это будет daily_task_list), то, скорее всего вы уже готовы использовать форму по полной. Я же назвал виджет не так как таблицу (в статье еще опущен префикс таблицы) и поимел последующий геморрой, который все-таки считаю нужным описать полностью.

Вот тут нам пригодится аналогия с группами и правами доступа. Если покопошиться в исходниках плагина, выяснится, что для сохранения связей, реализовано два дополнительных метода – savegroupsList иsavepermissionsList. Т.о. мы реализуем наш метод savedailytasksList и переопределяем метод doSave():

# lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends BasesfGuardUserAdminForm
{
  //...
  /**
   * Сохранение списка ежедневных задач
   */
  public function savedailytasksList($con = null)
  {
    if (!$this->isValid())
    {
      throw $this->getErrorSchema();
    }
    if (!isset($this->widgetSchema['daily_tasks_list']))
    {
      // somebody has unset this widget
      return;
    }
    if (null === $con)
    {
      $con = $this->getConnection();
    }
    $existing = $this->object->DailyTask->getPrimaryKeys();
    $values = $this->getValue('daily_tasks_list');
    if (!is_array($values))
    {
      $values = array();
    }
    $unlink = array_diff($existing, $values);
    if (count($unlink))
    {
      $this->object->unlink('DailyTask', array_values($unlink));
    }
    $link = array_diff($values, $existing);
    if (count($link))
    {
      $this->object->link('DailyTask', array_values($link), true);
    }
  }

  /**
   * Сохранение формы
   */
  protected function doSave($con = null)
  {
    $this->savedailytasksList($con);
    parent::doSave($con);
  }
}

Ура! Теперь мы можем сохранять наши задачи! )) Остался один маленький заключительный штришок – сохранить то мы сохраняем, но при повторном редактировании пользователя сохраненные связи не отображаются. Для этого переопределим метод определения значения по-умолчанию для виджета:

# lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends BasesfGuardUserAdminForm
{
  //...

  /**
   * Значение по-умолчанию для виджета ежедневных задач
   */
  public function updateDefaultsFromObject()
  {
    parent::updateDefaultsFromObject();
    if (isset($this->widgetSchema['daily_tasks_list']))
    {
      $this->setDefault('daily_tasks_list', $this->object->DailyTask->getPrimaryKeys());
    }
  }
}

оригинал

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: