<NME> FieldTypeBase.php
<BEF> <?php

namespace Bolt\Storage\Field\Type;

use Bolt\Common\Deprecated;
use Bolt\Common\Json;
use Bolt\Storage\CaseTransformTrait;
use Bolt\Storage\EntityManager;
use Bolt\Storage\Field\FieldInterface;
use Bolt\Storage\Field\Sanitiser\SanitiserAwareInterface;
use Bolt\Storage\Field\Sanitiser\WysiwygAwareInterface;
use Bolt\Storage\Mapping\ClassMetadata;
use Bolt\Storage\Query\QueryInterface;
use Bolt\Storage\QuerySet;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Types\Type;

/**
 * This is an abstract class for a field type that handles
 * the lifecycle of a field from pre-query to persist.
 *
 * @author Ross Riley <riley.ross@gmail.com>
 */
abstract class FieldTypeBase implements FieldTypeInterface, FieldInterface
{
    use CaseTransformTrait;

    /** @var string[] */
    public $mapping;

    /** @var EntityManager */
    protected $em;
    /** @var AbstractPlatform */
    protected $platform;

    /**
     * Constructor.
     *
     * @param array              $mapping
     * @param EntityManager|null $em
     */
    public function __construct(array $mapping = [], EntityManager $em = null)
    {
        $this->mapping = $mapping;
        $this->em = $em;
        if ($em) {
            $this->setPlatform($em->createQueryBuilder()->getConnection()->getDatabasePlatform());
        }
    }

    /**
     * Returns the platform.
     *
     * @return AbstractPlatform
     */
    public function getPlatform()
    {
        return $this->platform;
    }

    /**
     * Sets the current platform to an instance of AbstractPlatform.
     *
     * @param AbstractPlatform $platform
     */
    public function setPlatform(AbstractPlatform $platform)
    {
        $this->platform = $platform;
    }

    /**
     * {@inheritdoc}
     */
    public function load(QueryBuilder $query, ClassMetadata $metadata)
    {
    }

    /**
     * {@inheritdoc}
     */
    public function query(QueryInterface $query, ClassMetadata $metadata)
    {
    }

    /**
     * {@inheritdoc}
     */
    public function persist(QuerySet $queries, $entity)
    {
        $attribute = $this->getMappingAttribute();
        $key = $this->mapping['fieldname'];

        $qb = &$queries[0];
        $valueMethod = 'serialize' . ucfirst($this->camelize($attribute));
        $value = $entity->$valueMethod();

        if ($this instanceof SanitiserAwareInterface && is_string($value)) {
            $isWysiwyg = $this instanceof WysiwygAwareInterface;
            $value = $this->getSanitiser()->sanitise($value, $isWysiwyg);
        }

        $type = $this->getStorageTypeObject();

        if ($value !== null) {
            $value = $type->convertToDatabaseValue($value, $this->getPlatform());
        } elseif (isset($this->mapping['default'])) {
            $value = $this->mapping['default'];
        }
        $qb->setValue($key, ':' . $key);
        $qb->set($key, ':' . $key);
        $qb->setParameter($key, $value);
    }

    /**
     * {@inheritdoc}
     */
    public function hydrate($data, $entity)
    {
        $key = $this->mapping['fieldname'];
        $type = $this->getStorageTypeObject();
        $val = isset($data[$key]) ? $data[$key] : null;
        if ($val !== null) {
            $value = $type->convertToPHPValue($val, $this->getPlatform());
            $this->set($entity, $value);
        }
    }

    /**
     * The set method takes a raw php value and performs the conversion to the entity value.
     * Normally this is as simple as $entity->$key = $value although more complicated transforms
     * can happen should a field type choose to override this method.
     *
     * Note too that this will also be the default method called for an entity builder which is
     * designed to receive raw data to initialise an entity.
     *
     * @param object $entity
     * @param mixed  $value
     */
    public function set($entity, $value)
    {
        $key = $this->mapping['fieldname'];
        if ($value === null && isset($this->mapping['data']['default'])) {
            $value = $this->mapping['data']['default'];
        }
        $entity->$key = $value;
    }

    /**
     * Reads the current value of the field from an entity and returns value.
     *
     * @param $entity
     *
     * @return mixed
     */
    public function get($entity)
    {
        $key = $this->mapping['fieldname'];
        $valueMethod = 'get' . ucfirst($key);
        $value = $entity->$valueMethod();

        return $value;
    }

    /**
     * {@inheritdoc}
     */
    public function present($entity)
    {
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'text';
    }

    /**
     * Returns the name of the Doctrine storage type to use for a field.
     *
     * @return Type
     */
    public function getStorageType()
    {
        return Type::getType($this->mapping['type']);
    }

    /**
     * Helper method to bridge compatibility between old and new Field interfaces. Previously a string storage
     * type was allowed whereas new behaviour is to expect a Type object.
     *
     * @return Type
     */
    protected function getStorageTypeObject()
    {
        $type = $this->getStorageType();
        if (is_string($type)) {
            $type = Type::getType($type);
        }

        return $type;
    }

