[+BUGFIX] Extbase (Reflection): Fixed a bug that would occur if an array with a non...
authorJochen Rau <j.rau@web.de>
Mon, 15 Mar 2010 20:53:40 +0000 (20:53 +0000)
committerJochen Rau <j.rau@web.de>
Mon, 15 Mar 2010 20:53:40 +0000 (20:53 +0000)
[+BUGFIX] Extbase (Reflection): ObjectAccess::getProperty() now throws an exception when a property does not exist, fixes #6005.
[~TASK] Extbase (Persistence): Removed implodeAnd() and implodeOr(). You can pass an array of constraints as an argument to logicalAnd() and logicalOr(), or you can pass one or more constraints directly as reguments. Resolves #6735.
[~TASK] Extbase (Persistence): The unions are now translated into LEFT JOIN instead of INNER JOIN. And there is no pid constraint added for (internal) unions anymore. Related to #6735.
[~TASK] Extbase: Empty DateTime field values are now translated into NULL as property value (0->NULL). This is necessary because of the way TYPO3 4.x treats 0 as a "special" timestamp. The same is with incoming values which are not accepted as constructor argument of a DateTime object.

typo3/sysext/extbase/Classes/Persistence/Mapper/DataMap.php
typo3/sysext/extbase/Classes/Persistence/Query.php
typo3/sysext/extbase/Classes/Persistence/QueryInterface.php
typo3/sysext/extbase/Classes/Persistence/Storage/Typo3DbBackend.php
typo3/sysext/extbase/Classes/Property/Mapper.php
typo3/sysext/extbase/Classes/Reflection/ObjectAccess.php

index d483d09..a0223a8 100644 (file)
@@ -594,7 +594,11 @@ class Tx_Extbase_Persistence_Mapper_DataMap {
                        case Tx_Extbase_Persistence_PropertyType::DECIMAL:
                                return (float) $string;
                        case Tx_Extbase_Persistence_PropertyType::DATE:
-                               return new DateTime(date('r', $string));
+                               if (empty($string)) { // 0 -> NULL !!!
+                                       return NULL;
+                               } else {
+                                       return new DateTime(date('c', $string));
+                               }
                        case Tx_Extbase_Persistence_PropertyType::BOOLEAN:
                                return (boolean) $string;
                        default:
@@ -613,7 +617,7 @@ class Tx_Extbase_Persistence_Mapper_DataMap {
                        $convertedValue = $propertyValue ? 1 : 0;
                } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
                        $convertedValue = $propertyValue->getUid();
-               } elseif (is_a($propertyValue, 'DateTime')) {
+               } elseif ($propertyValue instanceof DateTime) {
                        $convertedValue = $propertyValue->format('U');
                } elseif (is_int($propertyValue)) {
                        $convertedValue = $propertyValue;
index c294db3..e4c5eef 100644 (file)
@@ -341,97 +341,56 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
        }
 
        /**
-        * Performs a logical conjunction of the given constraints.
+        * Performs a logical conjunction of the given constraints. The method takes one or more contraints and concatenates them with a boolean AND.
+        * It also scepts a single array of constraints to be concatenated.
         *
-        * @param object $constraint1 First constraint
-        * @param object $constraint2 Second constraint
+        * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
         * @return Tx_Extbase_Persistence_QOM_AndInterface
-        * @api Passing more than two constraints as arguments is currently not yet conform to the api of FLOW3
+        * @api
         */
