281 lines
9.2 KiB
PHP
281 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace php;
|
|
|
|
|
|
/**
|
|
* Validates arrays of inputs such as `$_POST` or `$_SESSION` using `Rule`s.
|
|
*/
|
|
class Validator
|
|
{
|
|
/**
|
|
* Checks whether values in `inputs` match the rules specified in `rule_sets`.
|
|
*
|
|
* @param array<string, string> $inputs the array of inputs in which to check the values
|
|
* @param array<string, Rule[]> $rule_sets maps keys in `inputs` to an array of `Rule`s to be checked
|
|
* @return Response|null `null` if all rules are satisfied, or an unsatisfied `Response` otherwise
|
|
*/
|
|
static function validate_inputs(array $inputs, array $rule_sets): Response|null
|
|
{
|
|
foreach ($rule_sets as $key => $rules) {
|
|
foreach ($rules as $rule) {
|
|
$is_valid = $rule->check($inputs, $key);
|
|
if ($is_valid !== null)
|
|
return $is_valid;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* A rule/constraint/assertion that should hold over an input.
|
|
*/
|
|
abstract class Rule
|
|
{
|
|
/**
|
|
* @var string|null The message to return if the rule does not apply to some input. If `null`, the rule
|
|
* implementation can choose an appropriate message.
|
|
*/
|
|
public ?string $override_message;
|
|
|
|
|
|
/**
|
|
* Instantiates a new rule.
|
|
*
|
|
* @param string|null $override_message the message to return if the rule does not apply to some input. If `null`,
|
|
* the rule implementation can choose an appropriate message
|
|
*/
|
|
public function __construct(?string $override_message = null)
|
|
{
|
|
$this->override_message = $override_message;
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks whether the rule holds for `$inputs[$key]`.
|
|
*
|
|
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
|
* @param string $key the key in `inputs` of the input to check
|
|
* @return Response|null `null` if the rule holds, or an unsatisfied `Response` otherwise
|
|
*/
|
|
public abstract function check(array $inputs, string $key): Response|null;
|
|
}
|
|
|
|
/**
|
|
* Requires the input to be a valid email address.
|
|
*/
|
|
class EmailRule extends Rule
|
|
{
|
|
/**
|
|
* Checks whether the input is a valid email address.
|
|
*
|
|
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
|
* @param string $key the key in `inputs` of the input to check
|
|
* @return Response|null `null` if `$inputs[$key]` is an email address, or an unsatisfied `Response` otherwise
|
|
*/
|
|
public function check(array $inputs, string $key): Response|null
|
|
{
|
|
if (!isset($inputs[$key]) || !filter_var($inputs[$key], FILTER_VALIDATE_EMAIL))
|
|
return new Response(
|
|
payload: ["target" => $key, "message" => $this->override_message ?? "Invalid email address."],
|
|
satisfied: false
|
|
);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requires the input to be equal to a pre-known value.
|
|
*/
|
|
class EqualsRule extends Rule
|
|
{
|
|
/**
|
|
* @var string|null The known value to check against, or `null` if the check should always fail.
|
|
*/
|
|
private string|null $known;
|
|
|
|
|
|
/**
|
|
* Instantiates a new rule.
|
|
*
|
|
* @param string|null $known the known value to check against, or `null` if the check should always fail
|
|
* @param string|null $override_message the message to return if the rule does not apply to some input. If `null`,
|
|
* the rule implementation can choose an appropriate message
|
|
*/
|
|
public function __construct(string|null $known, ?string $override_message = null)
|
|
{
|
|
parent::__construct($override_message);
|
|
|
|
$this->known = $known;
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks whether the input equals the known value.
|
|
*
|
|
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
|
* @param string $key the key in `inputs` of the input to check
|
|
* @return Response|null `null` if `$this->known` is not `null` and `$inputs[$key] === $this->known`, or an
|
|
* unsatisfied `Response` otherwise
|
|
*/
|
|
public function check(array $inputs, string $key): Response|null
|
|
{
|
|
if ($this->known === null || !isset($inputs[$key]) || !hash_equals($this->known, $inputs[$key]))
|
|
return new Response(
|
|
payload: [
|
|
"target" => $key,
|
|
"message" => $this->override_message ?? "Input '$key' does not equal known string."
|
|
],
|
|
satisfied: false
|
|
);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requires the input to not be blank.
|
|
*/
|
|
class IsNotBlankRule extends Rule
|
|
{
|
|
/**
|
|
* Checks whether the input is not blank.
|
|
*
|
|
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
|
* @param string $key the key in `inputs` of the input to check
|
|
* @return Response|null `null` if `trim($inputs[$key])` is not an empty string, or an unsatisfied `Response`
|
|
* otherwise
|
|
*/
|
|
public function check(array $inputs, string $key): Response|null
|
|
{
|
|
if (!isset($inputs[$key]) || trim($inputs[$key]) === "")
|
|
return new Response(
|
|
payload: ["target" => $key, "message" => $this->override_message ?? "'$key' should not be blank."],
|
|
satisfied: false
|
|
);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requires the input to be unset.
|
|
*/
|
|
class IsNotSetRule extends Rule
|
|
{
|
|
/**
|
|
* Checks whether the input is unset.
|
|
*
|
|
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
|
* @param string $key the key in `inputs` of the input to check
|
|
* @return Response|null `null` if `!isset($inputs[$key])`, or an unsatisfied `Response` otherwise
|
|
*/
|
|
public function check(array $inputs, string $key): Response|null
|
|
{
|
|
if (isset($inputs[$key]))
|
|
return new Response(
|
|
payload: ["target" => $key, "message" => $this->override_message ?? "Unexpected input '$key'."],
|
|
satisfied: false
|
|
);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requires the input to be set.
|
|
*/
|
|
class IsSetRule extends Rule
|
|
{
|
|
/**
|
|
* Checks whether the input is set.
|
|
*
|
|
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
|
* @param string $key the key in `inputs` of the input to check
|
|
* @return Response|null `null` if `isset($inputs[$key])`, or an unsatisfied `Response` otherwise
|
|
*/
|
|
public function check(array $inputs, string $key): Response|null
|
|
{
|
|
if (!isset($inputs[$key]))
|
|
return new Response(
|
|
payload: ["target" => $key, "message" => $this->override_message ?? "Missing input '$key'."],
|
|
satisfied: false
|
|
);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requires the input to be of a specific length.
|
|
*/
|
|
class LengthRule extends Rule
|
|
{
|
|
/**
|
|
* @var int|null The minimum length (inclusive), or `null` if there is no minimum length.
|
|
*/
|
|
private readonly ?int $min_length;
|
|
/**
|
|
* @var int|null The maximum length (inclusive), or `null` if there is no maximum length.
|
|
*/
|
|
private readonly ?int $max_length;
|
|
|
|
|
|
/**
|
|
* Instantiates a new rule.
|
|
*
|
|
* @param int|null $min_length the minimum length (inclusive), or `null` if there is no minimum length
|
|
* @param int|null $max_length the maximum length (inclusive), or `null` if there is no maximum length
|
|
* @param string|null $override_message the message to return if the rule does not apply to some input. If `null`,
|
|
* the rule implementation can choose an appropriate message
|
|
*/
|
|
public function __construct(?int $min_length = null, ?int $max_length = null, ?string $override_message = null)
|
|
{
|
|
parent::__construct($override_message);
|
|
|
|
$this->min_length = $min_length;
|
|
$this->max_length = $max_length;
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks whether the input is of the specified length.
|
|
*
|
|
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
|
|
* @param string $key the key in `inputs` of the input to check
|
|
* @return Response|null `null` if the input is of the specified length, or an unsatisfied `Response` otherwise
|
|
*/
|
|
public function check(array $inputs, string $key): Response|null
|
|
{
|
|
if (!isset($inputs[$key]))
|
|
return new Response(
|
|
payload: ["target" => $key, "message" => $this->override_message ?? "Missing input '$key'."],
|
|
satisfied: false
|
|
);
|
|
|
|
if ($this->min_length !== null && strlen($inputs[$key]) < $this->min_length)
|
|
return new Response(
|
|
payload: [
|
|
"target" => $key,
|
|
"message" => $this->override_message ?? "'$key' should be at least $this->min_length characters."
|
|
],
|
|
satisfied: false
|
|
);
|
|
|
|
if ($this->max_length !== null && strlen($inputs[$key]) > $this->max_length)
|
|
return new Response(
|
|
payload: [
|
|
"target" => $key,
|
|
"message" => $this->override_message ?? "'$key' should be at most $this->max_length characters."
|
|
],
|
|
satisfied: false
|
|
);
|
|
|
|
return null;
|
|
}
|
|
}
|