Skip to content

Commit 2b7c95c

Browse files
committed
NEW Validate DBFields
1 parent c1a51aa commit 2b7c95c

86 files changed

Lines changed: 3418 additions & 120 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎_config/model.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ SilverStripe\Core\Injector\Injector:
2020
class: SilverStripe\ORM\FieldType\DBDecimal
2121
Double:
2222
class: SilverStripe\ORM\FieldType\DBDouble
23+
Email:
24+
class: SilverStripe\ORM\FieldType\DBEmail
2325
Enum:
2426
class: SilverStripe\ORM\FieldType\DBEnum
2527
Float:
@@ -36,6 +38,8 @@ SilverStripe\Core\Injector\Injector:
3638
class: SilverStripe\ORM\FieldType\DBHTMLVarchar
3739
Int:
3840
class: SilverStripe\ORM\FieldType\DBInt
41+
IP:
42+
class: SilverStripe\ORM\FieldType\DBIp
3943
BigInt:
4044
class: SilverStripe\ORM\FieldType\DBBigInt
4145
Locale:
@@ -58,6 +62,8 @@ SilverStripe\Core\Injector\Injector:
5862
class: SilverStripe\ORM\FieldType\DBText
5963
Time:
6064
class: SilverStripe\ORM\FieldType\DBTime
65+
URL:
66+
class: SilverStripe\ORM\FieldType\DBUrl
6167
Varchar:
6268
class: SilverStripe\ORM\FieldType\DBVarchar
6369
Year:

‎composer.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"symfony/dom-crawler": "^7.0",
4949
"symfony/filesystem": "^7.0",
5050
"symfony/http-foundation": "^7.0",
51+
"symfony/intl": "^7.0",
5152
"symfony/mailer": "^7.0",
5253
"symfony/mime": "^7.0",
5354
"symfony/translation": "^7.0",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace SilverStripe\Core\Validation\FieldValidation;
4+
5+
use RunTimeException;
6+
use SilverStripe\Core\Validation\FieldValidation\IntFieldValidator;
7+
8+
/**
9+
* A field validator for 64-bit integers
10+
* Will throw a RunTimeException if used on a 32-bit system
11+
*/
12+
class BigIntFieldValidator extends IntFieldValidator
13+
{
14+
/**
15+
* The minimum value for a signed 64-bit integer.
16+
* Defined as string instead of int otherwise will end up as a float
17+
* on 64-bit systems
18+
*
19+
* When this is cast to an int in IntFieldValidator::__construct()
20+
* it will be properly cast to an int
21+
*/
22+
protected const MIN_INT = '-9223372036854775808';
23+
24+
/**
25+
* The maximum value for a signed 64-bit integer.
26+
*/
27+
protected const MAX_INT = '9223372036854775807';
28+
29+
public function __construct(
30+
string $name,
31+
mixed $value,
32+
?int $minValue = null,
33+
?int $maxValue = null
34+
) {
35+
if (is_null($minValue) || is_null($maxValue)) {
36+
$bits = strlen(decbin(~0));
37+
if ($bits === 32) {
38+
throw new RunTimeException('Cannot use BigIntFieldValidator on a 32-bit system');
39+
}
40+
}
41+
parent::__construct($name, $value, $minValue, $maxValue);
42+
}
43+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace SilverStripe\Core\Validation\FieldValidation;
4+
5+
use SilverStripe\Core\Validation\ValidationResult;
6+
use SilverStripe\Core\Validation\FieldValidation\FieldValidator;
7+
8+
/**
9+
* Validates that a value is a boolean
10+
*/
11+
class BooleanFieldValidator extends FieldValidator
12+
{
13+
protected function validateValue(): ValidationResult
14+
{
15+
$result = ValidationResult::create();
16+
if (!is_bool($this->value)) {
17+
$message = _t(__CLASS__ . '.INVALID', 'Invalid value');
18+
$result->addFieldError($this->name, $message);
19+
}
20+
return $result;
21+
}
22+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace SilverStripe\Core\Validation\FieldValidation;
4+
5+
use InvalidArgumentException;
6+
use SilverStripe\Core\Validation\ValidationResult;
7+
use SilverStripe\Core\Validation\FieldValidation\FieldValidator;
8+
use SilverStripe\Core\Validation\FieldValidation\FieldValidationInterface;
9+
10+
/**
11+
* A field validator used to validate DBComposite fields
12+
*/
13+
class CompositeFieldValidator extends FieldValidator
14+
{
15+
/**
16+
* @param mixed $value - an iterable list of FieldValidators
17+
*/
18+
public function __construct(string $name, mixed $value)
19+
{
20+
parent::__construct($name, $value);
21+
if (!is_iterable($value)) {
22+
throw new InvalidArgumentException('Value must be iterable');
23+
}
24+
foreach ($value as $child) {
25+
if (!is_a($child, FieldValidationInterface::class)) {
26+
throw new InvalidArgumentException('Child is not a' . FieldValidationInterface::class);
27+
}
28+
}
29+
}
30+
31+
protected function validateValue(): ValidationResult
32+
{
33+
$result = ValidationResult::create();
34+
foreach ($this->value as $child) {
35+
$result->combineAnd($child->validate());
36+
}
37+
return $result;
38+
}
39+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace SilverStripe\Core\Validation\FieldValidation;
4+
5+
use SilverStripe\Core\Validation\FieldValidation\FieldValidator;
6+
use SilverStripe\Core\Validation\ValidationResult;
7+
8+
/**
9+
* Validates that a value is a valid date, which means that it follows the equivalent formats:
10+
* - PHP date format Y-m-d
11+
* - SO format y-MM-dd i.e. DBDate::ISO_DATE
12+
*
13+
* Blank string values are allowed
14+
*/
15+
class DateFieldValidator extends FieldValidator
16+
{
17+
protected function validateValue(): ValidationResult
18+
{
19+
$result = ValidationResult::create();
20+
// Allow empty strings
21+
if ($this->value === '') {
22+
return $result;
23+
}
24+
// Not using symfony/validator because it was allowing d-m-Y format strings
25+
$date = date_parse_from_format($this->getFormat(), $this->value ?? '');
26+
if ($date === false || $date['error_count'] > 0 || $date['warning_count'] > 0) {
27+
$result->addFieldError($this->name, $this->getMessage());
28+
}
29+
return $result;
30+
}
31+
32+
protected function getFormat(): string
33+
{
34+
return 'Y-m-d';
35+
}
36+
37+
protected function getMessage(): string
38+
{
39+
return _t(__CLASS__ . '.INVALID', 'Invalid date');
40+
}
41+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace SilverStripe\Core\Validation\FieldValidation;
4+
5+
use SilverStripe\Core\Validation\FieldValidation\DateFieldValidator;
6+
7+
/**
8+
* Validates that a value is a valid date/time, which means that it follows the equivalent formats:
9+
* - PHP date format Y-m-d H:i:s
10+
* - ISO format 'y-MM-dd HH:mm:ss' i.e. DBDateTime::ISO_DATETIME
11+
*
12+
* Blank string values are allowed
13+
*/
14+
class DatetimeFieldValidator extends DateFieldValidator
15+
{
16+
protected function getFormat(): string
17+
{
18+
return 'Y-m-d H:i:s';
19+
}
20+
21+
protected function getMessage(): string
22+
{
23+
return _t(__CLASS__ . '.INVALID', 'Invalid date/time');
24+
}
25+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace SilverStripe\Core\Validation\FieldValidation;
4+
5+
use SilverStripe\Core\Validation\ValidationResult;
6+
use SilverStripe\Core\Validation\FieldValidation\NumericFieldValidator;
7+
8+
/**
9+
* Validates that a value is a valid decimal
10+
* This intended for use when validating that a value can be stored in a database as a decimal
11+
*
12+
* Example of how digits are stored in the database
13+
* Decimal(5,2) is allowed a total of 5 digits, and will always round to 2 decimal places
14+
* This means it has a maximum 3 digits before the decimal point
15+
*
16+
* Valid
17+
* 123.99
18+
* 999.99
19+
* -999.99
20+
* 123.999 - will round to 124.00
21+
*
22+
* Not valid
23+
* 1234.9 - 4 digits the before the decimal point
24+
* 999.999 - would be rounded to 1000.00 which exceeds 5 total digits
25+
*/
26+
class DecimalFieldValidator extends NumericFieldValidator
27+
{
28+
/**
29+
* Whole number size e.g. For Decimal(9,2) this would be 9
30+
*/
31+
private int $wholeSize;
32+
33+
/**
34+
* Decimal size e.g. For Decimal(5,2) this would be 2
35+
*/
36+
private int $decimalSize;
37+
38+
public function __construct(string $name, mixed $value, int $wholeSize, int $decimalSize)
39+
{
40+
parent::__construct($name, $value);
41+
$this->wholeSize = $wholeSize;
42+
$this->decimalSize = $decimalSize;
43+
}
44+
45+
protected function validateValue(): ValidationResult
46+
{
47+
$result = parent::validateValue();
48+
if (!$result->isValid()) {
49+
return $result;
50+
}
51+
// Convert to absolute value - the minus sign is not relevant for validation
52+
$absValue = abs($this->value);
53+
// Round to the decimal size which is what the database will do
54+
$rounded = round($absValue, $this->decimalSize);
55+
// Get formatted as a string, which will right pad with zeros to the decimal size
56+
$rounded = number_format($rounded, $this->decimalSize, thousands_separator: '');
57+
// Count this number of digits - the minus 1 is for the decimal point
58+
$digitCount = strlen((string) $rounded) - 1;
59+
if ($digitCount > $this->wholeSize) {
60+
$message = _t(
61+
__CLASS__ . '.TOOLARGE',
62+
'Cannot have more than {wholeSize} digits, including after the decimal point',
63+
['wholeSize' => $this->wholeSize]
64+
);
65+
$result->addFieldError($this->name, $message);
66+
}
67+
return $result;
68+
}
69+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace SilverStripe\Core\Validation\FieldValidation;
4+
5+
use Symfony\Component\Validator\Constraint;
6+
use Symfony\Component\Validator\Constraints\Email;
7+
use SilverStripe\Core\Validation\FieldValidation\StringFieldValidator;
8+
use SilverStripe\Core\Validation\FieldValidation\SymfonyFieldValidatorTrait;
9+
use SilverStripe\Core\Validation\FieldValidation\SymfonyFieldValidatorInterface;
10+
11+
/**
12+
* Validates that a value is a valid email address
13+
* Uses Symfony's Email constraint to validate
14+
*/
15+
class EmailFieldValidator extends StringFieldValidator implements SymfonyFieldValidatorInterface
16+
{
17+
use SymfonyFieldValidatorTrait;
18+
19+
public function getConstraint(): Constraint|array
20+
{
21+
$message = _t(__CLASS__ . '.INVALID', 'Invalid email address');
22+
return new Email(message: $message);
23+
}
24+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace SilverStripe\Core\Validation\FieldValidation;
4+
5+
use SilverStripe\Core\Validation\ValidationInterface;
6+
7+
/**
8+
* Interface for fields e.g. a DBField or FormField, that can use FieldValidator's
9+
* Intended for use on classes that have the FieldValidationTrait applied
10+
*/
11+
interface FieldValidationInterface extends ValidationInterface
12+
{
13+
public function getName(): string;
14+
15+
public function getValue(): mixed;
16+
17+
public function getValueForValidation(): mixed;
18+
}

0 commit comments

Comments
 (0)