Pular para o conteúdo principal

Changelog - Version 7.0

Status: in development. This document tracks changes landing on the 7.0 branch. Nothing here is released yet, and the contents may still change.

Overview​

Version 7.0 starts as a performance-focused release. ObjectCopy gains a direct copy path for its most common usage, and the property handlers were simplified. Backed enums are now supported, and are serialized as their value (the one breaking change). The PHP requirement is now >=8.3 <8.7 (see Requirements below).

New Features​

Backed enums​

A property typed with a backed enum (public ?Status $status) works in both directions:

  • Serialize writes a backed enum as its ->value, as json_encode() does, at every level, so also with withStopAtFirstLevel(). It used to become {"name": ..., "value": ...}, or stay an enum object at the first level (which byjg/micro-orm passes to the database).
  • ObjectCopy builds the enum from an int or string with from() when the target property, or its setter's parameter, is typed with the enum (Status or ?Status). Writing 'PAID' into that property used to be a TypeError. An unknown value throws a ValueError; a type that also accepts a scalar (string|Status) receives the value as it is.

A pure enum is unchanged. See docs/serialize.md and docs/objectcopy.md.

Performance​

ObjectCopy direct copy path​

ObjectCopy::copy() previously routed every copy through the full Serialize pipeline. When the source is an array and no property handler is given, that pipeline does no work beyond iterating the array: withStopAtFirstLevel() makes every value pass through untouched, no property filters are configured, and null values are copied by default.

That case now uses direct iteration instead. The resulting assignments are identical — the behaviour is pinned by ObjectCopyTest::testFastPathMatchesPipeline(), which runs the same inputs through both paths and asserts the targets match.

Copies made with a property handler, or from an object source, are unaffected and still use the pipeline.

Cached property-name resolution​

The case-insensitive property lookup used when a target has no setter and no exactly matching property is now cached statically, keyed by class name, rather than rebuilt on every call to copy(). Class properties do not change at runtime, so the cache is both correct and bounded by the number of classes copied into.

Measured impact​

200,000 copies of a five-property model, PHP 8.5, no opcache JIT, median of three runs:

Scenario6.07.0
Array source, no property handler3.67 s1.26 s~2.9x faster
Array source, with property handler5.09 s4.90 s~4% faster

The second row only benefits from the cached property-name resolution; it still goes through the pipeline, since a property handler may map or transform every name and value.

Internal Changes​

  • ObjectCopy::applyAttribute() no longer threads a property-name cache through its arguments; target assignment moved into a dedicated applyToTarget() helper shared by both copy paths.
  • CamelToSnakeCase and SnakeToCamelCase use arrow functions for their preg_replace_callback bodies, dropped an unused use Closure; import, and had trailing whitespace cleaned up.

These are all private/internal to final class ObjectCopy, so they are not part of the public API.

Bug Fixes​

  • Serialize::withMethodPattern() now rejects a search pattern shorter than 2 characters, as its message ("cannot be empty or less than 2 characters") already said. The check used && (empty($search) && strlen($search) <= 2), so only '' and '0' were rejected and a one-character pattern such as '/' was stored, failing later in preg_replace() with "No ending delimiter". A 2-character pattern ('//') is still accepted.
  • ObjectCopy::copy() now copies a backed enum the same way on every path. An array source without a property handler (the fast path) copied the enum object as it was, while every other path (an object source, or any source with a handler) goes through Serialize and copied its ->value. A target typed with the enum got the same result either way, but a string|Enum property, a stdClass or an array target got the enum object from one path and its value from the other. The fast path now copies the value too.

Breaking Changes​

A backed enum is serialized as its value​

Serialize::from($model)->toArray() (and toJson(), toYaml(), ...) gave a backed enum as ['name' => 'Paid', 'value' => 'PAID']; it now gives 'PAID'. With withStopAtFirstLevel() it gave the enum object; it now gives 'PAID'. Code that read ['value'] from the output reads the value directly.

ObjectCopy copies a backed enum from an array as its value​

ObjectCopy::copy(['status' => Status::Paid], $target) (array source, no property handler) put the enum object into a stdClass or array target, or into a property that also takes a scalar (string|Status); it now puts 'PAID', as an object source or a property handler already did. A property or setter typed with the enum (Status, ?Status) still gets Status::Paid.

Path to Upgrade from 6.x to 7.x​

If you read a backed enum from Serialize output as ['value'] (or as the enum object at the first level), read the value directly. If you copy an array holding a backed enum into a stdClass, an array or a string|Enum property and expect the enum object, call Status::from() on the copied value. Otherwise no action is required. Existing code using ObjectCopy, ObjectCopyTrait, BaseModel, Serialize, and the property handlers continues to work unchanged.

For the 6.x migration guide, see CHANGELOG-6.0.md.

Requirements​

  • PHP 8.3, 8.4, 8.5 and 8.6 are now supported: "php": ">=8.3 <8.7". The previous <8.6 upper bound excluded PHP 8.6, since <8.6 is exclusive.

Toolchain​

  • PHPUnit updated to ^12.5.

  • Psalm is installed as psalm/phar (^6.16) instead of vimeo/psalm.

    vimeo/psalm lists the PHP versions it supports and no published release includes 8.6, so as a dev dependency it made composer install fail on the 8.6 build job before any test ran. psalm/phar requires only php ^8.2 and bundles its own dependencies, so it installs on every PHP version in the matrix and cannot conflict with the project's. Psalm itself still refuses to run on 8.6, which is why the Psalm job uses 8.5. composer psalm runs it.

    PHPUnit 13 is deliberately not used. It requires PHP >=8.4.1, which would break the 8.3 floor.

Continuous Integration​

  • The build matrix now includes PHP 8.6.
  • The Psalm job now runs on PHP 8.5. Psalm declares ~8.1.31 || ~8.2.27 || ~8.3.16 || ~8.4.3 || ~8.5.0 and therefore does not run on PHP 8.6.

Housekeeping​

  • phpunit.xml.dist renamed to phpunit.xml.