[BUGFIX] Do not try to map properties with unknown type 42/62542/12
authorSusanne Moog <look@susi.dev>
Thu, 5 Dec 2019 14:02:18 +0000 (15:02 +0100)
committerAlexander Schnitzler <git@alexanderschnitzler.de>
Wed, 4 Mar 2020 08:50:59 +0000 (09:50 +0100)
The Extbase data mapper has been adjusted to only
map properties where the type is known.

Resolves: #89857
Releases: master, 9.5
Change-Id: If432fd201467a740643c7e3c322b97955f42c5be
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62542
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Susanne Moog <look@susi.dev>
Tested-by: Alexander Schnitzler <git@alexanderschnitzler.de>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Susanne Moog <look@susi.dev>
Reviewed-by: Alexander Schnitzler <git@alexanderschnitzler.de>
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapper.php
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/Exception.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/Exception/NonExistentPropertyException.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/Exception/UnknownPropertyTypeException.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Mapper/DataMapperTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Mapper/Fixture/DummyEntity.php

index cd00833..889f831 100644 (file)
@@ -22,6 +22,7 @@ use TYPO3\CMS\Extbase\Object\Exception\CannotReconstituteObjectException;
 use TYPO3\CMS\Extbase\Persistence;
 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException;
 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
+use TYPO3\CMS\Extbase\Reflection\ClassSchema;
 use TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception\NoSuchPropertyException;
 use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
 
@@ -204,6 +205,8 @@ class DataMapper
      *
      * @param DomainObjectInterface $object The object to set properties on
      * @param array $row
+     * @throws Exception\NonExistentPropertyException
+     * @throws Exception\UnknownPropertyTypeException
      */
     protected function thawProperties(DomainObjectInterface $object, array $row)
     {
@@ -230,7 +233,28 @@ class DataMapper
             }
             $columnMap = $dataMap->getColumnMap($propertyName);
             $columnName = $columnMap->getColumnName();
-            $propertyType = $classSchema->getProperty($propertyName)->getType();
+
+            try {
+                $property = $classSchema->getProperty($propertyName);
+            } catch (NoSuchPropertyException $e) {
+                throw new Exception\NonExistentPropertyException(
+                    'The type of property ' . $className . '::' . $propertyName . ' could not be identified, ' .
+                    'as property ' . $propertyName . ' is unknown to the ' . ClassSchema::class . ' instance of class .' .
+                    $className . '. Please make sure said property exists and that you cleared all caches to trigger' .
+                    'a new build of said ' . ClassSchema::class . ' instance.',
+                    1580056272
+                );
+            }
+
+            $propertyType = $property->getType();
+            if ($propertyType === null) {
+                throw new Exception\UnknownPropertyTypeException(
+                    'The type of property ' . $className . '::' . $propertyName . ' could not be identified, therefore the desired value (' .
+                    var_export($propertyValue, true) . ') cannot be mapped onto it. The type of a class property is usually defined via php doc blocks. ' .
+                    'Make sure the property has a valid @var tag set which defines the type.',
+                    1579965021
+                );
+            }
             $propertyValue = null;
             if (isset($row[$columnName])) {
                 switch ($propertyType) {
diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/Exception.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/Exception.php
new file mode 100644 (file)
index 0000000..fe801bf
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+class Exception extends \TYPO3\CMS\Extbase\Persistence\Exception
+{
+}
diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/Exception/NonExistentPropertyException.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/Exception/NonExistentPropertyException.php
new file mode 100644 (file)
index 0000000..762cb8b
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper\Exception;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+class NonExistentPropertyException extends \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\Exception
+{
+}
diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/Exception/UnknownPropertyTypeException.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/Exception/UnknownPropertyTypeException.php
new file mode 100644 (file)
index 0000000..f662a35
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper\Exception;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+class UnknownPropertyTypeException extends \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\Exception
+{
+}
index 4d7b999..b9104e2 100644 (file)
@@ -20,7 +20,10 @@ use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
 use TYPO3\CMS\Extbase\Object\Container\Container;
 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException;
 use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap;
+use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap;
+use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory;
 use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
+use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\Exception\UnknownPropertyTypeException;
 use TYPO3\CMS\Extbase\Reflection\ClassSchema;
 use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
@@ -48,7 +51,7 @@ class DataMapperTest extends UnitTestCase
                 $this->createMock(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class),
                 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class),
                 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Session::class),
-                $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class),
+                $this->createMock(DataMapFactory::class),
                 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface::class),
                 $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class),
                 $this->createMock(EventDispatcherInterface::class),
@@ -84,7 +87,7 @@ class DataMapperTest extends UnitTestCase
                 $this->createMock(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class),
                 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class),
                 $persistenceSession,
-                $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class),
+                $this->createMock(DataMapFactory::class),
                 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface::class),
                 $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class),
                 $this->createMock(EventDispatcherInterface::class),
@@ -118,9 +121,9 @@ class DataMapperTest extends UnitTestCase
             'firstProperty' => new ColumnMap('firstProperty', 'firstProperty'),
             'secondProperty' => new ColumnMap('secondProperty', 'secondProperty'),
             'thirdProperty' => new ColumnMap('thirdProperty', 'thirdProperty'),
-            'fourthProperty' => new ColumnMap('fourthProperty', 'fourthProperty')
+            'fourthProperty' => new ColumnMap('fourthProperty', 'fourthProperty'),
         ];
-        $dataMap = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class, ['dummy'], [$className, $className]);
+        $dataMap = $this->getAccessibleMock(DataMap::class, ['dummy'], [$className, $className]);
         $dataMap->_set('columnMaps', $columnMaps);
         $dataMaps = [
             $className => $dataMap
@@ -128,10 +131,10 @@ class DataMapperTest extends UnitTestCase
         /** @var AccessibleObjectInterface|\TYPO3\CMS\Extbase\Reflection\ClassSchema $classSchema */
         $classSchema = new ClassSchema($className);
         $mockReflectionService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class)
-            ->setMethods(['getClassSchema'])
+            ->onlyMethods(['getClassSchema'])
             ->getMock();
         $mockReflectionService->expects(self::any())->method('getClassSchema')->willReturn($classSchema);
-        $dataMapFactory = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class, ['dummy'], [], '', false);
+        $dataMapFactory = $this->getAccessibleMock(DataMapFactory::class, ['dummy'], [], '', false);
         $dataMapFactory->_set('dataMaps', $dataMaps);
         $dataMapper = $this->getAccessibleMock(
             DataMapper::class,
@@ -155,6 +158,50 @@ class DataMapperTest extends UnitTestCase
     }
 
     /**
+     * @test
+     */
+    public function thawPropertiesThrowsExceptionOnUnknownPropertyType(): void
+    {
+        $className = Fixture\DummyEntity::class;
+        $object = new Fixture\DummyEntity();
+        $row = [
+            'uid' => '1234',
+            'unknownType' => 'What am I?'
+        ];
+        $columnMaps = [
+            'unknownType' => new ColumnMap('unknownType', 'unknownType'),
+        ];
+        $dataMap = $this->getAccessibleMock(DataMap::class, ['dummy'], [$className, $className]);
+        $dataMap->_set('columnMaps', $columnMaps);
+        $dataMaps = [
+            $className => $dataMap
+        ];
+        /** @var AccessibleObjectInterface|\TYPO3\CMS\Extbase\Reflection\ClassSchema $classSchema */
+        $classSchema = new ClassSchema($className);
+        $mockReflectionService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class)
+            ->onlyMethods(['getClassSchema'])
+            ->getMock();
+        $mockReflectionService->method('getClassSchema')->willReturn($classSchema);
+        $dataMapFactory = $this->getAccessibleMock(DataMapFactory::class, ['dummy'], [], '', false);
+        $dataMapFactory->_set('dataMaps', $dataMaps);
+        $dataMapper = $this->getAccessibleMock(
+            DataMapper::class,
+            ['dummy'],
+            [
+                $mockReflectionService,
+                $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class),
+                $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Session::class),
+                $dataMapFactory,
+                $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface::class),
+                $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class),
+                $this->createMock(EventDispatcherInterface::class),
+            ]
+        );
+        $this->expectException(UnknownPropertyTypeException::class);
+        $dataMapper->_call('thawProperties', $object, $row);
+    }
+
+    /**
      * Test if fetchRelatedEager method returns NULL when $fieldValue = '' and relation type == RELATION_HAS_ONE
      *
      * This is actually a functional test as it tests multiple units along with a very specific setup of dependencies.
@@ -166,7 +213,7 @@ class DataMapperTest extends UnitTestCase
     {
         $columnMap = new ColumnMap('columnName', 'propertyName');
         $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
-        $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
+        $dataMap = $this->getMockBuilder(DataMap::class)
             ->setMethods(['getColumnMap'])
             ->disableOriginalConstructor()
             ->getMock();
@@ -189,7 +236,7 @@ class DataMapperTest extends UnitTestCase
     {
         $columnMap = new ColumnMap('columnName', 'propertyName');
         $columnMap->setTypeOfRelation(ColumnMap::RELATION_BELONGS_TO_MANY);
-        $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
+        $dataMap = $this->getMockBuilder(DataMap::class)
             ->setMethods(['getColumnMap'])
             ->disableOriginalConstructor()
             ->getMock();
@@ -213,7 +260,7 @@ class DataMapperTest extends UnitTestCase
     {
         $columnMap = new ColumnMap('columnName', 'propertyName');
         $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
-        $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
+        $dataMap = $this->getMockBuilder(DataMap::class)
             ->setMethods(['getColumnMap'])
             ->disableOriginalConstructor()
             ->getMock();
@@ -239,7 +286,7 @@ class DataMapperTest extends UnitTestCase
     {
         $columnMap = new ColumnMap('columnName', 'propertyName');
         $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
-        $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
+        $dataMap = $this->getMockBuilder(DataMap::class)
             ->setMethods(['getColumnMap'])
             ->disableOriginalConstructor()
             ->getMock();
@@ -276,7 +323,7 @@ class DataMapperTest extends UnitTestCase
                 $mockReflectionService,
                 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class),
                 $session,
-                $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class),
+                $this->createMock(DataMapFactory::class),
                 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface::class),
                 $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class),
                 $this->createMock(EventDispatcherInterface::class),
index e8edc96..f1de89e 100644 (file)
@@ -39,4 +39,9 @@ class DummyEntity extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
      * @var bool
      */
     public $fourthProperty;
+
+    /**
+     * no var here :(
+     */
+    public $unknownType;
 }