mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW Validate DBFields
This commit is contained in:
parent
c523022cb9
commit
e10f6f421a
@ -20,6 +20,8 @@ SilverStripe\Core\Injector\Injector:
|
||||
class: SilverStripe\ORM\FieldType\DBDecimal
|
||||
Double:
|
||||
class: SilverStripe\ORM\FieldType\DBDouble
|
||||
Email:
|
||||
class: SilverStripe\ORM\FieldType\DBEmail
|
||||
Enum:
|
||||
class: SilverStripe\ORM\FieldType\DBEnum
|
||||
Float:
|
||||
|
@ -2,11 +2,18 @@
|
||||
|
||||
namespace SilverStripe\Forms;
|
||||
|
||||
use SilverStripe\Validation\EmailValidator;
|
||||
|
||||
/**
|
||||
* Text input field with validation for correct email format according to RFC 2822.
|
||||
* Text input field with validation for correct email format
|
||||
*/
|
||||
class EmailField extends TextField
|
||||
{
|
||||
private static array $field_validators = [
|
||||
[
|
||||
'class' => EmailValidator::class,
|
||||
],
|
||||
];
|
||||
|
||||
protected $inputType = 'email';
|
||||
/**
|
||||
@ -17,39 +24,6 @@ class EmailField extends TextField
|
||||
return 'email text';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates for RFC 2822 compliant email addresses.
|
||||
*
|
||||
* @see http://www.regular-expressions.info/email.html
|
||||
* @see http://www.ietf.org/rfc/rfc2822.txt
|
||||
*
|
||||
* @param Validator $validator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function validate($validator)
|
||||
{
|
||||
$result = true;
|
||||
$this->value = trim($this->value ?? '');
|
||||
|
||||
$pattern = '^[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$';
|
||||
|
||||
// Escape delimiter characters.
|
||||
$safePattern = str_replace('/', '\\/', $pattern ?? '');
|
||||
|
||||
if ($this->value && !preg_match('/' . $safePattern . '/i', $this->value ?? '')) {
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t('SilverStripe\\Forms\\EmailField.VALIDATION', 'Please enter an email address'),
|
||||
'validation'
|
||||
);
|
||||
|
||||
$result = false;
|
||||
}
|
||||
|
||||
return $this->extendValidationResult($result, $validator);
|
||||
}
|
||||
|
||||
public function getSchemaValidation()
|
||||
{
|
||||
$rules = parent::getSchemaValidation();
|
||||
|
@ -15,6 +15,8 @@ use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\View\AttributesHTML;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
/**
|
||||
* Represents a field in a form.
|
||||
@ -275,6 +277,8 @@ class FormField extends RequestHandler
|
||||
'Description' => 'HTMLFragment',
|
||||
];
|
||||
|
||||
private static array $field_validators = [];
|
||||
|
||||
/**
|
||||
* Structured schema state representing the FormField's current data and validation.
|
||||
* Used to render the FormField as a ReactJS Component on the front-end.
|
||||
@ -1231,15 +1235,25 @@ class FormField extends RequestHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method each {@link FormField} subclass must implement, determines whether the field
|
||||
* is valid or not based on the value.
|
||||
* Subclasses can define an existing FieldValidatorClass to validate the FormField value
|
||||
* They may also override this method to provide custom validation logic
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator)
|
||||
{
|
||||
return $this->extendValidationResult(true, $validator);
|
||||
$isValid = true;
|
||||
$name = strip_tags($this->Title() ? $this->Title() : $this->getName());
|
||||
$fieldValidators = FieldValidator::createFieldValidatorsForField($this, $name, $this->value);
|
||||
foreach ($fieldValidators as $fieldValidator) {
|
||||
$validationResult = $fieldValidator->validate();
|
||||
if (!$validationResult->isValid()) {
|
||||
$validator->getResult()->combineAnd($validationResult);
|
||||
$isValid = false;
|
||||
}
|
||||
}
|
||||
return $this->extendValidationResult($isValid, $validator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace SilverStripe\Forms;
|
||||
|
||||
use SilverStripe\Validation\StringLengthValidator;
|
||||
|
||||
/**
|
||||
* Text input field.
|
||||
*/
|
||||
@ -14,6 +16,13 @@ class TextField extends FormField implements TippableFieldInterface
|
||||
|
||||
protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_TEXT;
|
||||
|
||||
private static array $field_validators = [
|
||||
[
|
||||
'class' => StringLengthValidator::class,
|
||||
'argCalls' => [null, 'getMaxLength'],
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Tip|null A tip to render beside the input
|
||||
*/
|
||||
@ -117,31 +126,6 @@ class TextField extends FormField implements TippableFieldInterface
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this field
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator)
|
||||
{
|
||||
$result = true;
|
||||
if (!is_null($this->maxLength) && mb_strlen($this->value ?? '') > $this->maxLength) {
|
||||
$name = strip_tags($this->Title() ? $this->Title() : $this->getName());
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'SilverStripe\\Forms\\TextField.VALIDATEMAXLENGTH',
|
||||
'The value for {name} must not exceed {maxLength} characters in length',
|
||||
['name' => $name, 'maxLength' => $this->maxLength]
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
$result = false;
|
||||
}
|
||||
return $this->extendValidationResult($result, $validator);
|
||||
}
|
||||
|
||||
public function getSchemaValidation()
|
||||
{
|
||||
$rules = parent::getSchemaValidation();
|
||||
|
@ -1230,6 +1230,15 @@ class DataObject extends ModelData implements DataObjectInterface, i18nEntityPro
|
||||
public function validate()
|
||||
{
|
||||
$result = ValidationResult::create();
|
||||
// Call DBField::validate() on every DBField
|
||||
$specs = static::getSchema()->fieldSpecs(static::class);
|
||||
foreach (array_keys($specs) as $fieldName) {
|
||||
$dbField = $this->dbObject($fieldName);
|
||||
$validationResult = $dbField->validate();
|
||||
if (!$validationResult->isValid()) {
|
||||
$result->combineAnd($validationResult);
|
||||
}
|
||||
}
|
||||
$this->extend('updateValidate', $result);
|
||||
return $result;
|
||||
}
|
||||
|
31
src/ORM/FieldType/DBEmail.php
Normal file
31
src/ORM/FieldType/DBEmail.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\FieldType;
|
||||
|
||||
use SilverStripe\Forms\EmailField;
|
||||
use SilverStripe\ORM\FieldType\DBVarchar;
|
||||
use SilverStripe\Validation\EmailValidator;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\Forms\NullableField;
|
||||
|
||||
class DBEmail extends DBVarchar
|
||||
{
|
||||
private static array $field_validators = [
|
||||
[
|
||||
'class' => EmailValidator::class,
|
||||
],
|
||||
];
|
||||
|
||||
public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
|
||||
{
|
||||
// Set field with appropriate size
|
||||
$field = EmailField::create($this->name, $title);
|
||||
$field->setMaxLength($this->getSize());
|
||||
|
||||
// Allow the user to select if it's null instead of automatically assuming empty string is
|
||||
if (!$this->getNullifyEmpty()) {
|
||||
return NullableField::create($field);
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
}
|
@ -10,6 +10,8 @@ use SilverStripe\Forms\TextField;
|
||||
use SilverStripe\ORM\Filters\SearchFilter;
|
||||
use SilverStripe\ORM\Queries\SQLSelect;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
/**
|
||||
* Single field in the database.
|
||||
@ -43,7 +45,6 @@ use SilverStripe\Model\ModelData;
|
||||
*/
|
||||
abstract class DBField extends ModelData implements DBIndexable
|
||||
{
|
||||
|
||||
/**
|
||||
* Raw value of this field
|
||||
*/
|
||||
@ -99,6 +100,8 @@ abstract class DBField extends ModelData implements DBIndexable
|
||||
'ProcessedRAW' => 'HTMLFragment',
|
||||
];
|
||||
|
||||
private static array $field_validators = [];
|
||||
|
||||
/**
|
||||
* Default value in the database.
|
||||
* Might be overridden on DataObject-level, but still useful for setting defaults on
|
||||
@ -468,6 +471,22 @@ abstract class DBField extends ModelData implements DBIndexable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this field. Called during DataObject::validate().
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
$result = ValidationResult::create();
|
||||
$fieldValidators = FieldValidator::createFieldValidatorsForField($this, $this->getName(), $this->getValue());
|
||||
foreach ($fieldValidators as $fieldValidator) {
|
||||
$validationResult = $fieldValidator->validate();
|
||||
if (!$validationResult->isValid()) {
|
||||
$result->combineAnd($validationResult);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FormField instance used as a default
|
||||
* for form scaffolding.
|
||||
|
@ -8,6 +8,7 @@ use SilverStripe\Forms\NullableField;
|
||||
use SilverStripe\Forms\TextField;
|
||||
use SilverStripe\ORM\Connect\MySQLDatabase;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Validation\StringLengthValidator;
|
||||
|
||||
/**
|
||||
* Class Varchar represents a variable-length string of up to 255 characters, designed to store raw text
|
||||
@ -18,6 +19,13 @@ use SilverStripe\ORM\DB;
|
||||
*/
|
||||
class DBVarchar extends DBString
|
||||
{
|
||||
private static array $field_validators = [
|
||||
[
|
||||
'class' => StringLengthValidator::class,
|
||||
'argCalls' => [null, 'getSize'],
|
||||
]
|
||||
];
|
||||
|
||||
private static array $casting = [
|
||||
'Initial' => 'Text',
|
||||
'URL' => 'Text',
|
||||
|
24
src/Validation/EmailValidator.php
Normal file
24
src/Validation/EmailValidator.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
use SilverStripe\Core\Validation\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
|
||||
class EmailValidator extends FieldValidator
|
||||
{
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
$message = _t('SilverStripe\\Forms\\EmailField.VALIDATION', 'Please enter an email address');
|
||||
$validationResult = ConstraintValidator::validate(
|
||||
$this->value,
|
||||
new Constraints\Email(message: $message),
|
||||
$this->name
|
||||
);
|
||||
return $result->combineAnd($validationResult);
|
||||
}
|
||||
}
|
67
src/Validation/FieldValidator.php
Normal file
67
src/Validation/FieldValidator.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use RuntimeException;
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
|
||||
/**
|
||||
* Abstract class that can be used as a validator for FormFields and DBFields
|
||||
*/
|
||||
abstract class FieldValidator
|
||||
{
|
||||
protected string $name;
|
||||
protected mixed $value;
|
||||
|
||||
public function __construct(string $name, mixed $value)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
$result = ValidationResult::create();
|
||||
$result = $this->validateValue($result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
abstract protected function validateValue(ValidationResult $result): ValidationResult;
|
||||
|
||||
public static function createFieldValidatorsForField(
|
||||
FormField|DBField $field,
|
||||
string $name,
|
||||
mixed $value
|
||||
): array {
|
||||
$fieldValidators = [];
|
||||
$config = $field->config()->get('field_validators');
|
||||
foreach ($config as $spec) {
|
||||
$class = $spec['class'];
|
||||
$argCalls = $spec['argCalls'] ?? null;
|
||||
if (!is_a($class, FieldValidator::class, true)) {
|
||||
throw new RuntimeException("Class $class is not a FieldValidator");
|
||||
}
|
||||
$args = [$name, $value];
|
||||
if (!is_null($argCalls)) {
|
||||
if (!is_array($argCalls)) {
|
||||
throw new RuntimeException("argCalls for $class is not an array");
|
||||
}
|
||||
foreach ($argCalls as $i => $argCall) {
|
||||
if (!is_string($argCall) && !is_null($argCall)) {
|
||||
throw new RuntimeException("argCall $i for $class is not a string or null");
|
||||
}
|
||||
if ($argCall) {
|
||||
$args[] = call_user_func([$field, $argCall]);
|
||||
} else {
|
||||
$args[] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
$fieldValidators[] = Injector::inst()->createWithArgs($class, $args);
|
||||
}
|
||||
return $fieldValidators;
|
||||
}
|
||||
}
|
33
src/Validation/StringLengthValidator.php
Normal file
33
src/Validation/StringLengthValidator.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
class StringLengthValidator extends FieldValidator
|
||||
{
|
||||
private ?int $minLength;
|
||||
private ?int $maxLength;
|
||||
|
||||
public function __construct(string $name, mixed $value, ?int $minLength = null, ?int $maxLength = null)
|
||||
{
|
||||
parent::__construct($name, $value);
|
||||
$this->minLength = $minLength;
|
||||
$this->maxLength = $maxLength;
|
||||
}
|
||||
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
if (!is_null($this->maxLength) && mb_strlen($this->value ?? '') > $this->maxLength) {
|
||||
$message = _t(
|
||||
'SilverStripe\\Forms\\TextField.VALIDATEMAXLENGTH',
|
||||
'The value for {name} must not exceed {maxLength} characters in length',
|
||||
['name' => $this->name, 'maxLength' => $this->maxLength]
|
||||
);
|
||||
$result->addFieldError($this->name, $message);
|
||||
}
|
||||
// TODO: minlength check
|
||||
return $result;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user