One goal of this library is to make it possible to provide type information for static analysis and for IDEs. Almost every interface and implementation in the library provides type templates to make this possible, and the few extension points exist for the purpose of allowing users to provide type information.
Recall that the ValidationResult
class declares the following as part of its definition:
namespace Phly\RuleValidation;
/**
* @template T
*/
interface ValidationResult
{
/** @return T */
public function value(): mixed;
}
This means that you can indicate that a result wraps a value of a specific type:
/** @var ValidationResult<DateTimeInterface> $date */
echo $date->value()->format('Y-m-d');
The Result
type declares this same template, so you can also write the above as follows if you are using the shipped result type:
/** @var Result<DateTimeInterface> $date */
echo $date->value()->format('Y-m-d');
When you write your own implementations, implement the template:
/** @template-implements ValidationResult<int> */
class IntegerResult implements ValidationResult
{
// ...
}
What if you want to allow multiple types?
/**
* @template T of int|float
* @template-implements ValidationResult<T>
*/
class NumericResult implements ValidationResult
{
/** @return T */
public function value(): int|float
{
}
// ...
}
You can then hint on these types:
/** @var IntegerResult $count */
$count = $form->count;
/** @var NumericResult<float> $currency */
$currency = $form->currency;
As noted in the ResultSet reference, all the methods are marked final; why extend it then?
To provide typehints for the composed results!
/**
* @property-read TextResult $title
* @property-read NumericResult<float> $amount
*/
class TransactionFormResult extends ResultSet
{
}
This will allow you to annotate instances:
/** @var TransactionFormResult $result */
$result = $ruleSet->validate($data);
// Static analysis and IDEs will then understand that
// $amount is a float:
$amount = $result->amount->value();
It also gives you a value you can use with rule sets.
When defining a rule, ensure your validate()
, default()
, and missing()
methods declare what they return.
You could define the more specific ValidationResult
implementation they return:
public function validate(mixed $value, array $context): IntegerResult;
Or you could provide a return annotation that declares the ValidationResult
type:
/**
* @return Result<int>
*/
public function validate(mixed $value, array $context): Result
The RuleSet
class defines @template T of ResultSet
.
This gives you a couple possibilities.
First, when using the RuleSetOptions
, you can provide the result set class to use, and you can then hint to static analysis and IDEs what you are using:
$options = new RuleSetOptions();
$options->setResultSetClass(TransactionFormResult::class);
$options->addRule(/* ... */);
/** @var RuleSet<TransactionFormResult> $ruleSet */
$ruleSet = new RuleSet($options);
Alternately, you could extend the class:
/**
* @template-extends RuleSet<TransactionFormResult>
*/
class TransactionForm extends RuleSet
{
public function __construct()
{
$options = new ResultSetOptions();
$options->setResultSetClass(TransactionFormResult::class);
$options->addRule(/* ... */);
parent::__construct($options);
}
}
Setting the options explicitly in the constructor of the extending class ensures you know the state. If you want to prevent child classes redefining the constructor and changing the semantics and behavior of your ruleset, create a named constructor:
/** * @template-extends RuleSet<TransactionFormResult> */ class TransactionForm extends RuleSet { public static function create(): self { $options = new ResultSetOptions(); $options->setResultSetClass(TransactionFormResult::class); $options->addRule(/* ... */); return new self($options); } }Alternately, you can also make your constructor
final
.
If you are creating an anonymous class extending RuleSet
, use the @template-extends
annotation:
/** @var RuleSet<TransactionForm> $ruleSet */
$ruleSet = new /** @template-extends RuleSet<TransactionFormResult> */ class($options) extends RuleSet {
// ...
};