0

我正在创建一个表单,让用户可以在指定的日期、时间和时区安排活动。我想组合这三个表单字段的输入并将它们存储在数据库的一个日期时间列中。根据输入,我想将指定的日期和时间转换为 UTC。

但是我不完全确定如何为此编写表单代码。我正在编写一个扩展 Fieldset 的 Fieldset 类,并将三个字段添加到该字段集中:

<?php
namespace Application\Form\Fieldset;

use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterInterface;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Stdlib\Hydrator\ClassMethods;

class SendDateFieldset extends Fieldset implements InputFilterProviderInterface
{
    public function __construct()
    {
        parent::__construct('senddate');

        $this->add(array(
                'name' => 'date',
                'type' => 'Text',
                'options' => array(
                        'label' => 'Date to send:',
                )
            )
        );

        $this->add(array(
                'name' => 'time',
                'type' => 'Text',
                'options' => array(
                        'label' => 'Time to send:',
                )
            )
        );

        $this->add(array(
                'name' => 'timezone',
                'type' => 'Select',
                'options'       => array(
                'label'             => "Recipient's timezone",
                'value_options'     => array(
                    -12           => '(GMT-12:00) International Date Line West',
                    -11           => '(GMT-11:00) Midway Island, Samoa',
                    -10           => '(GMT-10:00) Hawaii',
                ),
            ),
            )
        );
    }

    public function getInputFilterSpecification()
    {
        return array(
                'date' => array(
                    'required' => true,
                    'filters' => array(
                        array('name' => 'StringTrim'),
                    ),
                    'validators' => array(
                        array(
                            'name' => 'Date',
                            'break_chain_on_failure' => true,
                            'options' => array(
                                'message' => 'Invalid date'
                            ),
                        ),
                    ),
                ),

                'time' => array(
                    'required' => true,
                    'filters' => array(
                        array('name' => 'StringTrim'),
                    ),
                ),

                'timezone' => array(
                    'required' => true,
                ),
        );
    }
}

然后我将此字段集添加到我的表单中,如下所示:

<?php 
namespace Application\Form;

use Zend\Form\Form;

class Order extends Form
{
    public function __construct()
    {
        parent::__construct("new-order");
        $this->setAttribute('action', '/order');
        $this->setAttribute('method', 'post');

        $this->add(
                array(
                    'type' => 'Application\Form\Fieldset\SendDateFieldset',
                    'options' => array(
                            'use_as_base_fieldset' => false
                    ),      
                )
        );
    }
}

当然,我会在表单中添加其他字段集,订单信息本身的基本字段集和带有收件人信息的另一个字段集。

我对此有两个问题:

  1. 处理这三个字段并将它们作为 1 个日期时间(转换为 UTC)存储在数据库中的最优雅的方法是什么?我也有一个 Order 服务对象,它将负责处理新订单,所以我可以在负责处理该服务类中的新订单的方法中处理它,还是有更好的方法?

  2. 我只在 SendDate 字段集中发布了一小段时区列表。有没有更简洁的方法来渲染这个列表?

4

1 回答 1

0

好的,正如承诺的那样,我将分享我对这个问题的解决方案。希望它会在未来对其他人有所帮助。

我最终使用了我最初已经拥有的 SendDateFieldset。

Application\Form\Fieldset\SendDateFieldset:

<?php
namespace Application\Form\Fieldset;

use Application\Hydrator\SendDate as SendDateHydrator;

use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterInterface;
use Zend\InputFilter\InputFilterProviderInterface;

class SendDateFieldset extends Fieldset implements InputFilterProviderInterface
{
    public function __construct()
    {
        parent::__construct('senddate');
        $this->setHydrator(new SendDateHydrator());
        $this->setObject(new \DateTime());

        $this->add(array(
                'name' => 'date',
                'type' => 'Text',
                'options' => array(
                        'label' => 'Date to send:',
                )
            )
        );

        $this->add(array(
                'name' => 'time',
                'type' => 'Text',
                'options' => array(
                        'label' => 'Time to send:',
                )
            )
        );

        $this->add(array(
                'name' => 'timezone',
                'type' => 'Select',
                'options'       => array(
                'label'             => "Recipient's timezone",
                'value_options'     => array(
                        // The list of timezones is being populated by the OrderFormFactory
                ),
            ),
            )
        );
    }

