Symfony 表单中的字段集实现

发布于 2024-09-11 02:32:01 字数 1961 浏览 12 评论 0原文

在我用 Symfony 编写的项目中,表单中经常会有字段集,因此我想创建一种机制,以便我可以按字段集对字段进行分组,并且仍然使用表单的 __toString() 方法。 在此页面上,我了解了 sfWidgetFormSchema 以及如何它可以被视为一个小部件,可以嵌套字段。 所以这就是我所做的: 我创建了嵌套字段:

   $this->setWidgets(array(
      'customer'    => new sfWidgetFormSchema(array(
        'customer_name'      => new sfWidgetFormInputText(),
        'customer_email'     => new sfWidgetFormInputText(array())
      )),
      'library'     => new sfWidgetFormSchema(array(
        'library_name'      => new sfWidgetFormInputText(),
        'library_address'   => new sfWidgetFormInputText(),
        'library_city'      => new sfWidgetFormInputText(),
        'library_postcode'  => new sfWidgetFormInputText(),
        'library_website'   => new sfWidgetFormInputText()
      )),
      'message'     => new sfWidgetFormTextarea(array(),array( "cols" => 50, "rows" => 10 )),
    ));

然后我创建了一个 fieldsetFormSchemaFormatter 类,它基本上将字段包装在标签中,并将其与 sfWidgetFormSchema 字段关联:

foreach (array('customer', 'library') as $fieldset)
    {
      $this->widgetSchema[$fieldset]->addFormFormatter('tableless',
        new tableLessFormSchemaFormatter($this->widgetSchema['customer']));
      $this->widgetSchema[$fieldset]->setFormFormatterName('tableless');
      $this->widgetSchema[$fieldset]->setNameFormat('%s');
    }
    $this->widgetSchema->addFormFormatter('fieldset',
      new FieldsetFormSchemaFormatter($this->widgetSchema,
        'TableLessFormSchemaFormatter'));
    $this->widgetSchema->setFormFormatterName('fieldset');

它工作得很好,我得到了我的字段集表单。我遇到的问题是验证,我在这个问题中之前链接的页面上根本没有描述这一点。错误消息显示在除“消息”字段之外的所有字段的表单顶部,“消息”字段后面紧接着有一条错误消息。我不认为我能够在行之后显示错误消息,并且仍然使用 echo $form 构造而不编写丑陋的代码,所以我想我会采用另一个实现。 我认为 sfWidgetFormSchema 小部件旨在构建相互依赖的字段,这些字段将具有全局验证规则。

您将如何实现此字段集功能?

on the project I am writing with Symfony, there will be fieldsets in forms very often, so I would like to create a mechanism so that I can group fields by fieldsets and still use the __toString() method of my forms.
On this page, I read about the sfWidgetFormSchema, and how it could be considered as a widget, which enables to nest fields.
So here is what I did:
I created nested fields:

   $this->setWidgets(array(
      'customer'    => new sfWidgetFormSchema(array(
        'customer_name'      => new sfWidgetFormInputText(),
        'customer_email'     => new sfWidgetFormInputText(array())
      )),
      'library'     => new sfWidgetFormSchema(array(
        'library_name'      => new sfWidgetFormInputText(),
        'library_address'   => new sfWidgetFormInputText(),
        'library_city'      => new sfWidgetFormInputText(),
        'library_postcode'  => new sfWidgetFormInputText(),
        'library_website'   => new sfWidgetFormInputText()
      )),
      'message'     => new sfWidgetFormTextarea(array(),array( "cols" => 50, "rows" => 10 )),
    ));

Then I created a fieldsetFormSchemaFormatter class, which basically wraps fields in tags, and associated it with the sfWidgetFormSchema fields:

foreach (array('customer', 'library') as $fieldset)
    {
      $this->widgetSchema[$fieldset]->addFormFormatter('tableless',
        new tableLessFormSchemaFormatter($this->widgetSchema['customer']));
      $this->widgetSchema[$fieldset]->setFormFormatterName('tableless');
      $this->widgetSchema[$fieldset]->setNameFormat('%s');
    }
    $this->widgetSchema->addFormFormatter('fieldset',
      new FieldsetFormSchemaFormatter($this->widgetSchema,
        'TableLessFormSchemaFormatter'));
    $this->widgetSchema->setFormFormatterName('fieldset');

And it just worked fine, I got my fieldset form. The problem I have is with validation, which is not at all described on the page I linked sooner in this question. The error messages appear on top of the form for all fields but the "message" field, which has an error message right after it. I don't think I'm gonna be able to get the error messages to display right after the rows and still use the echo $form construct without coding something ugly, so I think I'm gonna go with another implementation.
I think that sfWidgetFormSchema widgets are meant to build interdependant fields, which would have global validation rules.

How would you implement this fieldset functionality?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

