はじめに
symfonyは非常に強力なformクラスを持ったフレームワークです。
案件で使いはじめたのですが、まとまった情報が少なく
(公式documentは豊富なんですが、最新は英語だけなのと必要な情報がどれかを調べるのが辛い)
備忘録も兼ねて、機能のまとめを作成してます。
前回、前々回でformの作成とvalidationの記述の仕方を書きました。
今回はvalidation ruleを作成することができるカスタムバリデーションのことについてまとめたいと思います。
環境
- symfony 2.8
- php 7.0
今まで書いたsymfonyの記事
- symfony でのform作成の基本
- symfony validation処理の方法
カスタムバリデーションの作り方
フォームやEntityは実装済みという前提で、日付のvalidationを作成していきます。
3ステップです。便利!
1. Constraintクラスの作成
- Symfony\Component\Validator\Constraintクラスを継承した制約クラスの作成
<?php
namespace AppBundle\Validator\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class CheckDateFormat extends Constraint
{
public $message = 'Invalid date format';
public function __construct($message= "")
{
if(!empty($message['message'])){
$this->message = $message['message'];
}
}
}
2. ConstraintValidatorクラスの作成
- Symfony\Component\Validator\ConstraintValidatorクラスを継承したvalidatorクラスの作成
<?php
namespace AppBundle\Validator\Constraint;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class CheckDateFormatValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if(self::checkDatetimeFormat($value)){
$this->context
->buildViolation($constraint->message)
->addViolation();
}
}
public function checkDatetimeFormat($datetime)
{
if (!self::validateDate($datetime)) {
return true;
}
return false;
}
private function validateDate($date, $format = 'Y-m-d')
{
try {
\DateTime::createFromFormat($format, $date);
$info = \DateTime::getLastErrors();
}catch (Exception $e){
return false;
}
return !$info['errors'] && !$info['warnings'];
}
}
3. Validationの設定
- entityにannotionで付与する場合
- 作成したConstraintのクラスをannotionで付与します。
例
- 作成したConstraintのクラスをannotionで付与します。
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use AppBundle\Validator\Constraint as Custom;
/**
* ToDo
*
* @ORM\Table(name="to_do")
* @ORM\Entity(repositoryClass="AppBundle\Repository\ToDoRepository")
*/
class ToDo
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
* @ORM\Column(name="task", type="integer", length=1)
*/
private $task;
/**
* @var string
* @Assert\NotBlank()
* @Assert\Length(min = 2,max = 255)
* @ORM\Column(name="memo", type="string", length=255, nullable=true)
*/
private $memo;
/**
* @var string
* @Custom\CheckDateFormat()
* @ORM\Column(name="date", type="datetime",nullable=false)
*/
private $date;
/**
* @var \DateTime
*
* @ORM\Column(name="r_datetime", type="datetime", nullable=true)
*/
private $rDatetime;
/**
* @var \DateTime
*
* @ORM\Column(name="u_datetime", type="datetime", nullable=true)
*/
private $uDatetime;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set task
*
* @param string $task
* @return ToDo
*/
public function setTask($task)
{
$this->task = $task;
return $this;
}
/**
* Get task
*
* @return string
*/
public function getTask()
{
return $this->task;
}
/**
* Set memo
*
* @param string $memo
* @return ToDo
*/
public function setMemo($memo)
{
$this->memo = $memo;
return $this;
}
/**
* Get memo
*
* @return date
*/
public function getMemo()
{
return $this->memo;
}
/**
* @return date
*/
public function getDate()
{
return $this->date;
return $this;
}
/**
* @param string $date
*/
public function setDate($date)
{
$this->date = $date;
}
/**
* Set rDatetime
*
* @param \DateTime $rDatetime
* @return ToDo
*/
public function setRDatetime($rDatetime)
{
$this->rDatetime = $rDatetime;
return $this;
}
/**
* Get rDatetime
*
* @return \DateTime
*/
public function getRDatetime()
{
return $this->rDatetime;
}
/**
* Set uDatetime
*
* @param \DateTime $uDatetime
* @return ToDo
*/
public function setUDatetime($uDatetime)
{
$this->uDatetime = $uDatetime;
return $this;
}
/**
* Get uDatetime
*
* @return \DateTime
*/
public function getUDatetime()
{
return $this->uDatetime;
}
/**
* @ORM\PrePersist
*/
public function refreshRDatetime()
{
$this->setRDatetime(new \Datetime());
}
/**
* @ORM\PrePersist
* @ORM\PreUpdate
*/
public function refreshUDatetime()
{
$this->setUDatetime(new \Datetime());
}
}
作成したConstraintのnamespaceに別名をつけて
use AppBundle\Validator\Constraint as Custom;
validationを付与したいメンバにannotionを付与しています。
/**
* @var string
* @Custom\CheckDateFormat()
* @ORM\Column(name="date", type="datetime",nullable=false)
*/
private $date;
- FormTypeに付与する場合
- 付与したいメンバーのOptionに設定したconstraintsに設定します。自分の設定したクラスを指定する以外は通常のvalidationをつける方法と変わりません。
例
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type as InputType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
use AppBundle\Validator\Constraint as Custom;
class ToDoType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('task',
InputType\ChoiceType::class,
[
'choices' => [
'1' => 'work',
'2' => 'hobby'
],
'constraints' =>
[
new Assert\NotBlank()
]
]
)
->add('memo',
InputType\TextareaType::class,
[
'constraints' => [
new Assert\NotBlank(),
new Assert\Length(['min' => 2, 'max' => 255])
]
])
->add('date',
InputType\TextType::class,
[
'constraints' => [
new Custom\CheckDateFormat(),
]
]);
$entity = $builder->getData();
if(!$entity->getId()){
$builder->add('Create', InputType\SubmitType::class,
[
'attr' => [
'class' => 'btn btn-primary'
]
]
);
}else{
$builder->add('Edit', InputType\SubmitType::class,
[
'attr' => [
'class' => 'btn btn-primary'
]
]
);
}
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\ToDo'
));
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_todo';
}
}
終わりに
- 通常のvalidationでもある程度のアプリケーションを作成することができますが、実際に利用していく中で特殊なvalidationが必要になることがままあると思います。
symfonyは疎結合が強く意識されたフレームワークでvalidationに関してもsymfonyの思想に合わせて作っていくと、個々のvalidationが切り離され、テストやvalidationをテストしやすいコードになると思います。