The return value of `uniqid()` is not very random and easily guessable. While
this likely does not introduce any issues in CompiledPhpFileCache, the fact
that `uniqid()` is often mis-used in locations where actual randomness is a
hard requirement, makes this a red flag.
Replace the use of `uniqid()` to generate a random filename by hexadecimal
encoded `random_bytes(16)`, giving 128 Bits of randomness, making the value
unguessable in practice and preventing the `CompiledPhpFileCache` from showing
up when searching for `uniqid()`.
The `TokenParser` is imported from the `doctrine/annotations` package in
order to reduce the coupling to this library, which will lead to its
removal from the dependencies in an upcoming version.
This notation is mainly useful when several cases in constants of a
class share a common prefix.
```php
final class SomeClassWithConstants
{
public const FOO = 1337;
public const BAR = 'bar';
public const BAZ = 'baz';
}
$mapper = (new MapperBuilder())->mapper();
$mapper->map('SomeClassWithConstants::BA*', 1337); // error
$mapper->map('SomeClassWithConstants::BA*', 'bar'); // ok
$mapper->map('SomeClassWithConstants::BA*', 'baz'); // ok
```
This notation can be used when several cases in an enum share a common
prefix.
```php
enum SomeEnum: string
{
case FOO = 'foo';
case BAR = 'bar';
case BAZ = 'baz';
}
$mapper = (new MapperBuilder())->mapper();
$mapper->map('SomeEnum::BA*', 'foo'); // error
$mapper->map('SomeEnum::BA*', 'bar'); // ok
$mapper->map('SomeEnum::BA*', 'baz'); // ok
```
The new class `\CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder` can be
used to easily create an instance of (error) message.
This new straightforward way of creating messages leads to the
depreciation of `\CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage`.
```php
$message = MessageBuilder::newError('Some message / {some_parameter}.')
->withCode('some_code')
->withParameter('some_parameter', 'some_value')
->build();
```
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}`.
Fixes the following message reported by `symfony/error-handler`:
`User Deprecated: Method "Psr\SimpleCache\CacheInterface::get()" might
add "mixed" as a native return type declaration in the future. Do the
same in implementation "CuyZ\Valinor\Cache\..." now to avoid errors or
add an explicit @return annotation to suppress this message.`
Method `\CuyZ\Valinor\Cache\FileSystemCache::get()` was not properly
looping on all delegates, leading to the values not being fetched from
the cache files and resulting in `null` (the default value) being
returned in some cases. Because of the following algorithm, the cache
entry was populated again, so the cache was not really working here.
```php
if ($this->cache->has($key)) {
$entry = $this->cache->get($key);
if ($entry) {
return $entry;
}
}
$class = $this->delegate->for($type);
$this->cache->set($key, $class);
return $class;
```
```php
(new \CuyZ\Valinor\MapperBuilder())
// Both `Cookie` and `ATOM` formats will be accepted
->supportDateFormats(DATE_COOKIE, DATE_ATOM)
->mapper()
->map(DateTimeInterface::class, 'Monday, 08-Nov-1971 13:37:42 UTC');
```
A new constructor can be registered to declare which format(s) are
supported during the mapping of a date object. By default, any valid
timestamp or ATOM-formatted value will be accepted.
```php
(new \CuyZ\Valinor\MapperBuilder())
// Both COOKIE and ATOM formats will be accepted
->registerConstructor(
new \CuyZ\Valinor\Mapper\Object\DateTimeFormatConstructor(DATE_COOKIE, DATE_ATOM)
)
->mapper()
->map(DateTimeInterface::class, 'Monday, 08-Nov-1971 13:37:42 UTC');
```
The previously very opinionated behaviour has been removed, but can be
temporarily used to help with the migration.
```php
(new \CuyZ\Valinor\MapperBuilder())
->registerConstructor(
new \CuyZ\Valinor\Mapper\Object\BackwardCompatibilityDateTimeConstructor()
)
->mapper()
->map(DateTimeInterface::class, 'Monday, 08-Nov-1971 13:37:42 UTC');
```
In some situations the type handled by a constructor is only known at
runtime, in which case the constructor needs to know what class must be
used to instantiate the object.
For instance, an interface may declare a static constructor that is then
implemented by several child classes. One solution would be to register
the constructor for each child class, which leads to a lot of
boilerplate code and would require a new registration each time a new
child is created. Another way is to use the attribute
`\CuyZ\Valinor\Mapper\Object\DynamicConstructor`.
When a constructor uses this attribute, its first parameter must be a
string and will be filled with the name of the actual class that the
mapper needs to build when the constructor is called. Other arguments
may be added and will be mapped normally, depending on the source given
to the mapper.
```php
interface InterfaceWithStaticConstructor
{
public static function from(string $value): self;
}
final class ClassWithInheritedStaticConstructor implements InterfaceWithStaticConstructor
{
private function __construct(private SomeValueObject $value) {}
public static function from(string $value): self
{
return new self(new SomeValueObject($value));
}
}
(new \CuyZ\Valinor\MapperBuilder())
->registerConstructor(
#[\CuyZ\Valinor\Attribute\DynamicConstructor]
function (string $className, string $value): InterfaceWithStaticConstructor {
return $className::from($value);
}
)
->mapper()
->map(ClassWithInheritedStaticConstructor::class, 'foo');
```
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');
```
Using two instances of the same class implementing the `__invoke()`
method in one of the mapper builder methods will now be properly handled
by the library