    public function getInputFilterSpecification()
    {
        return array(
                'date' => array(
                    'required' => true,
                    'filters' => array(
                        array('name' => 'StringTrim'),
                    ),
                    'validators' => array(
                        array(
                            'name' => 'Date',
                            'break_chain_on_failure' => true,
                            'options' => array(
                                'message' => 'Invalid date'
                            ),
                        ),
                    ),
                ),

                'time' => array(
                    'required' => true,
                    'filters' => array(
                        array('name' => 'StringTrim'),
                    ),
                    'validators' => array(
                        array(
                            'name' => 'Callback',
                            'options' => array(
                                    'callback' => function($value, $context)
                                    {
                                        // @todo: check if date and time is in the future
                                        return true;
                                    }
                            ),
                        ),
                    ),
                ),

                'timezone' => array(
                    'required' => true,
                ),
        );
    }
}

正如您在这个字段集中看到的那样,我现在使用一个普通的 DateTime 对象作为实体。为了填充 DateTime 对象,我为此字段集使用了一个自定义水合器:SendDateHydrator,它看起来像这样:

<?php
namespace Application\Hydrator;

use Zend\Stdlib\Hydrator\AbstractHydrator;
use DateTime;
use DateTimeZone;

class SendDate extends AbstractHydrator
{
    public function __construct($underscoreSeparatedKeys = true)
    {
        parent::__construct();
    }

    /**
     * Extract values from an object
     *
     * @param  object $object
     * @return array
     * @throws Exception\BadMethodCallException for a non-object $object
     */
    public function extract($object)
    {
        throw new Exception\BadMethodCallException(sprintf(
                    '%s is not implemented yet)', __METHOD__
            ));
    }

    /**
     * Hydrate data into DateTime object
     *
     * @param array $data
     * @param object $object
     * @return object
     * @throws Exception\BadMethodCallException for a non-object $object
     */
    public function hydrate(array $data, $object)
    {
        if (!$object instanceof DateTime)
        {
            throw new Exception\BadMethodCallException(sprintf(
                    '%s expects the provided $object to be a DateTime object)', __METHOD__
            ));
        }

        $object = null;
        $object = new DateTime();

        if (array_key_exists('date', $data) && array_key_exists('time', $data) && array_key_exists('timezone', $data))
        {
            $object = new DateTime($data['date'] . ' ' . $data['time'], new DateTimeZone($data['timezone']));
        }
        else
        {
            throw new Exception\BadMethodCallException(sprintf(
                    '%s expects the provided $data to contain a date, time and timezone)', __METHOD__
            ));
        }

        return $object;
    }
}

hydrate 方法负责使用用户使用选择框指定的时区创建 DateTime 对象。

为了在表单中生成带有时区的选择,我做了一个小服务,它使用 DateTimeZone 来生成时区列表并很好地格式化它们。最终结果是一个关联数组,可以传递给 select 的值选项。该数组的键是 DateTimeZone 可以处理的官方时区标识符。我在负责创建使用此选择框的表单的工厂类中传递此列表:

应用程序\工厂\OrderFormFactory:

<?php
namespace Application\Factory;

use Application\Service\TimezoneService;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

use Application\Form\Order as OrderForm;

class OrderFormFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $orderForm = new OrderForm();

        /* @var $timezoneSvc TimezoneService */
        $timezoneSvc = $serviceLocator->get('Application\Service\TimezoneService');

        // Set list of timezones in SendDate fieldset
        $orderForm->get('order')->get('senddate')->get('timezone')->setValueOptions(
            $timezoneSvc->getListOfTimezones()
        );
        return $orderForm;
    }
}

表单中生成的字段集如下所示: 截屏

保存订单时,orderservice 会将 DateTime 转换为 UTC 时间,然后再将其存储到数据库中。

于 2013-06-07T04:27:43.170 回答