[TASK] Add type converter for core types like Enumeration 08/24708/9
authorSascha Egerer <sascha.egerer@dkd.de>
Sun, 13 Oct 2013 18:44:17 +0000 (20:44 +0200)
committerThomas Maroschik <tmaroschik@dfau.de>
Tue, 15 Oct 2013 14:33:59 +0000 (16:33 +0200)
* Fixed a major bug in Enumeration in the isValid function
  to prevent PHP typeloose comparison hell and added Tests
* Added a CoreTypeInterface that can be used in the extbase
  data mapper to map properties to new core types like
  the enumeration

Resolves: #52762
Releases: 6.2
Change-Id: I966c64ae8a82d13bdcb47c41917da98c48f0b475
Reviewed-on: https://review.typo3.org/24708
Reviewed-by: Marc Bastian Heinrichs
Tested-by: Marc Bastian Heinrichs
Reviewed-by: Sascha Egerer
Reviewed-by: Stefan Neufeind
Reviewed-by: Thomas Maroschik
Tested-by: Thomas Maroschik
12 files changed:
typo3/sysext/core/Classes/Type/Enumeration.php
typo3/sysext/core/Classes/Type/Exception/InvalidEnumerationValueException.php
typo3/sysext/core/Classes/Type/Exception/InvalidValueExceptionInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/Type/TypeInterface.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Type/EnumerationTest.php
typo3/sysext/extbase/Classes/Persistence/Generic/Backend.php
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapper.php
typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php
typo3/sysext/extbase/Classes/Property/TypeConverter/CoreTypeConverter.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Utility/TypeHandlingUtility.php
typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php
typo3/sysext/extbase/ext_localconf.php

index 8ff67f8..89781b6 100644 (file)
@@ -36,7 +36,7 @@ use TYPO3\CMS\Core\Type\Exception;
  * The prefix "Abstract" has been left out by intention because
  * a "type" is abstract by definition.
  */
