[BUGFIX] Extbase does not restrict queries to use only properties
authorStefan Neufeind <typo3.neufeind@speedpartner.de>
Sat, 11 Jun 2011 11:37:27 +0000 (13:37 +0200)
committerStefan Neufeind <typo3.neufeind@speedpartner.de>
Sat, 11 Jun 2011 13:34:49 +0000 (15:34 +0200)
Check for restrictions and throw exceptions is needed.
Includes unittests.

Change-Id: I5886c402a08ad1e385092086a7755d61b567ec91
Resolves: #8494

typo3/sysext/extbase/Classes/Persistence/Exception/UnknownProperty.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/Query.php
typo3/sysext/extbase/Tests/Unit/Persistence/QueryTest.php

diff --git a/typo3/sysext/extbase/Classes/Persistence/Exception/UnknownProperty.php b/typo3/sysext/extbase/Classes/Persistence/Exception/UnknownProperty.php
new file mode 100644 (file)
index 0000000..5a0df93
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3.
+*  All credits go to the v5 team.
+*
+*  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.
+*
+*  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!
+***************************************************************/
+
+/**
+ * An "Unknown Property" exception
+ *
+ * @package Extbase
+ * @subpackage Persistence\Exception
+ * @version $ID:$
+ */
+class Tx_Extbase_Persistence_Exception_UnknownProperty extends Tx_Extbase_Persistence_Exception {
+}
+
+?>
\ No newline at end of file
index 911d66d..c66bd27 100644 (file)
@@ -52,6 +52,11 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
        protected $dataMapper;
 
        /**
+        * @var Tx_Extbase_Reflection_Service
+        */
+       protected $reflectionService;
+
+       /**
         * @var Tx_Extbase_Persistence_ManagerInterface
         */
        protected $persistenceManager;
@@ -136,6 +141,16 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
        }
 
        /**
+        * Injects the Reflection Service
+        *
+        * @param Tx_Extbase_Reflection_Service
+        * @return void
+        */
+       public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
+               $this->reflectionService = $reflectionService;
+       }
+
+       /**
         * Injects the Query Object Model Factory
         *
         * @param Tx_Extbase_Persistence_QOM_QueryObjectModelFactory $qomFactory
@@ -250,6 +265,9 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
         * @api
         */
        public function setOrderings(array $orderings) {
+               foreach (array_keys($orderings) as $propertyName) {
+                       if (!$this->queryTypeHasProperty($propertyName)) throw new Tx_Extbase_Persistence_Exception_UnknownProperty('The given property name"' . $propertyName . '" has no corresponding property in class "' . $this->getType() . '".', 1307805930);
+               }
                $this->orderings = $orderings;
                return $this;
        }