桃扇骨 2024-09-18 02:32:01

这是我经过一番研究后得出的结论,它似乎工作正常,但渲染时不再使用位置机制。我想知道这是否会有问题。

<?php
class UcWidgetFormSchema extends sfWidgetFormSchema
{
  /**
   * An associative array with all the fieldsets
   * <code>
   *   array(
   *    "fieldset1" => array("fieldName1", "fieldName2"),
   *    "fieldset2" => array("fieldName3", "fieldName4"),
   *   )
   * </code>
   *
   * @var array
   */
  private $fieldsets;

  /**
   * A fieldset-compatible constructor.
   *
   * @param mixed $fields     Initial fields. Values can be given this way:
   * <code>
   *  array(
   *    "fieldset1" => array(
   *      "field1" => $widget1,
   *      "field2" => $widget2
   *    )
   *    "fieldset1" => array(
   *      "field3" => $widget3,
   *      "field4" => $widget4,
   *      "field5" => $widget5
   *    )
   *    "message" => $widget6
   *  )
   * </code>
   * @param array $options    An array of options
   * @param array $attributes An array of default HTML attributes
   * @param array $labels     An array of HTML labels
   * @param array $helps      An array of help texts
   */
  public function __construct($fields = null, $options = array(),
    $attributes = array(), $labels = array(), $helps = array())
  {
    $this->addOption('name_format', '%s');
    $this->addOption('form_formatter', null);

    parent::__construct($options, $attributes);

    if (is_array($fields))
    {
      $fieldsets = array();
      foreach ($fields as $name => $value)
      {
        if (is_array($value))
        {
          $fieldsets[$name] = array_keys($value);
          foreach ($value as $valueName=> $valueWidget)
          {
            $this[$valueName] = $valueWidget;
          }
        }
        else
        {
          $this[$name] = $value;
        }
      }
      $this->setFieldsets($fieldsets);
    }
    else if (null !== $fields)
    {
      throw new InvalidArgumentException('sfWidgetFormSchema constructor takes an array of sfWidget objects.');
    }

    $this->setLabels($labels);
    $this->helps = $helps;
  }

  /**
   * Setter for the fieldsets
   *
   * @param array $fieldsets an associative array
   *
   * @return null
   */
  public function setFieldsets(array $fieldsets)
  {
    $fieldNames = array();
    foreach ($fieldsets as $fieldset => $fieldsetFieldNames)
    {
      $fieldNames = array_merge($fieldNames, $fieldsetFieldNames);
    }
    $availableFieldsNames =  array_keys($this->getFields());
    if ($diff = array_diff(array_unique($fieldNames), $fieldNames))
    {
      throw new InvalidArgumentException(
        'A field can only be used once in all fieldset. These do not: ' .
        implode(', ', $diff));
    }

    if ($diff = array_diff($fieldNames, $availableFieldsNames))
    {
      throw new InvalidArgumentException(
        'Widget schema does not include the following field(s): ' .
        implode(', ', $diff));
    }
    $this->fieldsets = $fieldsets;
  }

