jackai / symfony-validator
在symfony控制器注解中验证请求
v1.09
2022-05-24 05:59 UTC
Requires
- php: ^7.1|^8.0
- ext-json: *
- doctrine/annotations: ^1.0
- doctrine/orm: ^2.1|^4.0
Requires (Dev)
- doctrine/common: ^3.0
- friendsofsymfony/rest-bundle: ^2.0|^3.0@beta
- jms/serializer: ^1.14|^3.0
- jms/serializer-bundle: ^2.3|^3.0|^4.0|^5.0
- phpunit/phpunit: ^9.5
- sensio/framework-extra-bundle: ^4.0|^5.0|^6.0
- symfony/phpunit-bridge: ^4.0|^5.0|^6.0
- symfony/symfony: ^4.0|^5.0|^6.0
- willdurand/hateoas-bundle: ^1.0|^2.0
README
在symfony控制器注解中验证请求。
安装
1. 打开命令行,进入您的项目目录并执行以下命令以下载此扩展的最新版本
composer require jackai/symfony-validator
2. 打开config/services.yaml并添加以下配置
services:
Jackai\Validator\RequestAdvancedValidateListener:
arguments:
- {
doctrine: "@doctrine",
ruleAlias: {
"Assert": "Symfony\\Component\\Validator\\Constraints",
"My": "App\\Validator"
},
requireFormCode: null,
requireQueryCode: null,
throwOnValidateFail: true,
throwOnMissingValidate: false,
emptyStringIsUndefined: true,
shortErrorMsg: false,
}
tags:
- { name: kernel.event_listener, event: kernel.request }
使用说明
验证器参数说明
- throwOnMissingValidate: 当传入的参数不在验证列表中时,是否抛出异常,默认为
false
- throwOnValidateFail: 当验证失败时,是否抛出异常,默认为
true
- emptyStringIsUndefined: 当传入的参数为空字符串时,是否当作未传入参数处理,默认为
true
- requireQuery: 在query中必填的项
- requireForm: 在form中必填的项
- requireQueryCode: 当query中必填项未填写时,要抛出的错误代码,默认为
null
- requireFormCode: 当form中必填项未填写时,要抛出的错误代码,默认为
null
- query: 在query中字段验证规则
- form: 在form中字段验证规则
query及form验证规则说明
query和form参数中的验证规则如下:
- name: 字段名称,可以用
.
来连接字段名称,例如参数为config[abc]
就可写为config.abc
- dataType: 转换字段类型
- rule: 验证规则,目前有三种可用
- Assert\*: 为symfony的验证器,可参考官方网站的列表: (https://symfony.com.cn/doc/current/validation.html#constraints)
- 自制的symfony验证器: 在参数中写入Class位置即可,例如:
App\Validator\Constraints\ContainsAlphanumeric
,详细说明可参考symfony官方网站 (https://symfony.com.cn/doc/current/validation/custom_constraint.html) - require: 特殊规则的必填字段
- ruleOption: 验证规则的参数设置
- code: 条件验证失败时返回的错误代码,默认为
null
- msg: 条件验证失败时返回的错误信息,默认为
验证规则提供的错误信息
- require: 是否为必填字段,默认值为
false
- default: 当字段未填写时,默认传入
controller
的值
特殊规则的必填字段
当 "rule" = "require"
时,为验证特殊必填情况,特殊必填情况符合时 name
字段为必填。
特殊必填有以下几种模式:
- 指定字段的值等于其中一個数值:
"mode" = "if", "values" = {"指定字段", "数值1", "数值2",,, "数值N"}
- 其中一個指定字段有值:
"mode" = "with", "values" = {"指定字段A", "指定字段B",,, "指定字段N"}
- 全部指定字段都有值:
"mode" = "withAll", "values" = {"指定字段A", "指定字段B",,, "指定字段N"}
- 指定的字段其中一個没有值:
"mode" = "without", "values" = {"指定字段A", "指定字段B",,, "指定字段N"}
- 指定的字段全部没有值:
"mode" = "withoutAll", "values" = {"指定字段A", "指定字段B",,, "指定字段N"}
使用示例
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Jackai\Validator\AdvancedValidator;
class TestController extends AbstractController
{
/**
* @Route("/test")
* @AdvancedValidator(
* throwOnMissingValidate = true,
* throwOnValidateFail = true,
* emptyStringIsUndefined = true,
* requireQuery = {"name"},
* requireQueryCode = 998,
* query = {
* {"name" = "name", "rule" = "Assert\Length", "ruleOption" = {"min" = 1, "max" = 30}, "code" = "111", "msg" = "Invalid name"},
* {"name" = "price", "rule" = "Assert\GreaterThan", "ruleOption" = "0", "code" = "112", "default" = "99999", "msg" = "Invalid price"},
* {"name" = "picture", "rule" = "Assert\Length", "ruleOption" = {"min" = 1, "max" = 30}, "code" = "113", "msg" = "Invalid picture"},
* {"name" = "picture", "rule" = "require", "ruleOption" = {"mode" = "if", "values" = {"price", "999"}}, "code" = "118", "msg" = "If price is 999, you should private picture."},
* },
* requireForm = {"postParamName"},
* requireFormCode = 999,
* form = {
* {"name" = "postField", "rule" = "Assert\Length", "ruleOption" = {"min" = 1, "max" = 30}, "code" = "111", "msg" = "Invalid post_field"},
* }
* )
*/
public function test(Request $request)
{
// do something
}
}
创建自定义验证
使用官方Symfony网站上的示例创建验证器后 (https://symfony.com.cn/doc/current/validation/custom_constraint.html),在注解中添加以下行。
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Jackai\Validator\AdvancedValidator;
class TestController extends AbstractController
{
/**
* @Route("/test")
* @AdvancedValidator(
* throwOnMissingValidate = true,
* throwOnValidateFail = true,
* emptyStringIsUndefined = true,
* requireQuery = {"name"},
* requireQueryCode = 998,
* query = {
* {"name" = "name", "rule" = "Assert\Length", "ruleOption" = {"min" = 1, "max" = 30}, "code" = "111", "msg" = "Invalid name"},
+ * {"name" = "name", "rule" = "App\Validator\Constraints\ContainsAlphanumeric", "code" = "114", "msg" = "Name should be alphanumeric"},
* {"name" = "price", "rule" = "Assert\GreaterThan", "ruleOption" = "0", "code" = "112", "default" = "99999", "msg" = "Invalid price"},
* {"name" = "picture", "rule" = "Assert\Length", "ruleOption" = {"min" = 1, "max" = 30}, "code" = "113", "msg" = "Invalid picture"},
* {"name" = "picture", "rule" = "require", "ruleOption" = {"mode" = "if", "values" = {"price", "999"}}, "code" = "118", "msg" = "If price is 999, you should private picture."},
* },
* requireForm = {"postParamName"},
* requireFormCode = 999,
* form = {
* {"name" = "postField", "rule" = "Assert\Length", "ruleOption" = {"min" = 1, "max" = 30}, "code" = "111", "msg" = "Invalid post_field"},
* }
* )
*/
public function test(Request $request)
{
// do something
}
}
创建高级自定义验证
- 使用Jackai\Validator\Constraint
- 您可以在约束中使用rawValues和doctrine
- 别忘了添加别名:
"My": "App\\Validator"
- 然后您可以使用
"rule" = "My\DataUnique"
src\Validator\DataUnique.php
namespace App\Validator;
use Jackai\Validator\Constraint;
class DataUnique extends Constraint
{
public $message = 'The attribute "{{ path }}" data "{{ string }}" is duplicate.';
public $options = null;
public $entity = null;
public $rule = null;
public function __construct($options = null)
{
$this->options = $options;
parent::__construct($options);
}
}
src\Validator\DataUniqueValidator.php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class DataUniqueValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof DataUnique) {
throw new UnexpectedTypeException($constraint, DataUnique::class);
}
if ($this->checkDataBase($constraint)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ path }}', $path)
->setParameter('{{ string }}', $value)
->addViolation();
}
}
private function checkDataBase(DataUnique $constraint)
{
$ruleOption = $constraint->options;
foreach (['entity', 'rule'] as $k => $v) {
if (!array_key_exists($v, $ruleOption)) {
throw new \RuntimeException("ruleOption.{$v} is require.");
}
}
$entity = $ruleOption['entity'];
$rule = $ruleOption['rule'];
$values = $constraint->rawValues;
$doctrine = $constraint->doctrine;
$em = array_key_exists('em', $ruleOption) ? $ruleOption['em'] : 'default';
$search = [];
foreach ($rule as $k => $v) {
if (strncasecmp($v, ":", 1) === 0) {
$search[$k] = array_key_exists($k, $values) ? $values[$k] : null;
}
if (strncasecmp($v, ":", 1) !== 0) {
$search[$k] = $v;
}
$search[$k] = str_replace('\:', ':', $search[$k]);
}
$data = $doctrine->getManager($em)->getRepository("App\\Entity\\{$entity}")->findOneBy($search);
return $data !== null;
}
}
config/service.yaml
Jackai\Validator\RequestAdvancedValidateListener:
arguments:
- {
doctrine: "@doctrine",
ruleAlias: {
"Assert": "Symfony\\Component\\Validator\\Constraints",
"My": "App\\Validator"
},
}
tags:
- { name: kernel.event_listener, event: kernel.request }
src/Controller/TestController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Jackai\Validator\AdvancedValidator;
class TestController extends AbstractController
{
/**
* @Route("/test")
* @AdvancedValidator(
* throwOnMissingValidate = true,
* throwOnValidateFail = true,
* emptyStringIsUndefined = true,
* requireQuery = {"name"},
* requireQueryCode = 998,
* query = {
* {"name" = "name", "rule" = "Assert\Length", "ruleOption" = {"min" = 1, "max" = 30}, "code" = "111", "msg" = "Invalid name"},
+ * {"name" = "name", "rule" = "My\DataUnique", "ruleOption" = {"entity" = "Product", "rule" = {"name" = ":name"}}, "errorCode" = "119"},
* {"name" = "price", "rule" = "Assert\GreaterThan", "ruleOption" = "0", "code" = "112", "default" = "99999", "msg" = "Invalid price"},
* {"name" = "picture", "rule" = "Assert\Length", "ruleOption" = {"min" = 1, "max" = 30}, "code" = "113", "msg" = "Invalid picture"},
* {"name" = "picture", "rule" = "require", "ruleOption" = {"mode" = "if", "values" = {"price", "999"}}, "code" = "118", "msg" = "If price is 999, you should private picture."},
* },
* requireForm = {"postParamName"},
* requireFormCode = 999,
* form = {
* {"name" = "postField", "rule" = "Assert\Length", "ruleOption" = {"min" = 1, "max" = 30}, "code" = "111", "msg" = "Invalid post_field"},
* }
* )
*/
public function test(Request $request)
{
// do something
}
}