使用Zend Framework 2表单在指定的时区中处理日期和时间

时间:2013-06-05 02:38:52

标签: php zend-framework zend-framework2 zend-form

我正在创建一个表单,让用户在指定的日期,时间和时区安排活动。我想组合这三个表单字段的输入并将它们存储在数据库中的一个日期时间列中。根据输入,我想将指定的日期和时间转换为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)?我有 订单服务对象也将负责处理a 新订单,所以我可以在负责的方法中处理它 处理该服务类中的新订单还是有更好的方法?

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

1 个答案:

答案 0 :(得分:0)

好的,正如我所承诺的,我会分享我对这个问题的解决方案。希望将来可以帮助其他人。

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

应用程序\表格\字段集\ 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方法负责使用用户使用selectbox指定的时区创建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;
    }
}

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

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