SerializedToJson.php 3.81 KB
Newer Older
Ketan's avatar
Ketan committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Magento\Framework\DB\DataConverter;

use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\Serialize\Serializer\Serialize;

/**
 * Convert from serialized to JSON format
 */
class SerializedToJson implements DataConverterInterface
{
    /**
     * @var Serialize
     */
    private $serialize;

    /**
     * @var Json
     */
    private $json;

    /**
     * Constructor
     *
     * @param Serialize $serialize
     * @param Json $json
     */
    public function __construct(
        Serialize $serialize,
        Json $json
    ) {
        $this->serialize = $serialize;
        $this->json = $json;
    }

    /**
     * Convert from serialized to JSON format
     *
     * @param string $value
     * @return string
     * @throws DataConversionException
     */
    public function convert($value)
    {
        if ($this->isValidJsonValue($value)) {
            return $value;
        }
        return $this->encodeJson($this->unserializeValue($value));
    }

    /**
     * Is a valid JSON serialized value
     *
     * @param string $value
     * @return bool
     */
    protected function isValidJsonValue($value)
    {
        if (in_array($value, ['null', 'false', '0', '""', '[]'])
            || (json_decode($value) !== null && json_last_error() === JSON_ERROR_NONE)
        ) {
            return true;
        }
        //JSON last error reset
        json_encode([]);
        return false;
    }

    /**
     * Unserialize value
     *
     * @param string $value
     * @return mixed
     * @throws DataConversionException
     */
    protected function unserializeValue($value)
    {
        try {
            set_error_handler(function ($errorNumber, $errorString) {
                throw new DataConversionException($errorString, $errorNumber);
            });
            $value = $this->serialize->unserialize($value);
        } catch (\Throwable $throwable) {
            throw new DataConversionException($throwable->getMessage());
        } finally {
            restore_error_handler();
        }
        return $value;
    }

    /**
     * Encode value with json encoder.
     *
     * In PHP version < 7.1.0 serialize() uses PG(serialize_precision) which set to 17 be default.
     * Since json_encode() uses EG(precision) which set to 14 be default, json_encode() removes lower digits of
     * fraction parts and destroys original value even if PHP's float could hold more precise float value.
     * To prevent this issue EG(precision) is set to 17 to be equal with PG(serialize_precision) during
     * data converting from serialized format to JSON.
     *
     * In PHP version >= 7.1.0 serialize() and json_encode() use PG(serialize_precision) which set to -1 be default.
     * Setting -1 uses better algorithm for rounding float numbers.
     * But for data consistency during converting process PG(serialize_precision) is set to 17.
     *
     * @param string $value
     * @return string
     * @throws DataConversionException
     */
    protected function encodeJson($value)
    {
        $storedPrecision = ini_get('precision');
        $storedSerializePrecision = ini_get('serialize_precision');

        if (PHP_VERSION_ID < 70100) {
            // In PHP version < 7.1.0 json_encode() uses EG(precision).
            ini_set('precision', 17);
        } else {
            // In PHP version >= 7.1.0 json_encode() uses PG(serialize_precision).
            ini_set('serialize_precision', 17);
        }

        $value = $this->json->serialize($value);

        ini_set('precision', $storedPrecision);
        ini_set('serialize_precision', $storedSerializePrecision);

        if (json_last_error()) {
            throw new DataConversionException(json_last_error_msg());
        }
        return $value;
    }
}