cdekmarketteam / moysklad

Other
14 stars 19 forks source link

Проблема десериализации #42

Open evgeek opened 2 years ago

evgeek commented 2 years ago

Добрый день. Наблюдаются проблемы с пакетом jms/serializer, а именно исключение JMS\Serializer\Exception\NonStringCastableTypeException: Cannot convert value of type "array" to string. При этом, ответ от Моего Склада приходит вполне себе корректный.
Есть какие-то идеи, что с этим можно сделать?

Stacktrace:

#0 /var/www/html/vendor/jms/serializer/src/JsonDeserializationVisitor.php(58): JMS\Serializer\AbstractVisitor->assertValueCanBeCastToString(Array)
#1 /var/www/html/vendor/jms/serializer/src/GraphNavigator/DeserializationGraphNavigator.php(123): JMS\Serializer\JsonDeserializationVisitor->visitString(Array, Array)
#2 /var/www/html/vendor/jms/serializer/src/JsonDeserializationVisitor.php(188): JMS\Serializer\GraphNavigator\DeserializationGraphNavigator->accept(Array, Array)
#3 /var/www/html/vendor/jms/serializer/src/GraphNavigator/DeserializationGraphNavigator.php(215): JMS\Serializer\JsonDeserializationVisitor->visitProperty(Object(JMS\Serializer\Metadata\PropertyMetadata), Array)
#4 /var/www/html/vendor/jms/serializer/src/JsonDeserializationVisitor.php(112): JMS\Serializer\GraphNavigator\DeserializationGraphNavigator->accept(Array, Array)
#5 /var/www/html/vendor/jms/serializer/src/GraphNavigator/DeserializationGraphNavigator.php(141): JMS\Serializer\JsonDeserializationVisitor->visitArray(Array, Array)
#6 /var/www/html/vendor/jms/serializer/src/JsonDeserializationVisitor.php(188): JMS\Serializer\GraphNavigator\DeserializationGraphNavigator->accept(Array, Array)
#7 /var/www/html/vendor/jms/serializer/src/GraphNavigator/DeserializationGraphNavigator.php(215): JMS\Serializer\JsonDeserializationVisitor->visitProperty(Object(JMS\Serializer\Metadata\PropertyMetadata), Array)
#8 /var/www/html/vendor/jms/serializer/src/Serializer.php(252): JMS\Serializer\GraphNavigator\DeserializationGraphNavigator->accept(Array, Array)
#9 /var/www/html/vendor/jms/serializer/src/Serializer.php(180): JMS\Serializer\Serializer->visit(Object(JMS\Serializer\GraphNavigator\DeserializationGraphNavigator), Object(JMS\Serializer\JsonDeserializationVisitor), Object(JMS\Serializer\DeserializationContext), Array, 'json', Array)
#10 /var/www/html/vendor/cdekmarketteam/moysklad/src/Http/RequestExecutor.php(268): JMS\Serializer\Serializer->deserialize('{\n  "meta" : {\n...', 'MoySklad\\Entity...', 'json')
#11 /var/www/html/vendor/cdekmarketteam/moysklad/src/Client/Endpoint/GetEntityEndpoint.php(27): MoySklad\Http\RequestExecutor->get('MoySklad\\Entity...')
#12 /var/www/html/app/Tasks/MoyskladGetBalances.php(21): MoySklad\Client\ProductClient->get('03110e7a-6252-1...')
#13 /var/www/html/vendor/evgeek/scheduler/src/Task/TaskFromJob.php(34): App\Tasks\MoyskladGetBalances->dispatch()
#14 /var/www/html/vendor/evgeek/scheduler/src/Wrapper/TaskWrapper.php(208): Evgeek\Scheduler\Task\TaskFromJob->dispatch()
#15 /var/www/html/vendor/evgeek/scheduler/src/Wrapper/TaskWrapper.php(114): Evgeek\Scheduler\Wrapper\TaskWrapper->launchTask(Object(Carbon\Carbon))
#16 /var/www/html/vendor/evgeek/scheduler/src/Scheduler.php(61): Evgeek\Scheduler\Wrapper\TaskWrapper->dispatch()
#17 /var/www/html/start.php(28): Evgeek\Scheduler\Scheduler->run()
#18 {main}

image

evgeek commented 2 years ago

Ломается, судя по всему, в JMS\Serializer\JsonDeserializationVisitor::visitString, на массиве meta

image

artemvyshnevskiy commented 2 years ago

Проблема в поле $value класса MoySklad\Entity\Attribute. Оно помечено как string, но МойСклад в нем может присылать в т.ч. и массив в случае, если в МоемСкладе используется дополнительное поле с типом "справочник".

В качестве решения можно зарегистрировать собственный тип mixed. Для этого нужно создать обработчик сериализации

namespace MoySklad\Util\Serializer;

class MixedSerializeHandler
{
    /**
     * @param $visitor
     * @param mixed $value
     * @param array $type
     * @return array|mixed
     */
    public function __invoke($visitor, $value, array $type)
    {
        return $value;
    }
}

и десериализации

namespace MoySklad\Util\Serializer;

use JMS\Serializer\Serializer;

class MixedDeserializeHandler
{
    /**
     * @var Serializer
     */
    private $serializer;

    /**
     * @param $visitor
     * @param mixed $value
     * @param array $type
     * @return array|mixed
     */
    public function __invoke($visitor, $value, array $type)
    {
        $this->serializer = SerializerInstance::getInstance();

        return $this->serializer->deserialize(json_encode($value), gettype($value), 'json');
    }
}

зарегистрировать новые обработчики в классе MoySklad\Util\Serializer\SerializerInstance

public static function getInstance(): Serializer
{
    if (is_null(self::$instance)) {
        self::$instance = SerializerBuilder::create()
            ->setPropertyNamingStrategy(
                new SerializedNameAnnotationStrategy(
                    new IdenticalPropertyNamingStrategy()
                )
            )
            ->configureHandlers(
                function (HandlerRegistry $registry) {
                    $registry->registerHandler(
                        self::DIRECTION['deserialization'],
                        MetaEntity::class,
                        'json',
                        new MetaEntityDeserializeHandler()
                    );
                    $registry->registerHandler(
                        self::DIRECTION['deserialization'],
                        Barcode::class,
                        'json',
                        new BarcodeDeserializeHandler()
                    );
                    $registry->registerHandler(
                        self::DIRECTION['deserialization'],
                        'mixed',
                        'json',
                        new MixedDeserializeHandler()
                    );
                    $registry->registerHandler(
                        self::DIRECTION['serialization'],
                        'mixed',
                        'json',
                        new MixedSerializeHandler()
                    );
                }
            )
            ->addDefaultHandlers()
            ->build();
    }

    return self::$instance;
}

и поменять тип у поля value в классе MoySklad\Entity\Attribute

/**
 * @Type("mixed")
 */
public $value;

Буду очень признателен, если кто-нибудь оформит это в качестве pull request или предложит свое, более элегантное решение.