How to pass parameters to custom validation constraint in Symfony

Có lúc nào bạn cần validate data bằng validator cuả Symfony mà cần thêm các dữ liệu từ bên ngoài vào?

Sau đây mà một số cách mà mình đã sử dụng, tuỳ từng trường hợp mà áp dụng cho hợp lý.

Trước khi tiếp tục xem thì các bạn có thể đọc qua một số bài viết để có cái nhìn rõ hơn:

Validation

How to Create a custom Validation Constraint

Bài toán

Khi bạn cần thay đổi email của user A, bạn cần phải check trong database xem email này đã được sử dụng bởi user nào khác user A hay chưa. Vậy để validate thì ngoài email ra ta cần thêm thông tin của user hiện tại.

Cách 1: Pass class

1
2
3
4
5
6
7
8
9
/**
* @AppAssert\UniqueEmail()
*/
class User
{
private $id;

private $email;
}

Ở Constraint chỉ cần thay đổi target:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UniqueEmail extends Constraints
{
public $messsage = 'Email already exists.';

public function validateBy()
{
return UniqueEmailValidator::class;
}

public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}

Nếu muốn dùng constraint cho cả class và property thì chỉ cần:

1
2
3
4
 public function getTargets()
{
return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT];
}

1
2
3
4
5
6
7
8
class UniqueEmailValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
// lúc này $value sẽ là một User
// thực hiện check
}
}

Cách 2: Tạo thêm property cho Constraint

Thêm một property cho Constraint:

1
2
3
4
5
6
7
8
9
10
11
class UniqueEmail extends Constraints
{
public $messsage = 'Email already exists.';

public $id;

public function validateBy()
{
return UniqueEmailValidator::class;
}
}

Lúc này có thể pass giá trị cho id:

1
2
3
4
5
6
7
8
9
class User
{
private $id;

/**
* @AppAssert\UniqueEmail(id='foo')
*/
private $email;
}

1
2
3
4
5
6
7
class UniqueEmailValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
// $constraint->id = foo và có thể làm bất cứ thứ gì với nó
}
}

Nếu phức tạp hơn một xíu thì bạn có thể pass vào tên của một function:

1
2
3
4
5
6
7
8
9
10
11
class UniqueEmail extends Constraints
{
public $messsage = 'Email already exists.';

public $id;

public function validateBy()
{
return UniqueEmailValidator::class;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User
{
private $id;

/**
* @AppAssert\UniqueEmail(id="getValue")
*/
private $email;

public function getValue()
{
// xử lý
return 'foo';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UniqueEmailValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
// $constraint->id = 'getValue'
// kiểm tra xem getValue có phải là tên của function không

if (!is_callable($id = [$this->context->getObject(), $constraint->id)] && !is_callable($id = [$this->context->getClassName(), $constraint->id)] && !is_callable($id = $constraint->id)) {
throw new ConstraintDefinitionException('Id is invalid');
}
$id = call_user_func($id);
// $id = foo
}
}

Kết bài

Mong sau bài viết này các bạn có thể hiểu thêm về Validator Constraint của Symfony và nếu có ý kiến hoặc cách nào hay ho hơn nữa thì đừng ngần ngại mà comment bên dưới nhé.