Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Serializer][RFC] Context object as an alternative to array builders #45330

Closed as not planned
@vudaltsov

Description

@vudaltsov

Description

I have an alternative proposal for Serializer context (replaces #43973 for #30818). It requires new contracts for serializer and normalizer, but my approach allows to combine different structured contexts easily without array conversion. Both client and library will work with objects instead of arrays, so offset constants and array processing could be dropped.

Criterion Builders (#43973) Context object (this)
Reuses existing contracts
Always deals with structured data
Code maintainability / Simplicity of adding a new normalizer
Offset constants in the normalizer class, defaults processing, builders

Only ContextItem implementation
IDE/Autocomplete support 🟨
Partial, since arrays are not structured

The idea is to have a Context class that is a collection of individual context objects, indexed by class. Each context piece must provide a default factory method that is called in case the context item was not explicitly passed. Individual context items cannot be mutated or replaced once they are instantiated within a serializer/normalizer call to match the current array behavior, only a new Context instance can be created via constructor or a with method. The rest is easier to explain with the code:

/**
 * @psalm-immutable
 */
interface ContextItem
{
    public static function default(): static;
}

/**
 * @psalm-immutable
 */
final class Context
{
    /**
     * @var array<class-string<ContextItem>, ContextItem>
     */
    private array $items = [];

    public function __construct(
        ContextItem ...$items,
    ) {
        foreach ($items as $item) {
            $this->items[$item::class] = $item;
        }
    }

    /**
     * @template T of ContextItem
     * @param class-string<T> $class
     * @return T
     */
    public function get(string $class): ContextItem
    {
        /** @var T */
        return $this->items[$class] ?? $class::default();
    }

    public function with(ContextItem $item): self
    {
        $context = clone $this;
        $context->items[$item::class] = $item;

        return $context;
    }
}

interface NewNormalizerInterface
{
    public function normalize(mixed $data, string $format = null, Context $context = new Context()): mixed;

    public function supportsNormalization(mixed $data, string $format = null, Context $context = new Context()): bool;
}

final class DateTimeNormalizerContext implements ContextItem
{
    public function __construct(
        public string $format = 'Y-m-d',
    ) {
    }

    public static function default(): static
    {
        return new self();
    }
}

final class DateTimeNormalizer implements NewNormalizerInterface
{
    public function normalize(mixed $data, string $format = null, Context $context = new Context()): mixed
    {
        $format = $context->get(DateTimeNormalizerContext::class)->format;

        // ...
    }

    public function supportsNormalization(mixed $data, string $format = null, Context $context = new Context()): bool
    {
        // ...
    }
}

$serializer->serialize(/**  */, new Context(new DateTimeNormalizerContext('Y-m-d H:i')));

// or

$context = new Context();
$serializer->serialize(/**  */, $context->with(new DateTimeNormalizerContext('Y-m-d H:i'));

NewNormalizerInterface is not a name proposal :) I called it so for simplicity.

BC layer ideas

We can add a new interface that supports both array and Context parameter types, using the union operator, and meanwhile merge ContextAwareNormalizerInterface to reduce the number of contracts in the future.

final class Context
{
}

/**
 * @deprecated use NewNormalizerInterface instead
 */
interface NormalizerInterface
{
    public function normalize(mixed $object, string $format = null, array $context = []);

    public function supportsNormalization(mixed $data, string $format = null);
}

/**
 * @deprecated use NewNormalizerInterface instead
 */
interface ContextAwareNormalizerInterface extends NormalizerInterface
{
    public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool;
}

interface NewNormalizerInterface extends ContextAwareNormalizerInterface
{
    public function normalize(mixed $data, string $format = null, array|Context $context = []): mixed;

    public function supportsNormalization(mixed $data, string $format = null, array|Context $context = []): bool;
}

final class DateTimeNormalizer implements NewNormalizerInterface
{
    public function normalize(mixed $object, string $format = null, array|Context $context = []): mixed
    {
        // ...
    }

    public function supportsNormalization(mixed $data, string $format = null, array|Context $context = []): bool
    {
        // ...
    }
}

new DateTimeNormalizer();

It works, see https://3v4l.org/YmEbP.

Also a helper method Context::fromArray() can be introduced to simplify deprecated array context support.

IDE support

PhpStorm supports Context::get() autocompletion.

image

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions