The `MethodObjectBuilder` was incorrectly used when a registered
constructor is a static anonymous functions — it was handled like a
static method closure `Class::method(...)` and would yield errors like
this:
```
Error: Call to undefined method
stdClass::CuyZ\Valinor\Tests\Integration\Mapping\{closure}()
```
PHP Reflection does not provide any way of telling static functions and
closures of static methods apart, other than checking for the name
`{closure}`. We check that `{closure}` is actually the last part of the
fully-qualified name, instead of just checking that the string ends with
`{closure}`.
It is now possible to register a static method constructor that can be
inherited by a child class. The constructor will then be used correctly
to map the child class.
```php
abstract class ClassWithStaticConstructor
{
public string $value;
final private function __construct(string $value)
{
$this->value = $value;
}
public static function from(string $value): static
{
return new static($value);
}
}
final class ChildClass extends ClassWithStaticConstructor {}
(new MapperBuilder())
// The constructor can be used for every child of the parent class
->registerConstructor(ClassWithStaticConstructor::from(...))
->mapper()
->map(ChildClass::class, 'foo');
```
Handles race condition when the attribute is affected to a property or
parameter that was promoted, in this case the attribute will be applied
to both `ParameterReflection` and `PropertyReflection`, but the target
argument inside the attribute class is configured to support only one of
them (parameter or property).
More details: https://wiki.php.net/rfc/constructor_promotion#attributes
When the application runs in a development environment, the cache
implementation should be decorated with `FileWatchingCache` to prevent
invalid cache entries states, which can result in the library not
behaving as expected (missing property value, callable with outdated
signature, …).
```php
$cache = new \CuyZ\Valinor\Cache\FileSystemCache('path/to/cache-dir');
if ($isApplicationInDevelopmentEnvironment) {
$cache = new \CuyZ\Valinor\Cache\FileWatchingCache($cache);
}
(new \CuyZ\Valinor\MapperBuilder())
->withCache($cache)
->mapper()
->map(SomeClass::class, [/* … */]);
```
This behavior now forces to explicitly inject `FileWatchingCache`, when
it was done automatically before; but because it shouldn't be used in
a production environment, it will increase overall performance.
Using variadic parameters is now handled properly by the library,
meaning the following example will run:
```php
final class SomeClass
{
/** @var string[] */
private array $values;
public function __construct(string ...$values)
{
$this->values = $values;
}
}
(new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(SomeClass::class, ['foo', 'bar', 'baz']);
```
This abstraction layer was not useful, so it is removed to simplify the
API around `ClassDefinition`.
A new method `ClassDefinition::type()` is also added, giving access to
the `ClassType` instance when working with a class definition.
Integer range can be used as follows:
```php
final class SomeClass
{
/** @var int<42, 1337> */
public int $intRange; // accepts any int between 42 and 1337
/** @var int<-1337, 1337> */
public int $negativeIntRange; // also works with negative values
/** @var int<min, 1337> */
public int $minIntRange; // `min` can be used…
/** @var int<0, max> */
public int $maxIntRange; // …as well as `max`
}
```
Note that `min` and `max` will check the range with PHP's internal
constants `PHP_INT_MIN` and `PHP_INT_MAX`.