我試圖創建一個簡單的自定義Symfony2 dateRange formType輕微扭曲。Symfony2 DateRange自定義類型
這個想法是,用戶可以選擇日期的字符串表示,例如「今天」,「本月」或「自定義」開始/結束日期。
如果用戶選擇某個時間段(例如:「今天」),則提交表單時,忽略開始和結束提交的表單數據,並根據該時間段計算開始和結束時間。
在提交了 「定製」 期間的形式:
When I fill in the form
And submit "period" with "custom"
And submit "start" with "2014-01-01"
And submit "end" with "2014-01-01"
Then the form should display:
"custom" in the period select box
And "2014-01-01" in start
And "2014-01-01" in end
在提交了 「明日」 期間的形式(假設該日期是2014年1月1日):
When I fill in the form
And submit "period" with "tomorrow"
Then the form should display:
"tomorrow" in the period select box
And "2014-01-02" in start
And "2014-01-02" in end
的view/norm數據是一個由句點(int),開始,結束組成的數組。
$viewData = array(
'period' => 0,
'start' => new \DateTime()
'end' => new \DateTime()
);
模型數據是一個DateRange值對象。
<?php
namespace Nsm\Bundle\ApiBundle\Form\Model;
use DateTime;
class DateRange
{
/**
* @var DateTime
*/
protected $start;
/**
* @var DateTime
*/
protected $end;
/**
* @param DateTime $start
* @param DateTime $end
*/
public function __construct(DateTime $start = null, DateTime $end = null)
{
$this->start = $start;
$this->end = $end;
}
/**
* @param DateTime $start
*
* @return $this
*/
public function setStart(DateTime $start = null)
{
$this->start = $start;
return $this;
}
/**
* @return DateTime
*/
public function getStart()
{
return $this->start;
}
/**
* @param DateTime $end
*
* @return $this
*/
public function setEnd(DateTime $end = null)
{
$this->end = $end;
return $this;
}
/**
* @return DateTime
*/
public function getEnd()
{
return $this->end;
}
}
表單類型的樣子:
<?php
namespace Nsm\Bundle\ApiBundle\Form\Type;
use Nsm\Bundle\ApiBundle\Form\DataTransformer\DateRangeToArrayTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\Form;
class DateRangeType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'period',
'choice',
array(
'choices' => array(
'custom',
'today',
'tomorrow',
)
)
)
->add('start', 'date')
->add('end', 'date');
$transformer = new DateRangeToArrayTransformer();
$builder->addModelTransformer($transformer);
}
/**
* @param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
// Don't modify DateRange classes by reference, we treat
// them like immutable value objects
'by_reference' => false,
'error_bubbling' => false,
// If initialized with a DateRange object, FormType initializes
// this option to "DateRange". Since the internal, normalized
// representation is not DateRange, but an array, we need to unset
// this option.
'data_class' => null,
'required' => false
)
);
}
/**
* @return string
*/
public function getName()
{
return 'date_range';
}
}
最後變壓器的樣子:
<?php
namespace Nsm\Bundle\ApiBundle\Form\DataTransformer;
use Nsm\Bundle\ApiBundle\Form\Model\DateRange;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DateRangeToArrayTransformer implements DataTransformerInterface
{
/**
* Model to Norm
*
* @param mixed $dateRange
*
* @return array|mixed|string
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function transform($dateRange)
{
if (null === $dateRange) {
return null;
}
if (!$dateRange instanceof DateRange) {
throw new UnexpectedTypeException($dateRange, 'DateRange');
}
return array(
'period' => 0,
'start' => $dateRange->getStart(),
'end' => $dateRange->getEnd()
);
}
/**
* Norm to Model
*
* @param $value
*
* @return DateRange|null
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function reverseTransform($value)
{
if (null === $value) {
return null;
}
if (!is_array($value)) {
throw new UnexpectedTypeException($value, 'array');
}
// Check here if period is custom and calculate dates
return new DateRange($value['start'], $value['end']);
}
}
我已經決定要標準化的數據存儲爲一個數組,所以我可以保持'時期'價值。
上面的代碼轉換表單數據的預期,但我仍然是基於週期值操縱開始和結束值的問題。
我的第一次嘗試是在變壓器的reverseTransform方法中更改start/end的值。但是,這將break the bijective principal。
接下來的嘗試是使用事件。
使用FormEvents:PRE_SUBMIT
引入了一些更復雜的問題,即日期格式類型可以作爲原始單個文本或數組提交。使用FormEvents:SUBMIT
允許我成功操作表單數據。 $form->getData()
返回正確的\ DateRange對象。但是,開始,結束子窗體不會更新(其視圖數據已設置)。
$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
$data = $event->getData();
// ... manipulate $data here based on period ...
$event->setData($data);
});
所以我的問題:
- 反正是有操縱父窗體和影響的子窗體的視圖/標準/模型數據?
- 正在使用事件正確的方式來操縱父數據?
- 我應該將事件分配給子開始/結束表單而不是父表單嗎?
乾杯Leevi
更新
調整數據變換器來檢查reverseTransform的period
檢查期間並返回一個新DateRange
還挺工作。
優點:
$form->getData();
返回正確的DateRange
對象
缺點:
$form->get('end');
返回子形式,但它的數據不反映修改數據reverseTransform設置。- 視圖中的開始和結束表單字段不反映不反映修改後的數據集reverseTransform
- breaks the bijective principal。
所有的缺點都是因爲reverseTransform中的變化沒有被壓入子表單中,因爲它們首先被處理。
<?php
namespace Nsm\Bundle\ApiBundle\Form\DataTransformer;
use Nsm\Bundle\ApiBundle\Form\Model\DateRange;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DateRangeToArrayTransformer implements DataTransformerInterface
{
/**
* Model to Norm
*
* @param mixed $dateRange
*
* @return array|mixed|string
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function transform($dateRange)
{
if (null === $dateRange) {
return null;
}
if (!$dateRange instanceof DateRange) {
throw new UnexpectedTypeException($dateRange, 'DateRange');
}
return array(
'period' => 0,
'start' => $dateRange->getStart(),
'end' => $dateRange->getEnd()
);
}
/**
* Norm to Model
*
* @param $value
*
* @return DateRange|null
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function reverseTransform($value)
{
if (null === $value) {
return null;
}
if (!is_array($value)) {
throw new UnexpectedTypeException($value, 'array');
}
switch($value['period']) {
// Custom
case 0:
$start = $value['start'];
$end = $value['end'];
break;
// Today
case 1:
$start = new \DateTime('today');
$end = new \DateTime('today');
break;
// Tomorrow
case 2:
$start = new \DateTime('tomorrow');
$end = new \DateTime('tomorrow');
break;
// This week
case 3:
$start = new \DateTime('this week');
$end = new \DateTime('this week');
break;
default:
break;
}
// Check here if period is custom and calculate dates
return new DateRange($start, $end);
}
}
我使用的變壓器保留在normData的「期間」。 –
如果使用data_class,你不會有這段時間嗎?你不需要那邊的數據轉換器。我認爲處理'period'字段可以通過用戶來完成。 –