  public function render($name, $values = array(), $attributes = array(), $errors = array())
  {
    if(!$this->getFormFormatter() instanceof FieldsettedFormFormatterInterface )
    {
      throw new LogicException('The formatter you are using must implement FieldsettedFormFormatterInterface');
    }

    if (null === $values)
    {
      $values = array();
    }

    if (!is_array($values) && !$values instanceof ArrayAccess)
    {
      throw new InvalidArgumentException('You must pass an array of values to render a widget schema');
    }

    $formFormat = $this->getFormFormatter();


    $groups       = array();
    $hiddenRows   = array();
    $errorRows    = array();
    $lonelyFields = $this->getPositions();
    $lonelyRows   = array();

    // render each field
    foreach ($this->fieldsets as $fieldset => $fieldNames)
    {
      $rows = array();
      foreach ($fieldNames as $name)
      {
        $lonelyFields     = array_diff($lonelyFields, array($name));
        $widget           = $this[$name];
        $value            = isset($values[$name]) ? $values[$name] : null;
        $error            = isset($errors[$name]) ? $errors[$name] : array();
        $widgetAttributes = isset($attributes[$name]) ? $attributes[$name] : array();

        if ($widget instanceof sfWidgetForm && $widget->isHidden())
        {
          $hiddenRows[] = $this->renderField($name, $value, $widgetAttributes);
        }
        else
        {
          $field = $this->renderField($name, $value, $widgetAttributes, $error);

          // don't add a label tag and errors if we embed a form schema
          $label = $widget instanceof sfWidgetFormSchema ?
            $this->getFormFormatter()->generateLabelName($name) :
            $this->getFormFormatter()->generateLabel($name);
          $error = $widget instanceof sfWidgetFormSchema ? array() : $error;

          $rows[] = $formFormat->formatRow($label, $field, $error,
            $this->getHelp($name));
        }
        $groups[$fieldset] = $rows;
      }
    }

    foreach ($lonelyFields as $name)
    {
      $widget           = $this[$name];
      $value            = isset($values[$name]) ? $values[$name] : null;
      $error            = isset($errors[$name]) ? $errors[$name] : array();
      $widgetAttributes = isset($attributes[$name]) ? $attributes[$name] : array();

      if ($widget instanceof sfWidgetForm && $widget->isHidden())
      {
        $hiddenRows[] = $this->renderField($name, $value, $widgetAttributes);
      }
      else
      {
        $field = $this->renderField($name, $value, $widgetAttributes, $error);

        // don't add a label tag and errors if we embed a form schema
        $label = $widget instanceof sfWidgetFormSchema ?
          $this->getFormFormatter()->generateLabelName($name) :
          $this->getFormFormatter()->generateLabel($name);
        $error = $widget instanceof sfWidgetFormSchema ? array() : $error;

        $lonelyRows[] = strtr($formFormat
          ->formatRow($label, $field, $error, $this->getHelp($name)),
          array('%hidden_fields%' => ''));
      }
    }

    $html = '';

    if ($groups)
    {
      // insert hidden fields in the last row
      $i        = 0;
      $maxGroup = count($groups);
      foreach ($groups as $fieldset => $group)
      {

        for ($j = 0, $max = count($group); $j < $max; $j++)
        {
          $group[$j] = strtr($group[$j], array('%hidden_fields%' =>
            (($i == $maxGroup -1) && $j == $max - 1) ?
              implode("\n", $hiddenRows) : ''));
        }

        $html .= $this->getFormFormatter()
          ->formatFieldSet($fieldset, implode('', $group));
        $i++;
      }
    }
    else
    {
      // only hidden fields
      $lonelyRows[] = implode("\n", $hiddenRows);
    }
    $html .= implode('', $lonelyRows);

    return $this->getFormFormatter()
      ->formatErrorRow($this->getGlobalErrors($errors)) . $html;
  }
}

如果您想使用格式化程序,则必须实现以下接口:

interface FieldsettedFormFormatterInterface
{
  /**
   * This method will be used to render a fieldset
   *
   * @param string $name    the name of the widget
   * @param string $widgets the widgets html
   *
   * @return string the html for the fieldset
   */
  public function formatFieldset($name, $widgets);
}

Here is what I have come up with after some research, it seems to work fine, but the positions mechanism is not used anymore when rendering. I wonder if this can be problematic.

<?php
class UcWidgetFormSchema extends sfWidgetFormSchema
{
  /**
   * An associative array with all the fieldsets
   * <code>
   *   array(
   *    "fieldset1" => array("fieldName1", "fieldName2"),
   *    "fieldset2" => array("fieldName3", "fieldName4"),
   *   )
   * </code>
   *
   * @var array
   */
  private $fieldsets;

  /**
   * A fieldset-compatible constructor.
   *
   * @param mixed $fields     Initial fields. Values can be given this way:
   * <code>
   *  array(
   *    "fieldset1" => array(
   *      "field1" => $widget1,
   *      "field2" => $widget2
   *    )
   *    "fieldset1" => array(
   *      "field3" => $widget3,
   *      "field4" => $widget4,
   *      "field5" => $widget5
   *    )
   *    "message" => $widget6
   *  )
   * </code>
   * @param array $options    An array of options
   * @param array $attributes An array of default HTML attributes
   * @param array $labels     An array of HTML labels
   * @param array $helps      An array of help texts
   */
  public function __construct($fields = null, $options = array(),
    $attributes = array(), $labels = array(), $helps = array())
  {
    $this->addOption('name_format', '%s');
    $this->addOption('form_formatter', null);

    parent::__construct($options, $attributes);

    if (is_array($fields))
    {
      $fieldsets = array();
      foreach ($fields as $name => $value)
      {
        if (is_array($value))
        {
          $fieldsets[$name] = array_keys($value);
          foreach ($value as $valueName=> $valueWidget)
          {
            $this[$valueName] = $valueWidget;
          }
        }
        else
        {
          $this[$name] = $value;
        }
      }
      $this->setFieldsets($fieldsets);
    }
    else if (null !== $fields)
    {
      throw new InvalidArgumentException('sfWidgetFormSchema constructor takes an array of sfWidget objects.');
    }

    $this->setLabels($labels);
    $this->helps = $helps;
  }

