|
|
@@ -0,0 +1,339 @@
|
|
|
+# PHP Options Resolver
|
|
|
+
|
|
|
+**Strict, Fluent, and Type-Safe Option Validation for PHP.**
|
|
|
+
|
|
|
+Stop guessing what's in your `$options` array. This library provides a robust, fluent API to define, validate, and resolve options with strict type enforcement and custom validation logic. Designed for developers who value clarity and code quality.
|
|
|
+
|
|
|
+## Installation
|
|
|
+
|
|
|
+To install this library, use [Composer](https://getcomposer.org/)
|
|
|
+
|
|
|
+```bash
|
|
|
+composer require michel/options-resolver
|
|
|
+```
|
|
|
+
|
|
|
+## Requirements
|
|
|
+
|
|
|
+* PHP version 7.4 or higher
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Documentation (English)
|
|
|
+
|
|
|
+### Basic Usage
|
|
|
+
|
|
|
+Define the options for your class using `OptionsResolver` with the expected options. You can use static factory methods on the `Option` class to define types easily.
|
|
|
+
|
|
|
+```php
|
|
|
+<?php
|
|
|
+
|
|
|
+use Michel\Resolver\Option;
|
|
|
+use Michel\Resolver\OptionsResolver;
|
|
|
+
|
|
|
+class Database
|
|
|
+{
|
|
|
+ private array $options;
|
|
|
+
|
|
|
+ public function __construct(array $options = [])
|
|
|
+ {
|
|
|
+ $resolver = new OptionsResolver([
|
|
|
+ Option::string('host')->setOptional('localhost'),
|
|
|
+ Option::string('username')->required(),
|
|
|
+ Option::string('password')->required(),
|
|
|
+ Option::string('dbname')->required(),
|
|
|
+ Option::int('port')->setOptional(3306),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $this->options = $resolver->resolve($options);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Example usage:
|
|
|
+try {
|
|
|
+ $database = new Database([
|
|
|
+ 'username' => 'root',
|
|
|
+ 'password' => 'secret',
|
|
|
+ 'dbname' => 'app_db',
|
|
|
+ ]);
|
|
|
+ // 'host' will be 'localhost' and 'port' will be 3306
|
|
|
+} catch (InvalidArgumentException $e) {
|
|
|
+ echo "Error: " . $e->getMessage();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Available Types
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+The `Option` class provides several static factory methods to enforce types automatically. Here are examples for each type:
|
|
|
+
|
|
|
+#### String
|
|
|
+```php
|
|
|
+Option::string('host')->setOptional('localhost');
|
|
|
+```
|
|
|
+
|
|
|
+#### Integer
|
|
|
+```php
|
|
|
+Option::int('port')->setOptional(3306);
|
|
|
+```
|
|
|
+
|
|
|
+#### Float
|
|
|
+```php
|
|
|
+Option::float('timeout')->setOptional(2.5);
|
|
|
+```
|
|
|
+
|
|
|
+#### Boolean
|
|
|
+```php
|
|
|
+Option::bool('active')->setOptional(true);
|
|
|
+```
|
|
|
+
|
|
|
+#### Array
|
|
|
+```php
|
|
|
+Option::array('tags')->setOptional(['php', 'library']);
|
|
|
+```
|
|
|
+
|
|
|
+#### Iterable
|
|
|
+```php
|
|
|
+Option::iterable('items')->required();
|
|
|
+```
|
|
|
+
|
|
|
+#### Mixed (No type enforcement)
|
|
|
+```php
|
|
|
+Option::mixed('metadata')->setOptional(null);
|
|
|
+```
|
|
|
+
|
|
|
+### Required vs Optional
|
|
|
+
|
|
|
+* **Required**: Use `required()` to enforce that an option must be passed. If missing, an exception is thrown.
|
|
|
+* **Optional**: Use `setOptional($defaultValue)` to define a default value if the option is not provided.
|
|
|
+
|
|
|
+```php
|
|
|
+Option::string('apiKey')->required(); // Must be provided
|
|
|
+Option::bool('debug')->setOptional(false); // Defaults to false if missing
|
|
|
+```
|
|
|
+
|
|
|
+### Custom Validation
|
|
|
+
|
|
|
+You can add custom validation logic using the `validator()` method. The closure must return a `bool`.
|
|
|
+
|
|
|
+```php
|
|
|
+Option::string('driver')
|
|
|
+ ->setOptional('mysql')
|
|
|
+ ->validator(function ($value) {
|
|
|
+ return in_array($value, ['mysql', 'pgsql', 'sqlite']);
|
|
|
+ });
|
|
|
+```
|
|
|
+
|
|
|
+### Handling Errors
|
|
|
+
|
|
|
+The `resolve()` method throws an `InvalidArgumentException` if:
|
|
|
+* A required option is missing.
|
|
|
+* An undefined option is provided.
|
|
|
+* An option value is invalid (wrong type or failed custom validation).
|
|
|
+
|
|
|
+### Conditional Requirements
|
|
|
+
|
|
|
+You can make an option required only if another option has a specific value using `addRequiredIf`.
|
|
|
+
|
|
|
+```php
|
|
|
+$resolver = new OptionsResolver([
|
|
|
+ Option::bool('has_database')->setOptional(false),
|
|
|
+ Option::string('db_host')->setOptional(null),
|
|
|
+]);
|
|
|
+
|
|
|
+// 'db_host' becomes required only if 'has_database' is true
|
|
|
+$resolver->addRequiredIf('db_host', 'has_database', true);
|
|
|
+```
|
|
|
+
|
|
|
+### Deprecating Options
|
|
|
+
|
|
|
+You can mark an option as deprecated. A `E_USER_DEPRECATED` error will be triggered if the option is used.
|
|
|
+
|
|
|
+```php
|
|
|
+Option::string('old_option')->deprecate('Use "new_option" instead.');
|
|
|
+```
|
|
|
+
|
|
|
+### Additional Constraints
|
|
|
+
|
|
|
+The library provides helpers for common constraints like `min` and `max`. These work for strings (length), numbers (value), and arrays (count).
|
|
|
+
|
|
|
+```php
|
|
|
+Option::string('username')->min(3)->max(20);
|
|
|
+Option::int('age')->min(18);
|
|
|
+Option::array('tags')->max(5);
|
|
|
+```
|
|
|
+
|
|
|
+### Multiple Validators
|
|
|
+
|
|
|
+You can chain multiple validators. All of them must pass.
|
|
|
+
|
|
|
+```php
|
|
|
+Option::string('code')
|
|
|
+ ->validator(fn($v) => str_starts_with($v, 'A'))
|
|
|
+ ->validator(fn($v) => str_ends_with($v, 'Z'));
|
|
|
+```
|
|
|
+
|
|
|
+### License
|
|
|
+
|
|
|
+This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Documentation (Français)
|
|
|
+
|
|
|
+### Usage de base
|
|
|
+
|
|
|
+Définissez les options attendues pour votre classe en utilisant `OptionsResolver`. Vous pouvez utiliser les méthodes statiques de la classe `Option` pour définir les types facilement.
|
|
|
+
|
|
|
+```php
|
|
|
+<?php
|
|
|
+
|
|
|
+use Michel\Resolver\Option;
|
|
|
+use Michel\Resolver\OptionsResolver;
|
|
|
+
|
|
|
+class Database
|
|
|
+{
|
|
|
+ private array $options;
|
|
|
+
|
|
|
+ public function __construct(array $options = [])
|
|
|
+ {
|
|
|
+ $resolver = new OptionsResolver([
|
|
|
+ Option::string('host')->setOptional('localhost'),
|
|
|
+ Option::string('username')->required(),
|
|
|
+ Option::string('password')->required(),
|
|
|
+ Option::string('dbname')->required(),
|
|
|
+ Option::int('port')->setOptional(3306),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $this->options = $resolver->resolve($options);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Exemple d'utilisation :
|
|
|
+try {
|
|
|
+ $database = new Database([
|
|
|
+ 'username' => 'root',
|
|
|
+ 'password' => 'secret',
|
|
|
+ 'dbname' => 'app_db',
|
|
|
+ ]);
|
|
|
+ // 'host' vaudra 'localhost' et 'port' vaudra 3306
|
|
|
+} catch (InvalidArgumentException $e) {
|
|
|
+ echo "Erreur : " . $e->getMessage();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Types Disponibles
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+La classe `Option` fournit plusieurs méthodes statiques pour forcer les types automatiquement. Voici des exemples pour chaque type :
|
|
|
+
|
|
|
+#### Chaîne de caractères (String)
|
|
|
+```php
|
|
|
+Option::string('host')->setOptional('localhost');
|
|
|
+```
|
|
|
+
|
|
|
+#### Entier (Integer)
|
|
|
+```php
|
|
|
+Option::int('port')->setOptional(3306);
|
|
|
+```
|
|
|
+
|
|
|
+#### Flottant (Float)
|
|
|
+```php
|
|
|
+Option::float('timeout')->setOptional(2.5);
|
|
|
+```
|
|
|
+
|
|
|
+#### Booléen (Boolean)
|
|
|
+```php
|
|
|
+Option::bool('active')->setOptional(true);
|
|
|
+```
|
|
|
+
|
|
|
+#### Tableau (Array)
|
|
|
+```php
|
|
|
+Option::array('tags')->setOptional(['php', 'library']);
|
|
|
+```
|
|
|
+
|
|
|
+#### Itérable (Iterable)
|
|
|
+```php
|
|
|
+Option::iterable('items')->required();
|
|
|
+```
|
|
|
+
|
|
|
+#### Mixte (Mixed - Pas de vérification de type)
|
|
|
+```php
|
|
|
+Option::mixed('metadata')->setOptional(null);
|
|
|
+```
|
|
|
+
|
|
|
+### Requis vs Optionnel
|
|
|
+
|
|
|
+* **Requis** : Utilisez `required()` pour obliger l'utilisateur à fournir une option. Si elle est manquante, une exception est levée.
|
|
|
+* **Optionnel** : Utilisez `setOptional($defaultValue)` pour définir une valeur par défaut si l'option n'est pas fournie.
|
|
|
+
|
|
|
+```php
|
|
|
+Option::string('apiKey')->required(); // Doit être fourni
|
|
|
+Option::bool('debug')->setOptional(false); // Vaut false par défaut si absent
|
|
|
+```
|
|
|
+
|
|
|
+### Validation Personnalisée
|
|
|
+
|
|
|
+Vous pouvez ajouter une logique de validation personnalisée via la méthode `validator()`. La closure doit retourner un `bool`.
|
|
|
+
|
|
|
+```php
|
|
|
+Option::string('driver')
|
|
|
+ ->setOptional('mysql')
|
|
|
+ ->validator(function ($value) {
|
|
|
+ return in_array($value, ['mysql', 'pgsql', 'sqlite']);
|
|
|
+ });
|
|
|
+```
|
|
|
+
|
|
|
+### Gestion des Erreurs
|
|
|
+
|
|
|
+La méthode `resolve()` lance une `InvalidArgumentException` si :
|
|
|
+* Une option requise est manquante.
|
|
|
+* Une option non définie est fournie.
|
|
|
+* Une valeur d'option est invalide (mauvais type ou échec de validation personnalisée).
|
|
|
+
|
|
|
+### Prérequis Conditionnels
|
|
|
+
|
|
|
+Vous pouvez rendre une option obligatoire uniquement si une autre option a une valeur spécifique en utilisant `addRequiredIf`.
|
|
|
+
|
|
|
+```php
|
|
|
+$resolver = new OptionsResolver([
|
|
|
+ Option::bool('has_database')->setOptional(false),
|
|
|
+ Option::string('db_host')->setOptional(null),
|
|
|
+]);
|
|
|
+
|
|
|
+// 'db_host' devient requis uniquement si 'has_database' est true
|
|
|
+$resolver->addRequiredIf('db_host', 'has_database', true);
|
|
|
+```
|
|
|
+
|
|
|
+### Obsolescence (Deprecation)
|
|
|
+
|
|
|
+Vous pouvez marquer une option comme obsolète. Une erreur `E_USER_DEPRECATED` sera déclenchée si l'option est utilisée.
|
|
|
+
|
|
|
+```php
|
|
|
+Option::string('old_option')->deprecate('Utilisez "new_option" à la place.');
|
|
|
+```
|
|
|
+
|
|
|
+### Contraintes Supplémentaires
|
|
|
+
|
|
|
+La bibliothèque fournit des aides pour des contraintes courantes comme `min` et `max`. Elles fonctionnent pour les chaînes (longueur), les nombres (valeur) et les tableaux (nombre d'éléments).
|
|
|
+
|
|
|
+```php
|
|
|
+Option::string('username')->min(3)->max(20);
|
|
|
+Option::int('age')->min(18);
|
|
|
+Option::array('tags')->max(5);
|
|
|
+```
|
|
|
+
|
|
|
+### Validateurs Multiples
|
|
|
+
|
|
|
+Vous pouvez enchaîner plusieurs validateurs. Tous doivent être valides.
|
|
|
+
|
|
|
+```php
|
|
|
+Option::string('code')
|
|
|
+ ->validator(fn($v) => str_starts_with($v, 'A'))
|
|
|
+ ->validator(fn($v) => str_ends_with($v, 'Z'));
|
|
|
+```
|
|
|
+
|
|
|
+### Licence
|
|
|
+
|
|
|
+Ce projet est sous licence MIT. Voir le fichier [LICENSE](LICENSE) pour plus de détails.
|