Skip to content

Commit

Permalink
[+BUGFIX] Extbase (Reflection): Fixed a bug that would occur if an ar…
Browse files Browse the repository at this point in the history
…ray with a non-existing key would be passed to ObjectAccess::getProperty(). Related to #6073.

[+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.
  • Loading branch information
Jochen Rau committed Mar 15, 2010
1 parent 5753d51 commit d64ba0c
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 116 deletions.
8 changes: 6 additions & 2 deletions typo3/sysext/extbase/Classes/Persistence/Mapper/DataMap.php
Expand Up @@ -594,7 +594,11 @@ public function convertFieldValueToPropertyValue($type, $string) {
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:
Expand All @@ -613,7 +617,7 @@ public function convertPropertyValueToFieldValue($propertyValue) {
$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;
Expand Down
111 changes: 35 additions & 76 deletions typo3/sysext/extbase/Classes/Persistence/Query.php
Expand Up @@ -341,97 +341,56 @@ public function getConstraint() {
}

/**
* 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;
}
Expand Down
10 changes: 4 additions & 6 deletions typo3/sysext/extbase/Classes/Persistence/QueryInterface.php
Expand Up @@ -172,22 +172,20 @@ public function matching($constraint);
/**
* 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
Expand Down
Expand Up @@ -276,8 +276,7 @@ public function parseQuery(Tx_Extbase_Persistence_QueryInterface $query, array &
$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);
}
Expand Down Expand Up @@ -397,7 +396,7 @@ protected function parseJoin(Tx_Extbase_Persistence_QOM_JoinInterface $join, arr
}

$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) {
Expand Down Expand Up @@ -594,25 +593,25 @@ protected function addUnionStatement(&$className, &$tableName, &$propertyPath, a
$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);
Expand Down
2 changes: 1 addition & 1 deletion typo3/sysext/extbase/Classes/Property/Mapper.php
Expand Up @@ -253,7 +253,7 @@ protected function transformToObject($propertyValue, $targetType, $propertyName)
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 {
Expand Down
60 changes: 38 additions & 22 deletions typo3/sysext/extbase/Classes/Reflection/ObjectAccess.php
Expand Up @@ -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);
}

/**
Expand All @@ -81,8 +91,11 @@ static public function getProperty($object, $propertyName) {
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;
}
Expand Down Expand Up @@ -162,16 +175,19 @@ static public function getAccessibleProperties($object) {
}
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)));
}

/**
Expand Down

0 comments on commit d64ba0c

Please sign in to comment.