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 3, 2024
1 parent 7f11bf3 commit 1f4a38a
Show file tree
Hide file tree
Showing 60 changed files with 2,236 additions and 140 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 @@ -47,6 +47,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;
}
32 changes: 32 additions & 0 deletions src/Core/Validation/FieldValidation/BigIntFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?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);
}
}
29 changes: 29 additions & 0 deletions src/Core/Validation/FieldValidation/BooleanFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

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

class BooleanFieldValidator extends FieldValidator
{
// Accept a range of values as valid
private const VALID_VALUES = [
true,
false,
1,
0,
'1',
'0'
];

protected function validateValue(): ValidationResult
{
$result = ValidationResult::create();
if (!in_array($this->value, self::VALID_VALUES, true)) {

Check failure on line 23 in src/Core/Validation/FieldValidation/BooleanFieldValidator.php

View workflow job for this annotation

GitHub Actions / CI / 8.3 mysql80 phplinting

Can't use keyword 'self'. Use 'BooleanFieldValidator' instead.
$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)
{
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);
}
}
parent::__construct($name, $value);
}

protected function validateValue(): ValidationResult
{
$result = ValidationResult::create();
foreach ($this->value as $child) {
$result->combineAnd($child->validate());
}
return $result;
}
}
35 changes: 35 additions & 0 deletions src/Core/Validation/FieldValidation/DateFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?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
*/
class DateFieldValidator extends FieldValidator
{
protected function validateValue(): ValidationResult
{
// Not using symfony/validator because it was allowing d-m-Y format strings
$result = ValidationResult::create();
$date = date_parse_from_format($this->getFormat(), $this->value);
if ($date === false || $date['error_count'] > 0 || $date['warning_count'] > 0) {
$result->addError($this->name, $this->getMessage());
}
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');
}
}
61 changes: 61 additions & 0 deletions src/Core/Validation/FieldValidation/DecimalFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?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', 'Number is too large');
$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');
}
}
27 changes: 27 additions & 0 deletions src/Core/Validation/FieldValidation/EnumFieldValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

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

class EnumFieldValidator extends FieldValidator
{
private 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();
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;
}
}
12 changes: 12 additions & 0 deletions src/Core/Validation/FieldValidation/FieldValidationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace SilverStripe\Core\Validation\FieldValidation;

use SilverStripe\Core\Validation\ValidationInterface;

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

public function getValue(): mixed;
}
Loading

0 comments on commit 1f4a38a

Please sign in to comment.