  /**
   * Setter for the fieldsets
   *
   * @param array $fieldsets an associative array
   *
   * @return null
   */
  public function setFieldsets(array $fieldsets)
  {
    $fieldNames = array();
    foreach ($fieldsets as $fieldset => $fieldsetFieldNames)
    {
      $fieldNames = array_merge($fieldNames, $fieldsetFieldNames);
    }
    $availableFieldsNames =  array_keys($this->getFields());
    if ($diff = array_diff(array_unique($fieldNames), $fieldNames))
    {
      throw new InvalidArgumentException(
        'A field can only be used once in all fieldset. These do not: ' .
        implode(', ', $diff));
    }

    if ($diff = array_diff($fieldNames, $availableFieldsNames))
    {
      throw new InvalidArgumentException(
        'Widget schema does not include the following field(s): ' .
        implode(', ', $diff));
    }
    $this->fieldsets = $fieldsets;
  }

  public function render($name, $values = array(), $attributes = array(), $errors = array())
  {
    if(!$this->getFormFormatter() instanceof FieldsettedFormFormatterInterface )
    {
      throw new LogicException('The formatter you are using must implement FieldsettedFormFormatterInterface');
    }

    if (null === $values)
    {
      $values = array();
    }

    if (!is_array($values) && !$values instanceof ArrayAccess)
    {
      throw new InvalidArgumentException('You must pass an array of values to render a widget schema');
    }

    $formFormat = $this->getFormFormatter();


    $groups       = array();
    $hiddenRows   = array();
    $errorRows    = array();
    $lonelyFields = $this->getPositions();
    $lonelyRows   = array();

    // render each field
    foreach ($this->fieldsets as $fieldset => $fieldNames)
    {
      $rows = array();
      foreach ($fieldNames as $name)
      {
        $lonelyFields     = array_diff($lonelyFields, array($name));
        $widget           = $this[$name];
        $value            = isset($values[$name]) ? $values[$name] : null;
        $error            = isset($errors[$name]) ? $errors[$name] : array();
        $widgetAttributes = isset($attributes[$name]) ? $attributes[$name] : array();

        if ($widget instanceof sfWidgetForm && $widget->isHidden())
        {
          $hiddenRows[] = $this->renderField($name, $value, $widgetAttributes);
        }
        else
        {
          $field = $this->renderField($name, $value, $widgetAttributes, $error);

          // don't add a label tag and errors if we embed a form schema
          $label = $widget instanceof sfWidgetFormSchema ?
            $this->getFormFormatter()->generateLabelName($name) :
            $this->getFormFormatter()->generateLabel($name);
          $error = $widget instanceof sfWidgetFormSchema ? array() : $error;

          $rows[] = $formFormat->formatRow($label, $field, $error,
            $this->getHelp($name));
        }
        $groups[$fieldset] = $rows;
      }
    }

    foreach ($lonelyFields as $name)
    {
      $widget           = $this[$name];
      $value            = isset($values[$name]) ? $values[$name] : null;
      $error            = isset($errors[$name]) ? $errors[$name] : array();
      $widgetAttributes = isset($attributes[$name]) ? $attributes[$name] : array();

      if ($widget instanceof sfWidgetForm && $widget->isHidden())
      {
        $hiddenRows[] = $this->renderField($name, $value, $widgetAttributes);
      }
      else
      {
        $field = $this->renderField($name, $value, $widgetAttributes, $error);

        // don't add a label tag and errors if we embed a form schema
        $label = $widget instanceof sfWidgetFormSchema ?
          $this->getFormFormatter()->generateLabelName($name) :
          $this->getFormFormatter()->generateLabel($name);
        $error = $widget instanceof sfWidgetFormSchema ? array() : $error;

        $lonelyRows[] = strtr($formFormat
          ->formatRow($label, $field, $error, $this->getHelp($name)),
          array('%hidden_fields%' => ''));
      }
    }

    $html = '';

    if ($groups)
    {
      // insert hidden fields in the last row
      $i        = 0;
      $maxGroup = count($groups);
      foreach ($groups as $fieldset => $group)
      {

        for ($j = 0, $max = count($group); $j < $max; $j++)
        {
          $group[$j] = strtr($group[$j], array('%hidden_fields%' =>
            (($i == $maxGroup -1) && $j == $max - 1) ?
              implode("\n", $hiddenRows) : ''));
        }

        $html .= $this->getFormFormatter()
          ->formatFieldSet($fieldset, implode('', $group));
        $i++;
      }
    }
    else
    {
      // only hidden fields
      $lonelyRows[] = implode("\n", $hiddenRows);
    }
    $html .= implode('', $lonelyRows);

    return $this->getFormFormatter()
      ->formatErrorRow($this->getGlobalErrors($errors)) . $html;
  }
}

Here is the interface your formatter must implement if you want to use it:

interface FieldsettedFormFormatterInterface
{
  /**
   * This method will be used to render a fieldset
   *
   * @param string $name    the name of the widget
   * @param string $widgets the widgets html
   *
   * @return string the html for the fieldset
   */
  public function formatFieldset($name, $widgets);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文