-       public function logicalAnd($constraint1, $constraint2) {
-               $constraints = func_get_args();
-               $numberOfConstraints = func_num_args();
-               if (func_num_args() > 1) {
-                       $resultingConstraint = array_shift($constraints);
-                       foreach ($constraints as $constraint) {
-                               $resultingConstraint = $this->qomFactory->_and(
-                                       $resultingConstraint,
-                                       $constraint
-                                       );
-                       }
+       public function logicalAnd($constraint1) {
+               if (is_array($constraint1)) {
+                       $resultingConstraint = array_shift($constraint1);
+                       $constraints = $constraint1;
                } else {
-                       throw new Tx_Extbase_Persistence_Exception_InvalidNumberOfConstraints('There must be at least two constraints. Got only ' . func_num_args() . '.', 1268056288);
-               }
-               return $resultingConstraint;
-       }
-
-       /**
-        * Performs a logical conjunction of the given constraints.
-        *
-        * @param array $constraints An array of constraints
-        * @return Tx_Extbase_Persistence_QOM_AndInterface
-        */
-       public function implodeAnd(array $constraints) {
-               $numberOfConstraints = count($constraints);
-               $resultingConstraint = NULL;
-               if ($numberOfConstraints === 1) {
-                       return array_shift($constraints);
-               } elseif ($numberOfConstraints > 1) {
+                       $constraints = func_get_args();
                        $resultingConstraint = array_shift($constraints);
-                       foreach ($constraints as $constraint) {
-                               $resultingConstraint = $this->qomFactory->_and(
-                                       $resultingConstraint,
-                                       $constraint
-                                       );
-                       }
+               }
+               if ($resultingConstraint === NULL) {
+                       throw new Tx_Extbase_Persistence_Exception_InvalidNumberOfConstraints('There must be at least one constraint or a non-empty array of constraints given.', 1268056288);
+               }
+               foreach ($constraints as $constraint) {
+                       $resultingConstraint = $this->qomFactory->_and(
+                               $resultingConstraint,
+                               $constraint
+                               );
                }
                return $resultingConstraint;
        }
-
+       
        /**
         * Performs a logical disjunction of the two given constraints
         *
-        * @param object $constraint1 First constraint
-        * @param object $constraint2 Second constraint
+        * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
         * @return Tx_Extbase_Persistence_QOM_OrInterface
-        * @api Passing more than two constraints as arguments is currently not yet conform to the api of FLOW3
+        * @api
         */
-       public function logicalOr($constraint1, $constraint2) {
-               $constraints = func_get_args();
-               $numberOfConstraints = func_num_args();
-               if (func_num_args() > 1) {
-                       $resultingConstraint = array_shift($constraints);
-                       foreach ($constraints as $constraint) {
-                               $resultingConstraint = $this->qomFactory->_or(
-                                       $resultingConstraint,
-                                       $constraint
-                                       );
-                       }
+       public function logicalOr($constraint1) {
+               if (is_array($constraint1)) {
+                       $resultingConstraint = array_shift($constraint1);
+                       $constraints = $constraint1;
                } else {
-                       throw new Tx_Extbase_Persistence_Exception_InvalidNumberOfConstraints('There must be at least two constraints. Got only ' . func_num_args() . '.', 1268056288);
-               }
-               return $resultingConstraint;
-       }
-
-       /**
-        * Performs a logical conjunction of the given constraints.
-        *
-        * @param array $constraints An array of constraints
-        * @return Tx_Extbase_Persistence_QOM_AndInterface
-        */
-       public function implodeOr(array $constraints) {
-               $numberOfConstraints = count($constraints);
-               $resultingConstraint = NULL;
-               if ($numberOfConstraints === 1) {
-                       return array_shift($constraints);
-               } elseif ($numberOfConstraints > 1) {
+                       $constraints = func_get_args();
                        $resultingConstraint = array_shift($constraints);
-                       foreach ($constraints as $constraint) {
-                               $resultingConstraint = $this->qomFactory->_or(
-                                       $resultingConstraint,
-                                       $constraint
-                                       );
-                       }
+               }
+               if ($resultingConstraint === NULL) {
+                       throw new Tx_Extbase_Persistence_Exception_InvalidNumberOfConstraints('There must be at least one constraint or a non-empty array of constraints given.', 1268056288);
+               }
+               foreach ($constraints as $constraint) {
+                       $resultingConstraint = $this->qomFactory->_or(
+                               $resultingConstraint,
+                               $constraint
+                               );
                }
                return $resultingConstraint;
        }
index 67018e1..47c91a5 100644 (file)
@@ -172,22 +172,20 @@ interface Tx_Extbase_Persistence_QueryInterface {
        /**
         * Performs a logical conjunction of the two given constraints.
         *
-        * @param object $constraint1 First constraint
-        * @param object $constraint2 Second constraint
+        * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
         * @return object
         * @api
         */
-       public function logicalAnd($constraint1, $constraint2);
+       public function logicalAnd($constraint1);
 
        /**
         * Performs a logical disjunction of the two given constraints
         *
-        * @param object $constraint1 First constraint
-        * @param object $constraint2 Second constraint
+        * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
         * @return object
         * @api
         */
-       public function logicalOr($constraint1, $constraint2);
+       public function logicalOr($constraint1);
 
        /**
         * Performs a logical negation of the given constraint
index 5496090..9b631b0 100644 (file)
@@ -276,8 +276,7 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                $this->parseOrderings($query->getOrderings(), $source, $sql);
                $this->parseLimitAndOffset($query->getLimit(), $query->getOffset(), $sql);
 
-               $tables = array_merge(array_keys($sql['tables']), array_keys($sql['unions']));
-               foreach ($tables as $tableName) {
+               foreach (array_keys($sql['tables']) as $tableName) {
                        if (is_string($tableName) && strlen($tableName) > 0) {
                                $this->addAdditionalWhereClause($query->getQuerySettings(), $tableName, $sql);
                        }
@@ -397,7 +396,7 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                }
                
                $sql['tables'][$leftTableName] = $leftTableName;
-               $sql['unions'][$rightTableName] = 'INNER JOIN ' . $rightTableName;
+               $sql['unions'][$rightTableName] = 'LEFT JOIN ' . $rightTableName;
 
                $joinCondition = $join->getJoinCondition();
                if ($joinCondition instanceof Tx_Extbase_Persistence_QOM_EquiJoinCondition) {
@@ -594,25 +593,25 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                $childTableName = $columnMap->getChildTableName();
                if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
                        if (isset($parentKeyFieldName)) {
-                               $sql['unions'][$childTableName] = 'INNER JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
+                               $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
                        } else {
-                               $sql['unions'][$childTableName] = 'INNER JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $columnName . '=' . $childTableName . '.uid';
+                               $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $columnName . '=' . $childTableName . '.uid';
                        }
                        $className = $this->dataMapper->getType($className, $propertyName);
                } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
                        if (isset($parentKeyFieldName)) {
-                               $sql['unions'][$childTableName] = 'INNER JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
+                               $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
                        } else {
                                $onStatement = '(' . $tableName . '.' . $columnName . ' LIKE CONCAT(\'%,\',' . $childTableName . '.uid,\',%\')';
                                $onStatement .= ' OR ' . $tableName . '.' . $columnName . ' LIKE CONCAT(\'%,\',' . $childTableName . '.uid)';
                                $onStatement .= ' OR ' . $tableName . '.' . $columnName . ' LIKE CONCAT(' . $childTableName . '.uid,\',%\'))';
-                               $sql['unions'][$childTableName] = 'INNER JOIN ' . $childTableName . ' ON ' . $onStatement;
+                               $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $onStatement;
                        }
                        $className = $this->dataMapper->getElementType($className, $propertyName);
                } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
                        $relationTableName = $columnMap->getRelationTableName();
-                       $sql['unions'][$relationTableName] = 'INNER JOIN ' . $relationTableName . ' ON ' . $tableName . '.uid=' . $relationTableName . '.uid_local';
-                       $sql['unions'][$childTableName] = 'INNER JOIN ' . $childTableName . ' ON ' . $relationTableName . '.uid_foreign=' . $childTableName . '.uid';
+                       $sql['unions'][$relationTableName] = 'LEFT JOIN ' . $relationTableName . ' ON ' . $tableName . '.uid=' . $relationTableName . '.uid_local';
+                       $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $relationTableName . '.uid_foreign=' . $childTableName . '.uid';
                        $className = $this->dataMapper->getElementType($className, $propertyName);
                } else {
                        throw new Tx_Extbase_Persistence_Exception('Could not determine type of relation.', 1252502725);
index 2a01257..5a31bc5 100644 (file)
@@ -253,7 +253,7 @@ class Tx_Extbase_Property_Mapper {
                                try {
                                        $propertyValue = new $targetType($propertyValue);
                                } catch (Exception $e) {
-                                       throw new InvalidArgumentException('Conversion to a ' . $targetType . ' object is not possible. Cause: ' . $e->getMessage(), 1190034628);
+                                       $propertyValue = NULL;
                                }
                        }
                } else {
index 9fb2678..20372c1 100644 (file)
@@ -43,30 +43,40 @@ class Tx_Extbase_Reflection_ObjectAccess {
        /**
         * Get a property of a given object.
         * Tries to get the property the following ways:
+        * - if the target is an array, and has this property, we call it.
+        * - if public getter method exists, call it.
         * - if the target object is an instance of ArrayAccess, it gets the property
         *   on it if it exists.
-        * - if public getter method exists, call it.
         * - if public property exists, return the value of it.
         * - else, throw exception
         *
-        * @param object $object Object to get the property from
+        * @param mixed $subject Object or array to get the property from
         * @param string $propertyName name of the property to retrieve
         * @return object Value of the property.
+        * @throws InvalidArgumentException in case $subject was not an object or $propertyName was not a string
+        * @throws RuntimeException if the property was not accessible
         */
-       static public function getProperty($object, $propertyName) {
-               if (!is_object($object) && !is_array($object)) throw new InvalidArgumentException('$object must be an object or an array, ' . gettype($object). ' given.', 1237301367);
+       static public function getProperty($subject, $propertyName) {
+               if (!is_object($subject) && !is_array($subject)) throw new InvalidArgumentException('$subject must be an object or array, ' . gettype($subject). ' given.', 1237301367);
                if (!is_string($propertyName)) throw new InvalidArgumentException('Given property name is not of type string.', 1231178303);
 
-               if (is_array($object) && array_key_exists($propertyName, $object)) {
-                       return $object[$propertyName];
-               } elseif (is_callable(array($object, $getterMethodName = self::buildGetterMethodName($propertyName)))) {
-                       return call_user_func(array($object, $getterMethodName));
-               } elseif ($object instanceof ArrayAccess && isset($object[$propertyName])) {
-                       return $object[$propertyName];
-               } elseif (is_object($object) && array_key_exists($propertyName, get_object_vars($object))) {
-                       return $object->$propertyName;
+               if (is_array($subject)) {
+                       if (array_key_exists($propertyName, $subject)) {
+                               return $subject[$propertyName];
+                       }
+               } else {
+                       if (is_callable(array($subject, 'get' . ucfirst($propertyName)))) {
+                               return call_user_func(array($subject, 'get' . ucfirst($propertyName)));
+                       } elseif (is_callable(array($subject, 'is' . ucfirst($propertyName)))) {
+                               return call_user_func(array($subject, 'is' . ucfirst($propertyName)));
+                       } elseif ($subject instanceof ArrayAccess && isset($subject[$propertyName])) {
+                               return $subject[$propertyName];
+                       } elseif (array_key_exists($propertyName, get_object_vars($subject))) {
+                               return $subject->$propertyName;
+                       }
                }
-               return NULL;
+
+               throw new RuntimeException('The property "' . $propertyName . '" on the subject was not accessible.', 1263391473);
        }
 
        /**
@@ -81,8 +91,11 @@ class Tx_Extbase_Reflection_ObjectAccess {
        static public function getPropertyPath($object, $propertyPath) {
                $propertyPathSegments = explode('.', $propertyPath);
                foreach ($propertyPathSegments as $pathSegment) {
-                       $object = self::getProperty($object, $pathSegment);
-                       if ($object === NULL) return NULL;
+                       if (is_object($object) && self::isPropertyGettable($object, $pathSegment)) {
+                               $object = self::getProperty($object, $pathSegment);
+                       } else {
+                               return NULL;
+                       }
                }
                return $object;
        }
@@ -162,16 +175,19 @@ class Tx_Extbase_Reflection_ObjectAccess {
                }
                return $properties;
        }
-
+       
        /**
-        * Build the getter method name for a given property by capitalizing the
-        * first letter of the property, and prepending it with "get".
+        * Tells if the value of the specified property can be retrieved by this Object Accessor.
         *
-        * @param string $property Name of the property
-        * @return string Name of the getter method name
+        * @param object $object Object containting the property
+        * @param string $propertyName Name of the property to check
+        * @return boolean
         */
-       static protected function buildGetterMethodName($property) {
-               return 'get' . ucfirst($property);
+       static public function isPropertyGettable($object, $propertyName) {
+               if (!is_object($object)) throw new InvalidArgumentException('$object must be an object, ' . gettype($object). ' given.', 1259828921);
+               if (array_search($propertyName, array_keys(get_class_vars(get_class($object)))) !== FALSE) return TRUE;
+               if (is_callable(array($object, 'get' . ucfirst($propertyName)))) return TRUE;
+               return is_callable(array($object, 'is' . ucfirst($propertyName)));
        }
 
        /**