    /**
     * @deprecated
     * Here to maintain compatibility with the old interface
     */
    public function getStorageOptions()
    {
        Deprecated::method();

        return [];
    }

    /**
     * Gets the entity attribute name to be used for reading / persisting.
     *
     * @return string
     */
    public function getMappingAttribute()
    {
        if (!is_string($value)) {
            return false;
        }
        json_decode($value);

        return json_last_error() === JSON_ERROR_NONE;
    public function getTemplate()
    {
        Deprecated::method();

        return '@bolt/editcontent/fields/_' . $this->getName() . '.twig';
    }

    /**
     * Check if a value is a JSON string.
     *
     * @param string $value
     *
     * @return bool
     */
    protected function isJson($value)
    {
        Deprecated::method(3.4, '\Bolt\Common\Json::test');

        return Json::test($value);
    }
}

<MSG> Fix json parse inconsistency

Handles an inconsistency in the result from the JSON parser across 5.x and 7.x of PHP whereby empty strings give different results.
<DFF> @@ -224,6 +224,12 @@ abstract class FieldTypeBase implements FieldTypeInterface, FieldInterface
         if (!is_string($value)) {
             return false;
         }
+        
+        // This handles an inconsistency in the result from the JSON parser across 5.x and 7.x of PHP
+        if ($value === '') {
+            return false;
+        }
+        
         json_decode($value);
 
