B
B
BonBon Slick2021-05-16 23:38:48
symfony
BonBon Slick, 2021-05-16 23:38:48

For what reasons can the serializer not find the mapping?

"symfony/serializer": "^5.2",

serializer:
    enabled: true
    enable_annotations: true // пробовал и через аннотации, так же пустой массив
    mapping:
      paths: [ '%kernel.project_dir%/src/Infrastructure/Serializer/' ]


I'm stupid, I did something somewhere and now the serializer does not find the mappings.

<?xml version="1.0" encoding="UTF-8" ?>
<serializer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns="http://symfony.com/schema/dic/serializer-mapping"
            xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping
            https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
>
    <class name="App\Domain\AuthToken">
это поле лежит в абстрактном классе родителе 4 уровня выше
        <attribute name="id">
            <group>all</group>
        </attribute>
  это поле принадлежит AuthToken
        <attribute name="isDeviceRemembered">
            <group>all</group>
            <group>test</group>
        </attribute>
это поле лежит в абстрактном классе родителе 1 уровень выше
        <attribute name="user">
            <group>all</group>
            <group>test</group>
        </attribute>
    </class>
</serializer>


The path where the mapping file lies, tried everything

Infrastructure/Serializer/AuthToken.xml
Infrastructure/Serializer/Domain/AuthToken.xml

But when you try to get a mapping in the AbstractObjectNormalizer
, all fields will always be empty because the
attributes are empty
$attributes = $this->extractAttributes($object, $format, $context); //  === []

The ClassDiscriminatorFromClassMetadata will be null everywhere

in Kernel.php
/**
     * @return Generator|iterable|BundleInterface[]
     */
    public function registerBundles() {
        $contents = require $this->getProjectDir() . '/config/bundles.php';
        foreach ($contents as $class => $envs) {
            if (isset($envs['all']) || isset($envs[$this->environment])) {
                yield new $class();
            }
        }
    }


bundles.php
<?php


return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class            => ['all' => true],
    Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class             => ['all' => true],
    Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
    Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class     => ['dev' => true, 'test' => true],
    Symfony\Bundle\MonologBundle\MonologBundle::class                => ['all' => true],
    Nelmio\CorsBundle\NelmioCorsBundle::class                        => ['all' => true],
    Nelmio\ApiDocBundle\NelmioApiDocBundle::class                    => ['all' => true],
    Symfony\Bundle\SecurityBundle\SecurityBundle::class              => ['all' => true],
    Snc\RedisBundle\SncRedisBundle::class                            => ['all' => true],
    DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class            => ['test' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class                      => ['all' => true],
    Exercise\HTMLPurifierBundle\ExerciseHTMLPurifierBundle::class    => ['all' => true],
];


Symfony\Component\Serializer\Mapping\ClassMetadata {#21134
  +name: "App\Domain\AuthToken"
  +attributesMetadata: array:25 [
    "isDeviceRemembered" => Symfony\Component\Serializer\Mapping\AttributeMetadata {#26006
      +name: "isDeviceRemembered"
      +groups: []
      +maxDepth: null
      +serializedName: null
      +ignore: false
    }


use Symfony\Component\Serializer\SerializerInterface;
...
    protected SerializerInterface $serializer;
....
 $this->serializer->serialize($data, 'json', ['groups' => ['all', 'test']]),  // === []


namespace App\Domain\Entity;

use App\Domain\ValueObject\TokenIsDeviceRemembered;
use App\Domain\AbstractUserToken;
use Symfony\Component\Serializer\Annotation\Groups;


class AuthToken extends AbstractUserToken implements UserInterface {
    /**
     * @Groups({"all", "test"})
     */
    public TokenIsDeviceRemembered                                                                  $isDeviceRemembered;

final class TokenIsDeviceRemembered extends AbstractVO {
    /**
     * @Groups({"all", "test"})
     */
    public bool $isDeviceRemembered;

abstract class AbstractVO implements IValueObject, JsonSerializable {


As you can see from the code example, the path to the serializer files is set correctly, I also tried other paths and file names there
Infrastructure/Serializer/App/Domain/AuthToken.xml
.... // аналогичное с точкой вместо слеша /
Infrastructure/Serializer/App.Domain.AuthToken.xml
Infrastructure/Serializer/App.Domain.AuthToken.Serializer.xml
Infrastructure/Serializer/App.Domain.AuthToken.Serializer.orm.xml
Infrastructure/Serializer/Domain.AuthToken.Serializer.xml
Infrastructure/Serializer/Domain.AuthToken.Serializer.orm.xml

Answer the question

In order to leave comments, you need to log in

1 answer(s)
B
BonBon Slick, 2021-05-17
@BonBonSlick

1 - after each update of the mapping, you must manually demolish the cache folder. Perhaps there are commands that can update the cache

composer dump-autoload; php bin/console cache:clear
didn't work.
Will return something like
array:1 [
  0 => "all" // контекст
]
$serializedData"=== []

It is necessary to demolish the cache after changes in the mapping of both the doctrine and the serializer. Which means, when changing the contract, new parameters of the entity, you need to clear the cache. If we want to display something on the client tpa
// Serializer/Token.xml
        <attribute name="isValid">
            <group>all</group>
            <group>non_sensitive</group>
        </attribute>
// Token.php
    public function isValid(): bool {
        return false === $this->isExpired();
    }

You need to clear the cache.
2 - if VO is declared in the mapping like this
<attribute name="isDeviceRemembered">
            <group>all</group>
            <group>non_sensitive</group>
        </attribute>

and the method of the entity which mapim returns an object
public function isDeviceRemembered(): TokenIsDeviceRemembered {
        return $this->isDeviceRemembered;
    }

At the output, we get an empty array. the serializer does not know how to parse the object.
There are 2 best options
1 -
use JsonSerializable;

final class TokenIsDeviceRemembered implements IValueObject, JsonSerializable {

    /**
     * @throws JsonException
     */
  /**
     * @throws JsonException
     */
    final public function jsonSerialize(): string {
        if (true === is_object($this->value())) {
            return json_encode($this->value(), JSON_THROW_ON_ERROR);
        }
        if (true === is_bool($this->value())) {
            return $this->value() ? 'true' : 'false';
        }
        return $this->value();
    }

then parsing will be handled in the jsonSerialize method,
specify nested fields by type
<attribute name="isDeviceRemembered.isDeviceRemembered">
            <group>all</group>
            <group>non_sensitive</group>
        </attribute>

when a private field fails, then it will generally return an empty array of all data that can be serialized
public bool $isDeviceRemembered;
to open the field, which can cause other errors when working with VO, such as updating the field.
Therefore, we make the VO field private and add a separate method
public function isDeviceRemembered(): bool {
        return $this->isDeviceRemembered;
    }

if we want to avoid using the JsonSerializable interface
or use the jsonSerialize method with the interface, this approach is more convenient for me personally. Potmou that is more versatile.
Mapping is always specified like this
<attribute name="isDeviceRemembered">
            <group>all</group>
            <group>non_sensitive</group>
        </attribute>

and no investment
<attribute name="isDeviceRemembered.isDeviceRemembered">

Metada is always read from the cache, always. The only option during the initialization of the cache container may not yet be there and other metadata readers are used there. CacheClassMetadataFactory, ClassMetadataFactoryInterface.
You can also specify an additional serializer mapping for VO with a high degree of stubbornness.
An additional reason why it can produce an empty array is that annotations are not enabled, then by default the serializer group is an empty array.
And if the actual mapping file for the serializer is not found.
The file name can be specified as User.xml(.yml, .yaml...) without User.Serializer.orm.xml
These are the best options, it is also worth mentioning that you can combine normalizers, in my case I used ObjectNormilizer for AggregateRoot Entity and JsonNormilizer for VO. Declare annotations and getters through methods, that is, hang annotations on methods and then the object will be serialized by methods.
Also, you can remove the serializer altogether and shove data directly into JsonResponse, by default only public parameters will get into the response, but this is a security risk.
I ruined 5-6 hours, if it gave errors or there was a debugger, it would be simpler., maybe 2-3 hours faster. And so it returned an empty array for at least 2 reasons, cache, incorrect mapping for VO. There were other cases that were confusing and trace.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question