更新日志 2.6.0 — 2026 年 8 月 11 日¶
重要变更¶
此版本为库带来了一组新功能:
享受! 🎉
提供映射器配置器¶
映射器现在可以使用一组开箱即用的配置器,镜像以前版本中引入的规范化器配置器。每一个都可以通过configureWith()方法全局使用,也可以作为用于特定属性的特性在本地使用。
MapToDateTimeFromFormat配置器使用给定的日期格式解析输入字符串,该格式必须遵循DateTimeImmutable::createFromFormat()支持的语法:
use CuyZ\Valinor\Mapper\Configurator\MapToDateTimeFromFormat;
use CuyZ\Valinor\MapperBuilder;
use DateTimeInterface;
final readonly class Event
{
public function __construct(
public string $name,
#[MapToDateTimeFromFormat('d/m/Y')]
public DateTimeInterface $date,
) {}
}
$event = (new MapperBuilder())
->mapper()
->map(Event::class, [
'name' => 'Release of legendary album',
'date' => '08/11/1971', // mapped to a `DateTimeImmutable`
]);
MapExplodedStringToList配置器使用给定的分隔符将字符串分解为列表,当输入将列表作为单个分隔字符串(例如来自 CSV 文件或查询参数的值)时,这非常有用:
use CuyZ\Valinor\Mapper\Configurator\MapExplodedStringToList;
use CuyZ\Valinor\MapperBuilder;
final readonly class Product
{
public function __construct(
public string $name,
/** @var list<string> */
#[MapExplodedStringToList(separator: ',')]
public array $sizes,
) {}
}
$product = (new MapperBuilder())
->mapper()
->map(Product::class, [
'name' => 'T-Shirt',
'sizes' => 'XS,S,M,L,XL', // mapped to `['XS', 'S', 'M', 'L', 'XL']`
]);
MapArrayToList配置器会丢弃数组的键并将其值映射到列表,对于输入是关联数组或缺少或无序索引的稀疏列表的情况,应将其作为顺序列表处理:
use CuyZ\Valinor\Mapper\Configurator\MapArrayToList;
use CuyZ\Valinor\MapperBuilder;
final readonly class Basket
{
public function __construct(
/** @var list<string> */
#[MapArrayToList]
public array $products,
) {}
}
$basket = (new MapperBuilder())
->mapper()
->map(Basket::class, [
'a' => 'Coffee',
'b' => 'Tea',
]); // mapped to `['Coffee', 'Tea']`
最后, MapFromJson配置器解码 JSON 字符串并将结果交给映射器,以便通常的验证和错误报告仍然适用于解码的值:
use CuyZ\Valinor\Mapper\Configurator\MapFromJson;
use CuyZ\Valinor\MapperBuilder;
final readonly class User
{
public function __construct(
public string $name,
/** @var list<string> */
#[MapFromJson]
public array $roles,
) {}
}
$user = (new MapperBuilder())
->mapper()
->map(User::class, [
'name' => 'John Doe',
'roles' => '["admin", "editor"]', // mapped to `['admin', 'editor']`
]);
标量值转换¶
四个配置器在映射之前将标量值转换为特定类型: MapAsBool 、 MapAsInt 、 MapAsFloat和MapAsString 。当输入数据携带与目标类型不同的表示形式的值时,它们非常有用,例如在表单提交、CSV 文件或 JSON 有效负载中编码为字符串的数字或布尔值。
用作特性时,会强制转换单个属性,而对于其他每个值,严格规则均保持不变:
use CuyZ\Valinor\Mapper\Configurator\MapAsBool;
use CuyZ\Valinor\Mapper\Configurator\MapAsInt;
use CuyZ\Valinor\MapperBuilder;
final readonly class User
{
public function __construct(
public string $name,
#[MapAsInt]
public int $age,
#[MapAsBool(true: ['on', 'yes'], false: ['off', 'no'])]
public bool $isActive,
) {}
}
$user = (new MapperBuilder())
->mapper()
->map(User::class, [
'name' => 'John Doe',
'age' => '42', // mapped to `42`
'isActive' => 'on', // mapped to `true`
]);
还可以使用映射器构建器的新的allowCastingToBoolean() 、 allowCastingToInteger() 、 allowCastingToFloat()和allowCastingToString()方法为给定类型的每个值启用转换。它们提供了比allowScalarValueCasting()更好的控制,它同时放宽了对所有标量类型的严格性:
use CuyZ\Valinor\MapperBuilder;
$age = (new MapperBuilder())
->allowCastingToInteger()
->mapper()
->map('int', '42'); // mapped to `42`
从特定键映射属性¶
新的MapFromKey属性从特定的源键提供类属性或构造函数/方法参数,而不是将其与属性名称进行匹配:
use CuyZ\Valinor\Mapper\Configurator\MapFromKey;
use CuyZ\Valinor\MapperBuilder;
final readonly class Person
{
public function __construct(
public string $name,
#[MapFromKey('zipCode')]
public string $postalCode,
) {}
}
$person = (new MapperBuilder())
->mapper()
->map(Person::class, [
'name' => 'John Doe',
'zipCode' => '75001', // mapped to `$postalCode`
]);
该属性建立在对用户空间开放的轻量级协议之上:任何声明mapKey(string $key): string方法并携带#[AsConverter]属性的属性类都可以重新映射其所在元素的键。这可以方便地分解出重复的转换,例如由多个属性共享的前缀:
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER)]
#[\CuyZ\Valinor\Mapper\AsConverter]
final class MapWithPrefix
{
public function __construct(private string $prefix) {}
public function mapKey(string $key): string
{
return $this->prefix . $key;
}
}
final readonly class Configuration
{
public function __construct(
#[MapWithPrefix('app_')] // reads from `app_host`
public string $host,
#[MapWithPrefix('app_')] // reads from `app_port`
public int $port,
) {}
}
新的规范化器配置器¶
三个配置器加入了先前版本中引入的配置器。
当数据格式中使用的名称与 PHP 代码库中使用的名称不同时, NormalizeKeyTo属性会在规范化期间重命名属性的键:
use CuyZ\Valinor\Normalizer\Configurator\NormalizeKeyTo;
use CuyZ\Valinor\Normalizer\Format;
use CuyZ\Valinor\NormalizerBuilder;
final readonly class Address
{
public function __construct(
public string $street,
#[NormalizeKeyTo('town')]
public string $city,
) {}
}
$addressAsArray = (new NormalizerBuilder())
->normalizer(Format::array())
->normalize(new Address('221B Baker Street', 'London'));
// [
// 'street' => '221B Baker Street',
// 'town' => 'London',
// ]
NormalizeToSingleValue类展平包含单个属性的对象,以便规范化结果不是['someProperty' => 'value']而是简单的'value' 。它可以用作配置器,应用于具有单个属性的每个对象,也可以用作用于特定类或属性的特性:
use CuyZ\Valinor\Normalizer\Configurator\NormalizeToSingleValue;
use CuyZ\Valinor\Normalizer\Format;
use CuyZ\Valinor\NormalizerBuilder;
final readonly class Email
{
public function __construct(
public string $email,
) {}
}
final readonly class User
{
public function __construct(
public string $name,
#[NormalizeToSingleValue]
public Email $email,
) {}
}
$userAsArray = (new NormalizerBuilder())
->normalizer(Format::array())
->normalize(new User('John Doe', new Email('john.doe@example.com')));
// [
// 'name' => 'John Doe',
// 'email' => 'john.doe@example.com',
// ]
IgnoreOnNormalization属性从规范化输出中排除某个属性,例如隐藏密码等敏感数据。为了使该属性生效,还必须通过configureWith()在构建器上注册此类的实例:
use CuyZ\Valinor\Normalizer\Configurator\IgnoreOnNormalization;
use CuyZ\Valinor\Normalizer\Format;
use CuyZ\Valinor\NormalizerBuilder;
final readonly class User
{
public function __construct(
public string $name,
#[IgnoreOnNormalization]
public string $password,
) {}
}
$userAsArray = (new NormalizerBuilder())
->configureWith(new IgnoreOnNormalization())
->normalizer(Format::array())
->normalize(new User('John Doe', 's3cr3t'));
// ['name' => 'John Doe']
PHP 内部类的泛型¶
泛型过去仅限于用户态类,因为 PHP 内部的类或扩展提供的类不能在其自己的源代码中声明@template注释。该库现在为各种类型提供通用签名,包括ArrayObject 、 ArrayIterator 、SPL 数据结构和Ds集合类,因此它们可以像任何其他类一样进行参数化:
use CuyZ\Valinor\MapperBuilder;
$sizes = (new MapperBuilder())
->mapper()
->map('ArrayObject<string, int>', [
'S' => 36,
'M' => 38,
'L' => 40,
]);
这些模板中的每一个都声明了一种默认类型,因此像ArrayObject这样的裸引用可以像以前一样继续解析。
模板的默认类型¶
@template注释现在可以使用=声明默认类型。引用类时可以省略声明默认类型的模板,在这种情况下使用默认类型:
/**
* @template TValue
* @template TMeta of array<string, mixed> = array<string, string>
*/
final readonly class Page
{
public function __construct(
/** @var list<TValue> */
public array $items,
/** @var TMeta */
public array $meta,
) {}
}
final readonly class SomeClass
{
public function __construct(
// `TMeta` is not filled in, its default type is used
/** @var Page<string> */
public Page $pageWithDefaultMeta,
// `TMeta` is filled in, overriding its default type
/** @var Page<string, array{cursor: int}> */
public Page $pageWithCursorMeta,
) {}
}
默认类型使得可以将模板添加到已在其他地方引用的类中:现有的引用不会填充新模板,继续解析为其默认类型,并且可以在以后变得更加精确。
覆盖不可解析的类型¶
当属性、参数或返回类型使用库尚无法解析的 PHPStan 或 Psalm 语法时,例如条件类型($a is 1 ? int : null) ,专用的@valinor-var 、 @valinor-param和@valinor-return注释可用于为库提供它理解的类型。它们优先于所有其他注释,因此静态分析工具在库使用覆盖时继续使用它们自己的类型:
final class SomeClass
{
/**
* @phpstan-param ($a is 1 ? int : null) $b
* @valinor-param int|null $b
*/
public function __construct(
public readonly int $a,
public readonly ?int $b,
) {}
}
新功能¶
- 添加
@valinor-*注释来覆盖不可解析的类型 ( 11938c ) - 添加对
@template注释的默认值支持( 0d6efe ) - 添加映射器构建器方法以转换为标量类型( cdca3f )
- 添加映射器配置器
MapArrayToList( 1f81fa ) - 添加映射器配置器
MapAsBool( 65dfed ) - 添加映射器配置器
MapAsFloat( 84eea9 ) - 添加映射器配置器
MapAsInt( ea28a7 ) - 添加映射器配置器
MapAsString( 6b0528 ) - 添加映射器配置器
MapExplodedStringToList( beb4db ) - 添加映射器配置器
MapFromJson( 469863 ) - 添加映射器配置器
MapToDateTimeFromFormat( d6e53b ) - 添加规范化器配置器
IgnoreOnNormalization( 9769f2 ) - 添加规范化器配置器
NormalizeKeyTo( 947127 ) - 添加规范化器配置器
NormalizeToSingleValue( 7c5f13 ) - 允许将源键与特性映射( 631f66 )
- 支持 PHP 内部类的泛型 ( 5419b4 )
错误修复¶
- 将构造函数声明的模板绑定到正在映射的类型( 0629d8 )