         return json_last_error() === JSON_ERROR_NONE;

{'commit': "diff --git a/src/Storage/Field/Type/FieldTypeBase.php b/src/Storage/Field/Type/FieldTypeBase.php\nindex 567b70100..05292d50f 100644\n--- a/src/Storage/Field/Type/FieldTypeBase.php\n+++ b/src/Storage/Field/Type/FieldTypeBase.php\n@@ -224,6 +224,12 @@ abstract class FieldTypeBase implements FieldTypeInterface, FieldInterface\n         if (!is_string($value)) {\n             return false;\n         }\n+        \n+        // This handles an inconsistency in the result from the JSON parser across 5.x and 7.x of PHP\n+        if ($value === '') {\n+            return false;\n+        }\n+        \n         json_decode($value);\n \n         return json_last_error() === JSON_ERROR_NONE;\n", 'commit_subject': 'Fix json parse inconsistency', 'repo_name': 'bolt/bolt', 'committer': {'name': 'Gawain Lynch', 'email': 'f745ec5336bcd096400627fc09f6ec7139408726@gmail.com', 'time_sec': '1481012424', 'tz_offset': '60', 'date': {'seconds': '1481012424'}}, 'commit_tree': 'e7dc6490938323182bd3bd4756d7fa299243c81c', 'commit_message': 'Fix json parse inconsistency\n\nHandles an inconsistency in the result from the JSON parser across 5.x and 7.x of PHP whereby empty strings give different results.', 'language_name': 'PHP', 'license': 'mit', 'hunks': "@@ -224,6 +224,12 @@ abstract class FieldTypeBase implements FieldTypeInterface, FieldInterface\n         if (!is_string($value)) {\n             return false;\n         }\n+        \n+        // This handles an inconsistency in the result from the JSON parser across 5.x and 7.x of PHP\n+        if ($value === '') {\n+            return false;\n+        }\n+        \n         json_decode($value);\n \n         return json_last_error() === JSON_ERROR_NONE;\n", 'addition_count': 6, 'deletion_count': 0, 'src_file': 'a/src/Storage/Field/Type/FieldTypeBase.php', 'tgt_file': 'b/src/Storage/Field/Type/FieldTypeBase.php', 'file_extension': '.php', 'before_file': "<?php\n\nnamespace Bolt\\Storage\\Field\\Type;\n\nuse Bolt\\Common\\Deprecated;\nuse Bolt\\Common\\Json;\nuse Bolt\\Storage\\CaseTransformTrait;\nuse Bolt\\Storage\\EntityManager;\nuse Bolt\\Storage\\Field\\FieldInterface;\nuse Bolt\\Storage\\Field\\Sanitiser\\SanitiserAwareInterface;\nuse Bolt\\Storage\\Field\\Sanitiser\\WysiwygAwareInterface;\nuse Bolt\\Storage\\Mapping\\ClassMetadata;\nuse Bolt\\Storage\\Query\\QueryInterface;\nuse Bolt\\Storage\\QuerySet;\nuse Doctrine\\DBAL\\Platforms\\AbstractPlatform;\nuse Doctrine\\DBAL\\Query\\QueryBuilder;\nuse Doctrine\\DBAL\\Types\\Type;\n\n/**\n * This is an abstract class for a field type that handles\n * the lifecycle of a field from pre-query to persist.\n *\n * @author Ross Riley <riley.ross@gmail.com>\n */\nabstract class FieldTypeBase implements FieldTypeInterface, FieldInterface\n{\n    use CaseTransformTrait;\n\n    /** @var string[] */\n    public $mapping;\n\n    /** @var EntityManager */\n    protected $em;\n    /** @var AbstractPlatform */\n    protected $platform;\n\n    /**\n     * Constructor.\n     *\n     * @param array              $mapping\n     * @param EntityManager|null $em\n     */\n    public function __construct(array $mapping = [], EntityManager $em = null)\n    {\n        $this->mapping = $mapping;\n        $this->em = $em;\n        if ($em) {\n            $this->setPlatform($em->createQueryBuilder()->getConnection()->getDatabasePlatform());\n        }\n    }\n\n    /**\n     * Returns the platform.\n     *\n     * @return AbstractPlatform\n     */\n    public function getPlatform()\n    {\n        return $this->platform;\n    }\n\n    /**\n     * Sets the current platform to an instance of AbstractPlatform.\n     *\n     * @param AbstractPlatform $platform\n     */\n    public function setPlatform(AbstractPlatform $platform)\n    {\n        $this->platform = $platform;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function load(QueryBuilder $query, ClassMetadata $metadata)\n    {\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function query(QueryInterface $query, ClassMetadata $metadata)\n    {\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function persist(QuerySet $queries, $entity)\n    {\n        $attribute = $this->getMappingAttribute();\n        $key = $this->mapping['fieldname'];\n\n        $qb = &$queries[0];\n        $valueMethod = 'serialize' . ucfirst($this->camelize($attribute));\n        $value = $entity->$valueMethod();\n\n        if ($this instanceof SanitiserAwareInterface && is_string($value)) {\n            $isWysiwyg = $this instanceof WysiwygAwareInterface;\n            $value = $this->getSanitiser()->sanitise($value, $isWysiwyg);\n        }\n\n        $type = $this->getStorageTypeObject();\n\n        if ($value !== null) {\n            $value = $type->convertToDatabaseValue($value, $this->getPlatform());\n        } elseif (isset($this->mapping['default'])) {\n            $value = $this->mapping['default'];\n        }\n        $qb->setValue($key, ':' . $key);\n        $qb->set($key, ':' . $key);\n        $qb->setParameter($key, $value);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function hydrate($data, $entity)\n    {\n        $key = $this->mapping['fieldname'];\n        $type = $this->getStorageTypeObject();\n        $val = isset($data[$key]) ? $data[$key] : null;\n        if ($val !== null) {\n            $value = $type->convertToPHPValue($val, $this->getPlatform());\n            $this->set($entity, $value);\n        }\n    }\n\n    /**\n     * The set method takes a raw php value and performs the conversion to the entity value.\n     * Normally this is as simple as $entity->$key = $value although more complicated transforms\n     * can happen should a field type choose to override this method.\n     *\n     * Note too that this will also be the default method called for an entity builder which is\n     * designed to receive raw data to initialise an entity.\n     *\n     * @param object $entity\n     * @param mixed  $value\n     */\n    public function set($entity, $value)\n    {\n        $key = $this->mapping['fieldname'];\n        if ($value === null && isset($this->mapping['data']['default'])) {\n            $value = $this->mapping['data']['default'];\n        }\n        $entity->$key = $value;\n    }\n\n    /**\n     * Reads the current value of the field from an entity and returns value.\n     *\n     * @param $entity\n     *\n     * @return mixed\n     */\n    public function get($entity)\n    {\n        $key = $this->mapping['fieldname'];\n        $valueMethod = 'get' . ucfirst($key);\n        $value = $entity->$valueMethod();\n\n        return $value;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function present($entity)\n    {\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getName()\n    {\n        return 'text';\n    }\n\n    /**\n     * Returns the name of the Doctrine storage type to use for a field.\n     *\n     * @return Type\n     */\n    public function getStorageType()\n    {\n        return Type::getType($this->mapping['type']);\n    }\n\n    /**\n     * Helper method to bridge compatibility between old and new Field interfaces. Previously a string storage\n     * type was allowed whereas new behaviour is to expect a Type object.\n     *\n     * @return Type\n     */\n    protected function getStorageTypeObject()\n    {\n        $type = $this->getStorageType();\n        if (is_string($type)) {\n            $type = Type::getType($type);\n        }\n\n        return $type;\n    }\n\n    /**\n     * @deprecated\n     * Here to maintain compatibility with the old interface\n     */\n    public function getStorageOptions()\n    {\n        Deprecated::method();\n\n        return [];\n    }\n\n    /**\n     * Gets the entity attribute name to be used for reading / persisting.\n     *\n     * @return string\n     */\n    public function getMappingAttribute()\n    {\n        if (!is_string($value)) {\n            return false;\n        }\n        json_decode($value);\n\n        return json_last_error() === JSON_ERROR_NONE;\n    public function getTemplate()\n    {\n        Deprecated::method();\n\n        return '@bolt/editcontent/fields/_' . $this->getName() . '.twig';\n    }\n\n    /**\n     * Check if a value is a JSON string.\n     *\n     * @param string $value\n     *\n     * @return bool\n     */\n    protected function isJson($value)\n    {\n        Deprecated::method(3.4, '\\Bolt\\Common\\Json::test');\n\n        return Json::test($value);\n    }\n}\n", 'message': 'Fix json parse inconsistency\n\nHandles an inconsistency in the result from the JSON parser across 5.x and 7.x of PHP whereby empty strings give different results.', 'author_name': 'Gawain Lynch', 'lang': 'php'}