@@ -453,6 +471,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
         * @api
         */
        public function equals($propertyName, $operand, $caseSensitive = TRUE) {
+               if (!$this->queryTypeHasProperty($propertyName)) throw new Tx_Extbase_Persistence_Exception_UnknownProperty('The given property name"' . $propertyName . '" has no corresponding property in class "' . $this->getType() . '".', 1307805931);
                if (is_object($operand) || $caseSensitive) {
                        $comparison = $this->qomFactory->comparison(
                                $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
@@ -481,6 +500,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
         * @api
         */
        public function like($propertyName, $operand) {
+               if (!$this->queryTypeHasProperty($propertyName)) throw new Tx_Extbase_Persistence_Exception_UnknownProperty('The given property name"' . $propertyName . '" has no corresponding property in class "' . $this->getType() . '".', 1307805932);
                return $this->qomFactory->comparison(
                        $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
                        Tx_Extbase_Persistence_QueryInterface::OPERATOR_LIKE,
@@ -498,6 +518,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
         * @api
         */
        public function contains($propertyName, $operand){
+               if (!$this->queryTypeHasProperty($propertyName)) throw new Tx_Extbase_Persistence_Exception_UnknownProperty('The given property name"' . $propertyName . '" has no corresponding property in class "' . $this->getType() . '".', 1307805933);
                return $this->qomFactory->comparison(
                        $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
                        Tx_Extbase_Persistence_QueryInterface::OPERATOR_CONTAINS,
@@ -515,6 +536,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
         * @api
         */
        public function in($propertyName, $operand) {
+               if (!$this->queryTypeHasProperty($propertyName)) throw new Tx_Extbase_Persistence_Exception_UnknownProperty('The given property name"' . $propertyName . '" has no corresponding property in class "' . $this->getType() . '".', 1307805934);
                if (!is_array($operand) && (!$operand instanceof ArrayAccess) && (!$operand instanceof Traversable)) {
                        throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('The "in" operator must be given a mutlivalued operand (array, ArrayAccess, Traversable).', 1264678095);
                }
@@ -535,6 +557,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
         * @api
         */
        public function lessThan($propertyName, $operand) {
+               if (!$this->queryTypeHasProperty($propertyName)) throw new Tx_Extbase_Persistence_Exception_UnknownProperty('The given property name"' . $propertyName . '" has no corresponding property in class "' . $this->getType() . '".', 1307805935);
                return $this->qomFactory->comparison(
                        $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
                        Tx_Extbase_Persistence_QueryInterface::OPERATOR_LESS_THAN,
@@ -551,6 +574,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
         * @api
         */
        public function lessThanOrEqual($propertyName, $operand) {
+               if (!$this->queryTypeHasProperty($propertyName)) throw new Tx_Extbase_Persistence_Exception_UnknownProperty('The given property name"' . $propertyName . '" has no corresponding property in class "' . $this->getType() . '".', 1307805936);
                return $this->qomFactory->comparison(
                        $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
                        Tx_Extbase_Persistence_QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO,
@@ -567,6 +591,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
         * @api
         */
        public function greaterThan($propertyName, $operand) {
+               if (!$this->queryTypeHasProperty($propertyName)) throw new Tx_Extbase_Persistence_Exception_UnknownProperty('The given property name"' . $propertyName . '" has no corresponding property in class "' . $this->getType() . '".', 1307805937);
                return $this->qomFactory->comparison(
                        $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
                        Tx_Extbase_Persistence_QueryInterface::OPERATOR_GREATER_THAN,
@@ -583,6 +608,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
         * @api
         */
        public function greaterThanOrEqual($propertyName, $operand) {
+               if (!$this->queryTypeHasProperty($propertyName)) throw new Tx_Extbase_Persistence_Exception_UnknownProperty('The given property name"' . $propertyName . '" has no corresponding property in class "' . $this->getType() . '".', 1307805938);
                return $this->qomFactory->comparison(
                        $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
                        Tx_Extbase_Persistence_QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO,
@@ -591,6 +617,16 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
        }
 
        /**
+        * Checks if a property exists in the query type.
+        *
+        * @param string $propertyName The name of the property
+        * @return bool TRUE if the property exists; FALSE otherwise
+        */
+       protected function queryTypeHasProperty($propertyName) {
+               return $this->reflectionService->getClassSchema($this->getType())->hasProperty($propertyName);
+       }
+
+       /**
         * @return void
         */
        public function __wakeup() {
index 98b9f21..7332b3c 100644 (file)
@@ -127,5 +127,79 @@ class Tx_Extbase_Tests_Unit_Persistence_QueryTest extends Tx_Extbase_Tests_Unit_
                $this->query->setOffset(-1);
        }
 
+       /**
+        * @test
+        */
+       public function itCanBeTestedIfTheQueryTypeHasAGivenPropertyForAnUnknownProperty() {
+               $mockClassSchema = $this->getMock('Tx_Extbase_Reflection_ClassSchema', array('hasProperty'), array(), '', FALSE);
+               $mockClassSchema->expects($this->once())->method('hasProperty')->with($this->equalTo('unknownProperty'))->will($this->returnValue(FALSE));
+
+               $mockReflectionService = $this->getMock('Tx_Extbase_Reflection_Service', array('getClassSchema'), array(), '', FALSE);
+               $mockReflectionService->expects($this->once())->method('getClassSchema')->with($this->equalTo('Foo_Class_Name'))->will($this->returnValue($mockClassSchema));
+
+               $mockQuery = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_Persistence_Query'), array('dummy'), array('Foo_Class_Name'));
+               $mockQuery->_set('reflectionService', $mockReflectionService);
+               $this->assertEquals(FALSE, $mockQuery->_call('queryTypeHasProperty', 'unknownProperty'));
+       }
+
+       /**
+        * @test
+        */
+       public function itCanBeTestedIfTheQueryTypeHasAGivenPropertyForAKnownProperty() {
+               $mockClassSchema = $this->getMock('Tx_Extbase_Reflection_ClassSchema', array('hasProperty'), array(), '', FALSE);
+               $mockClassSchema->expects($this->once())->method('hasProperty')->with($this->equalTo('knownProperty'))->will($this->returnValue(TRUE));
+
+               $mockReflectionService = $this->getMock('Tx_Extbase_Reflection_Service', array('getClassSchema'), array(), '', FALSE);
+               $mockReflectionService->expects($this->once())->method('getClassSchema')->with($this->equalTo('Foo_Class_Name'))->will($this->returnValue($mockClassSchema));
+
+               $mockQuery = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_Persistence_Query'), array('dummy'), array('Foo_Class_Name'));
+               $mockQuery->_set('reflectionService', $mockReflectionService);
+               $this->assertEquals(TRUE, $mockQuery->_call('queryTypeHasProperty', 'knownProperty'));
+       }
+
+       /**
+        * dataProvider for methodNames
+        */
+       public function methodNames() {
+               return array(
+                       'equals' => array('equals'),
+                       'like' => array('like'),
+                       'contains' => array('contains'),
+                       'in' => array('in'),
+                       'lessThan' => array('lessThan'),
+                       'lessThanOrEqual' => array('lessThanOrEqual'),
+                       'greaterThan' => array('greaterThan'),
+                       'greaterThanOrEqual' => array('greaterThanOrEqual')
+                       );
+       }
+
+       /**
+        * @test
+        * @dataProvider methodNames
+        * @expectedException Tx_Extbase_Persistence_Exception_UnknownProperty
+        */
+       public function anExceptionIsThrownIfAGivenPropertyIsNotAvailable($methodName) {
+               $mockQomFactory = $this->getMock('Tx_Extbase_Persistence_QOM_QueryObjectModelFactory', array('dummy'), array(), '', FALSE);
+
+               $mockQuery = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_Persistence_Query'), array('queryTypeHasProperty', 'getSelectorName'), array(), '', FALSE);
+               $mockQuery->_set('qomFactory', $mockQomFactory);
+               $mockQuery->expects($this->once())->method('queryTypeHasProperty')->with($this->equalTo('unknownProperty'))->will($this->returnValue(FALSE));
+               $mockQuery->$methodName('unknownProperty', array());
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_Persistence_Exception_UnknownProperty
+        */
+       public function anExceptionIsThrownIfAPropertyOfGivenOrderingsIsNotAvailable() {
+               $mockQomFactory = $this->getMock('Tx_Extbase_Persistence_QOM_QueryObjectModelFactory', array('dummy'), array(), '', FALSE);
+
+               $mockQuery = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_Persistence_Query'), array('queryTypeHasProperty', 'getSelectorName'), array(), '', FALSE);
+               $mockQuery->_set('qomFactory', $mockQomFactory);
+               $mockQuery->expects($this->at(0))->method('queryTypeHasProperty')->with($this->equalTo('knownProperty'))->will($this->returnValue(TRUE));
+               $mockQuery->expects($this->at(1))->method('queryTypeHasProperty')->with($this->equalTo('unknownProperty'))->will($this->returnValue(FALSE));
+               $mockQuery->setOrderings(array('knownProperty' => 'ASC', 'unknownProperty' => 'ASC'));
+       }
+
 }
 ?>
\ No newline at end of file