Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: valid rule set, type templates, nested rule set #2

Closed
wants to merge 8 commits into from

Conversation

weierophinney
Copy link
Contributor

This patch adds a number of features:

  • Modifies ResultSet to allow extension; this is necessary so that developers can extend and provide @property or @property-read annotations mapping keys to specific result types.
  • Adds a type template to Result, allowing the ability to indicate the expected type for the composed $value.
  • Adds a type template to RuleSet, allowing the ability to indicate the expected ResultSet class from validate().
  • Adds a protected string property to RuleSet, $resultSetClass.
  • Adds RuleSet::createWithResultSetClass() allows creating a RuleSet that will use the specified ResultSet class when creating a new result set during validation.
  • Adds RuleSet::createValidResultSet(), which produces a valid result set with Result instances containing the values provided to the method, or the associated default values from the associated Rules; this is generally for use with pre-populating forms in order to minimize logic.
  • Modifies Result to remove the final and readonly keywords
    • All properties are defined as readonly
    • All methods are defined as final
    • The constructor is defined as final and protected, to ensure that the named constructors work with extensions.
  • Adds NestedResult; this is intended to allow composing a ResultSet as a $value of a NestedResult, which then will proxy property calls to the nested Result instances of the composed ResultSet.

These features allow better extension, improved type inference, and greater flexibility around nested data sets.

This method makes it possible to create an initial valid result set for use in populating a form.
Having this method makes the following possible:

```php
<?php $form = $ruleSet->createValidResultSet(); ?>

<!-- In a template: -->
<input type="text" name="first" value="<?= $this->e($form->first->value) ?>" />
<?php if (! $form->first->isValid): ?>
<p class="text-danger">The value you provided is invalid: <?= $this->e($form->first->message) ?></p>
<?php endif ?>
```

To make the tests work, I also needed to provide an `__isset()` implementation for `ResultSet`.
Rounds out remaining functionality of `createValidResultSet()`:

- ResultSet MUST NOT contain mappings for any `$valueMap` keys NOT in the rule set
- ResultSet MUST use `null` for any OPTIONAL rules with no default value set
- ResultSet MUST raise an exception if a key does not exist in the `$valueMap`, maps to a required rule, but has no default value.
  - Adds `RequiredRuleWithNoDefaultValueException` as the exception type for this
- Method MUST create a `ResultSet` of the type provided in `$resultSetClass` if a class was provided.
  - This required making the `ResultSet` class non-final; all methods were marked final, however.

To make this functionality more predictable and useful to developers:

- Updated `Result` to add a `@template T` annotation, and assigns `@var T` to the `$value` property.
- This allows the following patterns:
  - Extending `ResultSet` and defining `@property(-read)` mappings:

    ```php
    /**
     * @property-read Result<int> $count
     * @property-read Result<string> $title
     */
    class CustomResultSet extends ResultSet { }
    ```

  - Providing a template for a returned result set:

    ```php
    /** @var ResultSet{count: Result<int>, title: Result<string>} $form */
    ```
- Moves the definition of a custom result set class to use with a rule set to a new named constructor, `RuleSet::createWithResultSetClass()`.
  - This means that for the 80% use case, you will just use the constructor, with or without rules.
  - When you want to specify a custom result set class to use, use the named constructor.

- You can provide Psalm/PHPStan hints when creating a ResultSet or RuleSet

  ```php
  /** @var RuleSet<ResultSet> $ruleSet */
  $ruleSet = new RuleSet();

  /** @var ResultSet{title: string, description: string, createdDate: DateTimeImmutable} $result */
  $result = $ruleSet->validate($data);
  ```
- Makes `RuleSet::$resultSetClass` protected, to simplify extension.
- Documents how to provide types for results, result sets, and rule sets via template annotations.
Documents it and provides some examples.
- Removes `final` and `readonly` keywords from `Result`.
  - All properties are marked readonly
  - All methods are marked final
  - Named constructors use `static` when creating new instances, and `@return Result<T>`
  - constructor is made protected and marked final, to allow Psalm to validate that the named constructors will work with the class constructor
- `NestedResult` extends `Result, and adds `__isset()` and `__get()` implementations that check for nested result sets and the properties it defines.
@weierophinney
Copy link
Contributor Author

Turns out this was just the beginning of a comprehensive rewrite; will create a new PR with full rewrite.

@weierophinney weierophinney deleted the feat/create-valid-result-set branch November 20, 2023 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant