Skip to content

Commit

Permalink
NEW Validate DBFields
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Oct 21, 2024
1 parent ba97de9 commit 3fcdfb4
Show file tree
Hide file tree
Showing 90 changed files with 3,525 additions and 271 deletions.
6 changes: 6 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 All @@ -36,6 +38,8 @@ SilverStripe\Core\Injector\Injector:
class: SilverStripe\ORM\FieldType\DBHTMLVarchar
Int:
class: SilverStripe\ORM\FieldType\DBInt
IP:
class: SilverStripe\ORM\FieldType\DBIp
BigInt:
class: SilverStripe\ORM\FieldType\DBBigInt
Locale:
Expand All @@ -58,6 +62,8 @@ SilverStripe\Core\Injector\Injector:
class: SilverStripe\ORM\FieldType\DBText
Time:
class: SilverStripe\ORM\FieldType\DBTime
URL:
class: SilverStripe\ORM\FieldType\DBUrl
Varchar:
class: SilverStripe\ORM\FieldType\DBVarchar
Year:
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"symfony/dom-crawler": "^7.0",
"symfony/filesystem": "^7.0",
"symfony/http-foundation": "^7.0",
"symfony/intl": "^7.0",
"symfony/mailer": "^7.0",
"symfony/mime": "^7.0",
"symfony/translation": "^7.0",
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Validation/ConstraintValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ public static function validate(mixed $value, Constraint|array $constraints, str
/** @var ConstraintViolationInterface $violation */
foreach ($violations as $violation) {
if ($fieldName) {
$result->addFieldError($fieldName, $violation->getMessage());
$result->addFieldError($fieldName, $violation->getMessage(), value: $value);
} else {
$result->addError($violation->getMessage());
$result->addError($violation->getMessage(), value: $value);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use SilverStripe\Core\Validation\ValidationResult;
use SilverStripe\Core\Validation\ConstraintValidator;
use SilverStripe\Core\Validation\FieldValidation\StringFieldValidator;

/**
* Abstract class for validators that use Symfony constraints
*/
abstract class AbstractSymfonyFieldValidator extends StringFieldValidator
{
protected function validateValue(): ValidationResult
{
$result = parent::validateValue();
if (!$result->isValid()) {
return $result;
}
$constraintClass = $this->getConstraintClass();
$args = [
...$this->getContraintNamedArgs(),
'message' => $this->getMessage(),
];
$constraint = new $constraintClass(...$args);
$validationResult = ConstraintValidator::validate($this->value, $constraint, $this->name);
return $result->combineAnd($validationResult);
}

/**
* The symfony constraint class to use
*/
abstract protected function getConstraintClass(): string;

/**
* The named args to pass to the constraint
* Defined named args as assoc array keys
*/
protected function getContraintNamedArgs(): array
{
return [];
}

/**
* The message to use when the value is invalid
*/
abstract protected function getMessage(): string;
}
36 changes: 36 additions & 0 deletions src/Core/Validation/FieldValidation/BigIntFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use SilverStripe\Core\Validation\FieldValidation\IntFieldValidator;

class BigIntFieldValidator extends IntFieldValidator
{
/**
* The minimum value for a signed 64-bit integer.
* Defined as string instead of int otherwise will end up as a float
* on 64-bit systems if defined as an int
*/
private const MIN_64_BIT_INT = '-9223372036854775808';

/**
* The maximum value for a signed 64-bit integer.
*/
private const MAX_64_BIT_INT = '9223372036854775807';

public function __construct(
string $name,
mixed $value,
?int $minValue = null,
?int $maxValue = null
) {
if (is_null($minValue)) {
// Casting the string const to an int will properly return an int on 64-bit systems
$minValue = (int) BigIntFieldValidator::MIN_64_BIT_INT;
}
if (is_null($maxValue)) {
$maxValue = (int) BigIntFieldValidator::MAX_64_BIT_INT;
}
parent::__construct($name, $value, $minValue, $maxValue);
}
}
23 changes: 23 additions & 0 deletions src/Core/Validation/FieldValidation/BooleanFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use SilverStripe\Core\Validation\ValidationResult;
use SilverStripe\Core\Validation\FieldValidation\FieldValidator;

/**
* Validates value is boolean stored as an integer i.e. 1 or 0
* true and false are not valid values
*/
class BooleanFieldValidator extends FieldValidator
{
protected function validateValue(): ValidationResult
{
$result = ValidationResult::create();
if ($this->value !== true && $this->value !== false) {
$message = _t(__CLASS__ . '.INVALID', 'Invalid value');
$result->addFieldError($this->name, $message, value: $this->value);
}
return $result;
}
}
33 changes: 33 additions & 0 deletions src/Core/Validation/FieldValidation/CompositeFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use InvalidArgumentException;
use SilverStripe\Core\Validation\ValidationResult;
use SilverStripe\Core\Validation\FieldValidation\FieldValidator;
use SilverStripe\Core\Validation\FieldValidation\FieldValidationInterface;

class CompositeFieldValidator extends FieldValidator
{
public function __construct(string $name, mixed $value)
{
parent::__construct($name, $value);
if (!is_iterable($value)) {
throw new InvalidArgumentException('Value must be iterable');
}
foreach ($value as $child) {
if (!is_a($child, FieldValidationInterface::class)) {
throw new InvalidArgumentException('Child is not a' . FieldValidationInterface::class);
}
}
}

protected function validateValue(): ValidationResult
{
$result = ValidationResult::create();
foreach ($this->value as $child) {
$result->combineAnd($child->validate());
}
return $result;
}
}
40 changes: 40 additions & 0 deletions src/Core/Validation/FieldValidation/DateFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use SilverStripe\Core\Validation\FieldValidation\FieldValidator;
use SilverStripe\Core\Validation\ValidationResult;

/**
* Validates that a value is a valid date, which means that it follows the equivalent formats:
* - PHP date format Y-m-d
* - SO format y-MM-dd i.e. DBDate::ISO_DATE
* Emtpy values are allowed
*/
class DateFieldValidator extends FieldValidator
{
protected function validateValue(): ValidationResult
{
$result = ValidationResult::create();
// Allow empty strings
if ($this->value === '') {
return $result;
}
// Not using symfony/validator because it was allowing d-m-Y format strings
$date = date_parse_from_format($this->getFormat(), $this->value ?? '');
if ($date === false || $date['error_count'] > 0 || $date['warning_count'] > 0) {
$result->addFieldError($this->name, $this->getMessage(), value: $this->value);
}
return $result;
}

protected function getFormat(): string
{
return 'Y-m-d';
}

protected function getMessage(): string
{
return _t(__CLASS__ . '.INVALID', 'Invalid date');
}
}
23 changes: 23 additions & 0 deletions src/Core/Validation/FieldValidation/DatetimeFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use SilverStripe\Core\Validation\FieldValidation\DateFieldValidator;

/**
* Validates that a value is a valid date/time, which means that it follows the equivalent formats:
* - PHP date format Y-m-d H:i:s
* - ISO format 'y-MM-dd HH:mm:ss' i.e. DBDateTime::ISO_DATETIME
*/
class DatetimeFieldValidator extends DateFieldValidator
{
protected function getFormat(): string
{
return 'Y-m-d H:i:s';
}

protected function getMessage(): string
{
return _t(__CLASS__ . '.INVALID', 'Invalid date/time');
}
}
65 changes: 65 additions & 0 deletions src/Core/Validation/FieldValidation/DecimalFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use SilverStripe\Core\Validation\ValidationResult;
use SilverStripe\Core\Validation\FieldValidation\NumericFieldValidator;

class DecimalFieldValidator extends NumericFieldValidator
{
/**
* Whole number size e.g. For Decimal(9,2) this would be 9
*/
private int $wholeSize;

/**
* Decimal size e.g. For Decimal(5,2) this would be 2
*/
private int $decimalSize;

public function __construct(string $name, mixed $value, int $wholeSize, int $decimalSize)
{
parent::__construct($name, $value);
$this->wholeSize = $wholeSize;
$this->decimalSize = $decimalSize;
}

protected function validateValue(): ValidationResult
{
$result = parent::validateValue();
if (!$result->isValid()) {
return $result;
}
// Example of how digits are stored in the database
// Decimal(5,2) is allowed a total of 5 digits, and will always round to 2 decimal places
// This means it has a maximum 3 digits before the decimal point
//
// Valid
// 123.99
// 999.99
// -999.99
// 123.999 - will round to 124.00
//
// Not valid
// 1234.9 - 4 digits the before the decimal point
// 999.999 - would be rounted to 10000000.00 which exceeds the 9 digits

// Convert to absolute value - any the minus sign is not counted
$absValue = abs($this->value);
// Round to the decimal size which is what the database will do
$rounded = round($absValue, $this->decimalSize);
// Get formatted as a string, which will right pad with zeros to the decimal size
$rounded = number_format($rounded, $this->decimalSize, thousands_separator: '');
// Count this number of digits - the minus 1 is for the decimal point
$digitCount = strlen((string) $rounded) - 1;
if ($digitCount > $this->wholeSize) {
$message = _t(
__CLASS__ . '.TOOLARGE',
'Digit count cannot be greater than than {wholeSize}',
['wholeSize' => $this->wholeSize]
);
$result->addFieldError($this->name, $message, value: $this->value);
}
return $result;
}
}
19 changes: 19 additions & 0 deletions src/Core/Validation/FieldValidation/EmailFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use Symfony\Component\Validator\Constraints;
use SilverStripe\Core\Validation\FieldValidation\AbstractSymfonyFieldValidator;

class EmailFieldValidator extends AbstractSymfonyFieldValidator
{
protected function getConstraintClass(): string
{
return Constraints\Email::class;
}

protected function getMessage(): string
{
return _t(__CLASS__ . '.INVALID', 'Invalid email address');
}
}
31 changes: 31 additions & 0 deletions src/Core/Validation/FieldValidation/EnumFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use SilverStripe\Core\Validation\ValidationResult;
use SilverStripe\Core\Validation\FieldValidation\FieldValidator;

class EnumFieldValidator extends FieldValidator
{
protected array $allowedValues;

public function __construct(string $name, mixed $value, array $allowedValues)
{
parent::__construct($name, $value);
$this->allowedValues = $allowedValues;
}

protected function validateValue(): ValidationResult
{
$result = ValidationResult::create();
// Allow empty strings
if ($this->value === '') {
return $result;
}
if (!in_array($this->value, $this->allowedValues, true)) {
$message = _t(__CLASS__ . '.NOTALLOWED', 'Not an allowed value');
$result->addFieldError($this->name, $message, value: $this->value);
}
return $result;
}
}
14 changes: 14 additions & 0 deletions src/Core/Validation/FieldValidation/FieldValidationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use SilverStripe\Core\Validation\ValidationInterface;

interface FieldValidationInterface extends ValidationInterface
{
public function getName(): string;

public function getValue(): mixed;

public function getValueForValidation(): mixed;
}
Loading

0 comments on commit 3fcdfb4

Please sign in to comment.