Skip to content

Commit

Permalink
NEW Validate DBFields
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Sep 25, 2024
1 parent c523022 commit 0db4cf4
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 63 deletions.
2 changes: 2 additions & 0 deletions _config/model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
40 changes: 6 additions & 34 deletions src/Forms/EmailField.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

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 = [
EmailValidator::class
];

protected $inputType = 'email';
/**
Expand All @@ -17,39 +22,6 @@ public function Type()
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();
Expand Down
20 changes: 17 additions & 3 deletions src/Forms/FormField.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1231,15 +1235,25 @@ protected function extendValidationResult(bool $result, Validator $validator): b
}

/**
* 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);
}

/**
Expand Down
31 changes: 6 additions & 25 deletions src/Forms/TextField.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace SilverStripe\Forms;

use SilverStripe\Validation\StringLengthValidator;

/**
* Text input field.
*/
Expand All @@ -14,6 +16,10 @@ class TextField extends FormField implements TippableFieldInterface

protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_TEXT;

private static array $field_validators = [
StringLengthValidator::class => [null, 'getMaxLength']
];

/**
* @var Tip|null A tip to render beside the input
*/
Expand Down Expand Up @@ -117,31 +123,6 @@ public function getSchemaDataDefaults()
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();
Expand Down
9 changes: 9 additions & 0 deletions src/ORM/DataObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,15 @@ public function forceChange()
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;
}
Expand Down
29 changes: 29 additions & 0 deletions src/ORM/FieldType/DBEmail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?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 = [
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;
}
}
21 changes: 20 additions & 1 deletion src/ORM/FieldType/DBField.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
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.
Expand Down Expand Up @@ -43,7 +45,6 @@
*/
abstract class DBField extends ModelData implements DBIndexable
{

/**
* Raw value of this field
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -468,6 +471,22 @@ public function saveInto(ModelData $model): void
}
}

/**
* 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.
Expand Down
5 changes: 5 additions & 0 deletions src/ORM/FieldType/DBVarchar.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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
Expand All @@ -18,6 +19,10 @@
*/
class DBVarchar extends DBString
{
private static array $field_validators = [
StringLengthValidator::class => [null, 'getSize'],
];

private static array $casting = [
'Initial' => 'Text',
'URL' => 'Text',
Expand Down
24 changes: 24 additions & 0 deletions src/Validation/EmailValidator.php
Original file line number Diff line number Diff line change
@@ -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);
}
}
72 changes: 72 additions & 0 deletions src/Validation/FieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?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 $classOrIndex => $classOrExtraArgMethods) {
if (is_int($classOrIndex)) {
$class = $classOrExtraArgMethods;
$extraArgMethods = [];
} else {
$class = $classOrIndex;
$extraArgMethods = $classOrExtraArgMethods;
}
if (!is_a($class, FieldValidator::class, true)) {
throw new RuntimeException("Class $class is not a FieldValidator");
}
if (!is_array($extraArgMethods)) {
throw new RuntimeException("extra arg methods for $class is not an array");
}
$args = [$name, $value];
if (!is_null($extraArgMethods)) {
foreach ($extraArgMethods as $i => $extraArgMethod) {
if (!is_string($extraArgMethod) && !is_null($extraArgMethod)) {
throw new RuntimeException("extra arg method $i for $class is not a string or null");
}
if ($extraArgMethod) {
$args[] = call_user_func([$field, $extraArgMethod]);
} else {
$args[] = null;
}
}
}
$fieldValidators[] = Injector::inst()->createWithArgs($class, $args);
}
return $fieldValidators;
}
}
Loading

0 comments on commit 0db4cf4

Please sign in to comment.