-abstract class Enumeration {
+abstract class Enumeration implements TypeInterface {
 
        /**
         * @var mixed
@@ -55,7 +55,7 @@ abstract class Enumeration {
        public function __construct($value = NULL) {
                if ($value === NULL && !defined('static::__default')) {
                        throw new Exception\InvalidEnumerationValueException(
-                               sprintf('A value for %s is required if no __default is defined.', __CLASS__),
+                               sprintf('A value for %s is required if no __default is defined.', get_class($this)),
                                1381512753
                        );
                }
@@ -65,7 +65,7 @@ abstract class Enumeration {
                $this->loadValues();
                if (!$this->isValid($value)) {
                        throw new Exception\InvalidEnumerationValueException(
-                               sprintf('Invalid value %s for %s', $value, __CLASS__),
+                               sprintf('Invalid value %s for %s', $value, get_class($this)),
                                1381512761
                        );
                }
@@ -77,7 +77,7 @@ abstract class Enumeration {
         * @internal param string $class
         */
        protected function loadValues() {
-               $class = get_called_class();
+               $class = get_class($this);
 
                if (isset(static::$enumConstants[$class])) {
                        return;
@@ -137,14 +137,14 @@ abstract class Enumeration {
         * @throws Exception\InvalidEnumerationValueException
         */
        protected function setValue($value) {
-               $enumKey = array_search($value, static::$enumConstants[get_called_class()]);
+               $enumKey = array_search($value, static::$enumConstants[get_class($this)]);
                if ($enumKey === FALSE) {
                        throw new Exception\InvalidEnumerationValueException(
                                sprintf('Invalid value %s for %s', $value, __CLASS__),
                                1381615295
                        );
                }
-               $this->value = static::$enumConstants[get_called_class()][$enumKey];
+               $this->value = static::$enumConstants[get_class($this)][$enumKey];
        }
 
        /**
@@ -154,7 +154,13 @@ abstract class Enumeration {
         * @return boolean
         */
        protected function isValid($value) {
-               return in_array($value, static::$enumConstants[get_called_class()]);
+               $value = (string) $value;
+               foreach (static::$enumConstants[get_class($this)] as $constantValue) {
+                       if ($value === (string) $constantValue) {
+                               return TRUE;
+                       }
+               }
+               return FALSE;
        }
 
        /**
@@ -166,7 +172,7 @@ abstract class Enumeration {
         * @return array
         */
        public function getConstants($include_default = FALSE) {
-               $enumConstants = static::$enumConstants[get_called_class()];
+               $enumConstants = static::$enumConstants[get_class($this)];
                if (!$include_default) {
                        unset($enumConstants['__default']);
                }
index 7ba52bf..0653410 100644 (file)
@@ -29,6 +29,6 @@ namespace TYPO3\CMS\Core\Type\Exception;
 /**
  * Exception for an invalid enumeration value
  */
-class InvalidEnumerationValueException extends \TYPO3\CMS\Core\Type\Exception {
+class InvalidEnumerationValueException extends \TYPO3\CMS\Core\Type\Exception implements InvalidValueExceptionInterface {
 
 }
diff --git a/typo3/sysext/core/Classes/Type/Exception/InvalidValueExceptionInterface.php b/typo3/sysext/core/Classes/Type/Exception/InvalidValueExceptionInterface.php
new file mode 100644 (file)
index 0000000..f6ef70a
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+namespace TYPO3\CMS\Core\Type\Exception;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2013 Sascha Egerer <sascha.egerer@dkd.de>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Interface for Invalid value exception
+ */
+interface InvalidValueExceptionInterface {
+}
diff --git a/typo3/sysext/core/Classes/Type/TypeInterface.php b/typo3/sysext/core/Classes/Type/TypeInterface.php
new file mode 100644 (file)
index 0000000..f1d258f
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+namespace TYPO3\CMS\Core\Type;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2013 Sascha Egerer <sascha.egerer@dkd.de>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * This is an interface that has to be used by all Core Types.
+ * All of them have to implement a __toString() method that is
+ * used to get a flatten string for the persistence of the object.
+ */
+interface TypeInterface {
+
+       /**
+        * Core types must implement the __toString function in order to be
+        * serialized to the database;
+        *
+        * @return string
+        */
+       public function __toString();
+}
\ No newline at end of file
index 5cc70a0..4c251a3 100644 (file)
@@ -132,25 +132,79 @@ class EnumerationTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        }
 
        /**
-        * @test
+        * Array of value pairs and expected comparison result
         */
-       public function isValueReturnsTrueIfValueIsValid() {
-               $enumeration = $this->getAccessibleMock(
-                       'TYPO3\\CMS\\Core\\Tests\\Unit\\Type\Fixture\\Enumeration\\CompleteEnumeration',
-                       array('dummy')
+       public function isValidComparisonExpectations() {
+               return array(
+                       array(
+                               1,
+                               1,
+                               TRUE
+                       ),
+                       array(
+                               1,
+                               '1',
+                               TRUE
+                       ),
+                       array(
+                               '1',
+                               1,
+                               TRUE
+                       ),
+                       array(
+                               'a1',
+                               1,
+                               FALSE
+                       ),
+                       array(
+                               1,
+                               'a1',
+                               FALSE
+                       ),
+                       array(
+                               '1a',
+                               1,
+                               FALSE
+                       ),
+                       array(
+                               1,
+                               '1a',
+                               FALSE
+                       ),
+                       array(
+                               'foo',
+                               'foo',
+                               TRUE
+                       ),
+                       array(
+                               'foo',
+                               'bar',
+                               FALSE
+                       ),
+                       array(
+                               'foo',
+                               'foobar',
+                               FALSE
+                       )
                );
-               $this->assertTrue($enumeration->_call('isValid', 1));
        }
 
        /**
         * @test
+        * @dataProvider isValidComparisonExpectations
         */
-       public function isValueReturnsFalseIfValueIsValid() {
+       public function isValidDoesTypeLooseComparison($enumerationValue, $testValue, $expectation) {
+               $mockName = uniqid('CompleteEnumerationMock');
                $enumeration = $this->getAccessibleMock(
                        'TYPO3\\CMS\\Core\\Tests\\Unit\\Type\Fixture\\Enumeration\\CompleteEnumeration',
-                       array('dummy')
+                       array('dummy'),
+                       array(),
+                       $mockName,
+                       FALSE
                );
-               $this->assertFalse($enumeration->_call('isValid', 2));
+               $enumeration->_setStatic('enumConstants', array($mockName => array('CONSTANT_NAME' => $enumerationValue)));
+               $enumeration->_set('value', $enumerationValue);
+               $this->assertSame($expectation, $enumeration->_call('isValid', $testValue));
        }
 
        /**
index 025e736..cdc4302 100644 (file)
@@ -27,6 +27,7 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic;
  *
  *  This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
+use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
 
 /**
  * A persistence backend. This backend maps objects to the relational model of the storage backend.
@@ -996,6 +997,8 @@ class Backend implements \TYPO3\CMS\Extbase\Persistence\Generic\BackendInterface
                        }
                } elseif ($input instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
                        return $input->getUid();
+               } elseif (TypeHandlingUtility::isCoreType($input)) {
+                       return (string) $input;
                } elseif (is_bool($input)) {
                        return $input === TRUE ? 1 : 0;
                } else {
index 69d7da5..2079524 100644 (file)
@@ -27,6 +27,9 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper;
  *
  *  This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
+use TYPO3\CMS\Core\Type\TypeInterface;
+use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
+
 /**
  * A mapper to map database tables configured in $TCA on domain objects.
  */
@@ -219,6 +222,9 @@ class DataMapper implements \TYPO3\CMS\Core\SingletonInterface {
                                        case 'TYPO3\\CMS\\Extbase\\Persistence\\ObjectStorage':
                                                $propertyValue = $this->mapResultToPropertyValue($object, $propertyName, $this->fetchRelated($object, $propertyName, $row[$columnName]));
                                                break;
+                                       case TypeHandlingUtility::isCoreType($propertyData['type']):
+                                               $propertyValue = $this->mapCoreType($propertyData['type'], $row[$columnName]);
+                                               break;
                                        default:
                                                if ($propertyData['type'] === 'DateTime' || in_array('DateTime', class_parents($propertyData['type']))) {
                                                        $propertyValue = $this->mapDateTime($row[$columnName], $columnMap->getDateTimeStorageFormat());
@@ -234,6 +240,17 @@ class DataMapper implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
+        * Map value to a core type
+        *
+        * @param string $type
+        * @param mixed $value
+        * @return \TYPO3\CMS\Core\Type\TypeInterface
+        */
+       protected function mapCoreType($type, $value) {
+               return new $type($value);
+       }
+
+       /**
         * Creates a DateTime from an unix timestamp or date/datetime value.
         * If the input is empty, NULL is returned.
         *
index 905d8b5..fd80746 100644 (file)
@@ -29,6 +29,7 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Storage;
  ***************************************************************/
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
 
 /**
  * A Storage backend
@@ -664,6 +665,8 @@ class Typo3DbBackend implements \TYPO3\CMS\Extbase\Persistence\Generic\Storage\B
                }
                if ($input instanceof \DateTime) {
                        return $input->format('U');
+               } elseif (TypeHandlingUtility::isCoreType($input)) {
+                       return (string) $input;
                } elseif (is_object($input)) {
                        if ($input instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
                                $realInput = $input->_loadRealInstance();
diff --git a/typo3/sysext/extbase/Classes/Property/TypeConverter/CoreTypeConverter.php b/typo3/sysext/extbase/Classes/Property/TypeConverter/CoreTypeConverter.php
new file mode 100644 (file)
index 0000000..a274c0f
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+namespace TYPO3\CMS\Extbase\Property\TypeConverter;
+
+/*                                                                        *
+ * This script belongs to the Extbase framework                           *
+ *                                                                        *
+ * It is free software; you can redistribute it and/or modify it under    *
+ * the terms of the GNU Lesser General Public License as published by the *
+ * Free Software Foundation, either version 3 of the License, or (at your *
+ * option) any later version.                                             *
+ *                                                                        *
+ * This script is distributed in the hope that it will be useful, but     *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN-    *
+ * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser       *
+ * General Public License for more details.                               *
+ *                                                                        *
+ * You should have received a copy of the GNU Lesser General Public       *
+ * License along with the script.                                         *
+ * If not, see http://www.gnu.org/licenses/lgpl.html                      *
+ *                                                                        *
+ * The TYPO3 project - inspiring people to share!                         *
+ *                                                                        */
+use TYPO3\CMS\Core\Type\Exception\InvalidValueExceptionInterface;
+use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
+
+/**
+ * Converter which transforms simple types to a core type
+ * implementing \TYPO3\CMS\Core\Type\TypeInterface.
+ *
+ * @api
+ */
+class CoreTypeConverter extends \TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter {
+
+       /**
+        * @var array<string>
+        */
+       protected $sourceTypes = array('string', 'integer', 'float', 'boolean', 'array');
+
+       /**
+        * @var string
+        */
+       protected $targetType = 'TYPO3\\CMS\\Core\\Type\\TypeInterface';
+
+       /**
+        * @var integer
+        */
+       protected $priority = 1;
+
+       /**
+        * @param mixed $source
+        * @param string $targetType
+        * @return boolean
+        */
+       public function canConvertFrom($source, $targetType) {
+               return TypeHandlingUtility::isCoreType($targetType);
+       }
+
+       /**
+        * Convert an object from $source to an Enumeration.
+        *
+        * @param mixed $source
+        * @param string $targetType
+        * @param array $convertedChildProperties
+        * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
+        * @return object the target type
+        */
+       public function convertFrom($source, $targetType, array $convertedChildProperties = array(), \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration = NULL) {
+               try {
+                       return new $targetType($source);
+               } catch (InvalidValueExceptionInterface $exception) {
+                       return new \TYPO3\CMS\Extbase\Error\Error($exception->getMessage(), 1381680012);
+               }
+       }
+}
index 0ac814d..a6a0217 100644 (file)
@@ -104,6 +104,16 @@ class TypeHandlingUtility {
        }
 
        /**
+        * Returns TRUE if the $type is a CMS core type object.
+        *
+        * @param string $type
+        * @return boolean
+        */
+       static public function isCoreType($type) {
+               return is_subclass_of($type, 'TYPO3\\CMS\\Core\\Type\\TypeInterface');
+       }
+
+       /**
         * Returns TRUE if the $type is a collection type.
         *
         * @param string $type
index 50a767c..3fb6543 100644 (file)
@@ -285,7 +285,7 @@ class ValidatorResolver implements \TYPO3\CMS\Core\SingletonInterface {
                                if (\TYPO3\CMS\Extbase\Utility\TypeHandlingUtility::isCollectionType($propertyTargetClassName) === TRUE) {
                                        $collectionValidator = $this->createValidator('TYPO3\CMS\Extbase\Validation\Validator\CollectionValidator', array('elementType' => $parsedType['elementType'], 'validationGroups' => $validationGroups));
                                        $objectValidator->addPropertyValidator($classPropertyName, $collectionValidator);
-                               } elseif (class_exists($propertyTargetClassName) && $this->objectManager->isRegistered($propertyTargetClassName) && $this->objectManager->getScope($propertyTargetClassName) === \TYPO3\CMS\Extbase\Object\Container\Container::SCOPE_PROTOTYPE) {
+                               } elseif (class_exists($propertyTargetClassName) && !\TYPO3\CMS\Extbase\Utility\TypeHandlingUtility::isCoreType($propertyTargetClassName) && $this->objectManager->isRegistered($propertyTargetClassName) && $this->objectManager->getScope($propertyTargetClassName) === \TYPO3\CMS\Extbase\Object\Container\Container::SCOPE_PROTOTYPE) {
                                        $validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName, $validationGroups);
                                        if (count($validatorForProperty) > 0) {
                                                $objectValidator->addPropertyValidator($classPropertyName, $validatorForProperty);
index a95498d..ec25f57 100644 (file)
@@ -36,6 +36,7 @@ unset($extbaseObjectContainer);
 \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter('TYPO3\\CMS\\Extbase\\Property\\TypeConverter\\PersistentObjectConverter');
 \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter('TYPO3\\CMS\\Extbase\\Property\\TypeConverter\\ObjectConverter');
 \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter('TYPO3\\CMS\\Extbase\\Property\\TypeConverter\\StringConverter');
+\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter('TYPO3\\CMS\\Extbase\\Property\\TypeConverter\\CoreTypeConverter');
 // Experimental FAL<->extbase converters
 \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter('TYPO3\\CMS\\Extbase\\Property\\TypeConverter\\FileConverter');
 \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter('TYPO3\\CMS\\Extbase\\Property\\TypeConverter\\FileReferenceConverter');