Nette Schema

repository·master·Indexed 21 days ago

https://github.com/nette/schema

A PHP library for validating and normalizing data structures against defined schemas. It provides a fluent API via the Expect class to define types, structures, arrays, and constraints, ensuring input data is correct and transformed into desired formats such as stdClass or specific objects. Features include custom assertions, data transformation, casting to classes, and support for PHP 8.1 through 8.5.

Tokens
5.8K
Snippets
20
Records
32
Agent score
77%

What's inside nette-schema

  1. Understand default value merging behavior

    master

    In Schema 2.0, Type::$merge defaults to false. This means that if a partially supplied array is provided, the default values defined in the schema will not be merged into it; the array remains partial.

    While mergeDefaults() is still available, it is deprecated and scheduled for removal in the next major version.

  2. Configure Schema merging behavior

    master

    When combining multiple layers of data (e.g., via processMultiple), you can control how they merge using MergeMode.

    Available modes:

    • Replace: The new layer replaces the base layer entirely.
    • OverwriteKeys: Merges by keys; numeric keys are overwritten positionally.
    • AppendKeys: Merges by keys and appends new numeric elements.

    Note on v1 compatibility: The magic key '_prevent_merging' has been removed. To achieve the same effect in v2, use mergeMode(MergeMode::Replace) or the NEON key!: syntax (if using Nette DI).

  3. How error accumulation works in Schema

    master

    Errors in nette/schema are collected in a Context object and are never thrown mid-validation. This allows the engine to collect multiple errors before reporting them.

    To prevent a validation step from running on data that has already failed a previous check, developers must use the $isOk() short-circuit pattern provided by Context::createChecker(). This ensures that later logic never attempts to validate or transform 'garbage' data rejected by earlier steps.

    $isOk = $context->createChecker();
    Helpers::validateType(...);
    $isOk() && Helpers::validateRange(...);
    $isOk() && ... && $value = $this->doTransform(...);
  4. How Expect::from() maps objects and classes to schemas

    master

    The Expect::from() method allows you to automatically generate a schema from an existing object instance or a class name. Since version 2.0, it uses reflection to determine the schema structure based on the following rules:

    1. Constructor vs. Properties: If the class has a __construct method, the schema is built from its constructor parameters. If no constructor exists, the schema is built from its properties.
    2. Type Detection: Types are derived strictly from native PHP type declarations. Support for phpDoc @var annotations was removed in 2.0.
    3. Required vs. Default:
      • Parameters/properties with no default value (uninitialized or non-optional) are marked as required().
      • Parameters/properties with a default value (including null) are marked with default($value).
    4. Nesting:
      • Non-nullable class-typed items will automatically recurse into a nested Expect::from() schema.
      • If a default value is an object, the schema recurses into a nested from() using that instance.
    5. Output: The resulting schema is a Structure that, when processed, casts the input array to a stdClass and then to the target class instance.
  5. Understand the castTo() casting strategies

    master

    When calling castTo($type), the library uses Helpers::getCastStrategy to determine how to transform the input data into the target type. The behavior depends on the target $type:

    • Built-in types: Uses standard PHP settype (e.g., casting to int, string, etc.).
    • Classes with a constructor: The input array or stdClass is passed as named arguments to the constructor.
    • Classes without a constructor: The input is converted to an object, and properties are assigned via property assignment (using Arrays::toObject).
    • Enums: Note that Enums do not have constructors and fall into the new $type path, which will result in a PHP Error. Use specific enum handling if required.
    • Scalars: If a scalar is passed to a class-based castTo, it is passed as a single argument to the constructor.
  6. How to validate and normalize data with Processor

    master

    The Nette\Schema\Processor class is the main entry point. You provide a schema (defined via Expect) and the input data. The process() method returns normalized data (usually a stdClass object) or throws a Nette\Schema\ValidationException if validation fails.

    To handle errors, catch ValidationException. You can retrieve error messages via $e->getMessages() or detailed Nette\Schema\Message objects via $e->getMessageObjects().

    $processor = new Nette\Schema\Processor;
    
    try {
    	$normalized = $processor->process($schema, $data);
    } catch (Nette\Schema\ValidationException $e) {
    	echo 'Data is invalid: ' . $e->getMessage();
    }
  7. Understand the transformation pipeline order

    master

    The methods before(), transform(), assert(), and castTo() are not independent stages but parts of a single pipeline.

    1. before(): Runs during normalize() (pre-validation). Note that calling before() a second time will silently replace the first one.
    2. transform() / assert() / castTo(): These all append to a single transformation list and execute in declaration order during the complete() phase, after type/range/pattern validation.

    Because they run in order, the sequence matters: ->assert()->castTo() behaves differently than ->castTo()->assert().

  8. Understand the Schema processing lifecycle

    master

    The Schema object does not have a separate validate() phase. Instead, validation is integrated into the complete() step of the processing pipeline.

    When using Processor::process(), the following sequence occurs:

    1. normalize(): Data is normalized.
    2. complete(): Data is validated (type checking, ranges, patterns, recursion) and transformed.
    3. throwsErrors(): Errors are checked and thrown if present.

    When using Processor::processMultiple(), the behavior changes slightly:

    • before() hooks run for each individual dataset item during the normalization phase.
    • transform(), assert(), and castTo() run once on the final, merged result during the single complete() phase.

    This means before() sees individual configuration layers, while transform() sees the entire merged configuration.

  9. Handle nulls and defaults in Schema

    master

    There is a distinction between a type's default value and its nullability:

    • A Type's default is null by default, but the type does not accept null unless nullable() is explicitly called.
    • Array Coercion: If a type's default is an array, complete() will unconditionally turn a null value into an empty array []. This happens even if the type is marked nullable().
    • Merging Defaults: In v2, defaults are no longer automatically merged into partially supplied arrays. To merge defaults into a partial array, you must explicitly use mergeDefaults().
  10. How AnyOf variants are selected

    master

    The AnyOf element attempts to find a matching variant from a list of options.

    • Selection Logic: It tries variants in the order they were declared. The first variant that matches both layers (using a 'throwaway' validation context) wins.
    • Side Effects: Because matching happens in a throwaway context, any side effects or transformations performed by 'losing' variants are discarded.
    • Error Reporting: If no variant matches, an aggregated error is produced (e.g., "expects to be A|B|C").
  11. Understand deferred validation of DynamicParameter

    master

    When a schema contains DynamicParameter (often used in Nette DI), the actual type validation is deferred.

    During the complete() phase, the schema does not validate the real type of the dynamic parameter. Instead, it records the value, the expected type, and the path in the Context::dynamics collection. The actual validation is performed later by the consumer (like Nette DI) once the runtime parameters are known.