Differentiate the source or destination of a column when converting values

refs #8826
This commit is contained in:
Johannes Meyer 2015-05-29 11:32:15 +02:00
parent 60ce78c958
commit bb285db05b
5 changed files with 90 additions and 36 deletions

View File

@ -76,11 +76,15 @@ class DbUserBackend extends DbRepository implements UserBackendInterface
); );
/** /**
* The value conversion rules to apply on a query/statement * The value conversion rules to apply on a query or statement
* *
* @var array * @var array
*/ */
protected $conversionRules = array('password'); protected $conversionRules = array(
'user' => array(
'password'
)
);
/** /**
* Initialize this database user backend * Initialize this database user backend

View File

@ -35,14 +35,16 @@ class IniUserGroupBackend extends IniRepository implements UserGroupBackendInter
protected $filterColumns = array('group'); protected $filterColumns = array('group');
/** /**
* The value conversion rules to apply on a query * The value conversion rules to apply on a query or statement
* *
* @var array * @var array
*/ */
protected $conversionRules = array( protected $conversionRules = array(
'groups' => array(
'created_at' => 'date_time', 'created_at' => 'date_time',
'last_modified' => 'date_time', 'last_modified' => 'date_time',
'users' => 'comma_separated_string' 'users' => 'comma_separated_string'
)
); );
/** /**

View File

@ -401,6 +401,46 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
} }
} }
/**
* Return whether this repository is capable of converting values for the given table
*
* @param array|string $table
*
* @return bool
*/
public function providesValueConversion($table)
{
return parent::providesValueConversion($this->removeTablePrefix($this->clearTableAlias($table)));
}
/**
* Return the name of the conversion method for the given alias or column name and context
*
* @param array|string $table The datasource's table
* @param string $name The alias or column name for which to return a conversion method
* @param string $context The context of the conversion: persist or retrieve
*
* @return string
*
* @throws ProgrammingError In case a conversion rule is found but not any conversion method
*/
protected function getConverter($table, $name, $context)
{
if (
$this->validateQueryColumnAssociation($table, $name)
|| $this->validateStatementColumnAssociation($table, $name)
) {
$table = $this->removeTablePrefix($this->clearTableAlias($table));
} else {
$table = $this->findTableName($name);
if (! $table) {
throw new ProgrammingError('Column name validation seems to have failed. Did you require the column?');
}
}
return parent::getConverter($table, $name, $context);
}
/** /**
* Validate that the requested table exists * Validate that the requested table exists
* *
@ -692,6 +732,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
return $aliasTableMap[$column]; return $aliasTableMap[$column];
} }
// TODO(jom): Elaborate whether it makes sense to throw ProgrammingError
// instead (duplicate aliases in different tables?)
foreach ($aliasTableMap as $alias => $table) { foreach ($aliasTableMap as $alias => $table) {
if (strpos($alias, '.') !== false) { if (strpos($alias, '.') !== false) {
list($_, $alias) = split('.', $column, 2); list($_, $alias) = split('.', $column, 2);

View File

@ -106,7 +106,7 @@ abstract class Repository implements Selectable
protected $sortRules; protected $sortRules;
/** /**
* The value conversion rules to apply on a query * The value conversion rules to apply on a query or statement
* *
* This may be initialized by concrete repository implementations and describes for which aliases or column * This may be initialized by concrete repository implementations and describes for which aliases or column
* names what type of conversion is available. For entries, where the key is the alias/column and the value * names what type of conversion is available. For entries, where the key is the alias/column and the value
@ -403,27 +403,30 @@ abstract class Repository implements Selectable
} }
/** /**
* Return whether this repository is capable of converting values * Return whether this repository is capable of converting values for the given table
*
* @param string $table
* *
* @return bool * @return bool
*/ */
public function providesValueConversion() public function providesValueConversion($table)
{ {
$conversionRules = $this->getConversionRules(); $conversionRules = $this->getConversionRules();
return !empty($conversionRules); return !empty($conversionRules) && isset($conversionRules[$table]);
} }
/** /**
* Convert a value supposed to be transmitted to the data source * Convert a value supposed to be transmitted to the data source
* *
* @param string $table The table where to persist the value
* @param string $name The alias or column name * @param string $name The alias or column name
* @param mixed $value The value to convert * @param mixed $value The value to convert
* *
* @return mixed If conversion was possible, the converted value, otherwise the unchanged value * @return mixed If conversion was possible, the converted value, otherwise the unchanged value
*/ */
public function persistColumn($name, $value) public function persistColumn($table, $name, $value)
{ {
$converter = $this->getConverter($name, 'persist'); $converter = $this->getConverter($table, $name, 'persist');
if ($converter !== null) { if ($converter !== null) {
$value = $this->$converter($value); $value = $this->$converter($value);
} }
@ -434,14 +437,15 @@ abstract class Repository implements Selectable
/** /**
* Convert a value which was fetched from the data source * Convert a value which was fetched from the data source
* *
* @param string $table The table the value has been fetched from
* @param string $name The alias or column name * @param string $name The alias or column name
* @param mixed $value The value to convert * @param mixed $value The value to convert
* *
* @return mixed If conversion was possible, the converted value, otherwise the unchanged value * @return mixed If conversion was possible, the converted value, otherwise the unchanged value
*/ */
public function retrieveColumn($name, $value) public function retrieveColumn($table, $name, $value)
{ {
$converter = $this->getConverter($name, 'retrieve'); $converter = $this->getConverter($table, $name, 'retrieve');
if ($converter !== null) { if ($converter !== null) {
$value = $this->$converter($value); $value = $this->$converter($value);
} }
@ -452,6 +456,7 @@ abstract class Repository implements Selectable
/** /**
* Return the name of the conversion method for the given alias or column name and context * Return the name of the conversion method for the given alias or column name and context
* *
* @param string $table The datasource's table
* @param string $name The alias or column name for which to return a conversion method * @param string $name The alias or column name for which to return a conversion method
* @param string $context The context of the conversion: persist or retrieve * @param string $context The context of the conversion: persist or retrieve
* *
@ -459,12 +464,13 @@ abstract class Repository implements Selectable
* *
* @throws ProgrammingError In case a conversion rule is found but not any conversion method * @throws ProgrammingError In case a conversion rule is found but not any conversion method
*/ */
protected function getConverter($name, $context) protected function getConverter($table, $name, $context)
{ {
$conversionRules = $this->getConversionRules(); $conversionRules = $this->getConversionRules();
$tableRules = $conversionRules[$table];
// Check for a conversion method for the alias/column first // Check for a conversion method for the alias/column first
if (array_key_exists($name, $conversionRules) || in_array($name, $conversionRules)) { if (array_key_exists($name, $tableRules) || in_array($name, $tableRules)) {
$methodName = $context . join('', array_map('ucfirst', explode('_', $name))); $methodName = $context . join('', array_map('ucfirst', explode('_', $name)));
if (method_exists($this, $methodName)) { if (method_exists($this, $methodName)) {
return $methodName; return $methodName;
@ -472,22 +478,22 @@ abstract class Repository implements Selectable
} }
// The conversion method for the type is just a fallback, but it is required to exist if defined // The conversion method for the type is just a fallback, but it is required to exist if defined
if (isset($conversionRules[$name])) { if (isset($tableRules[$name])) {
$identifier = join('', array_map('ucfirst', explode('_', $conversionRules[$name]))); $identifier = join('', array_map('ucfirst', explode('_', $tableRules[$name])));
if (! method_exists($this, $context . $identifier)) { if (! method_exists($this, $context . $identifier)) {
// Do not throw an error in case at least one conversion method exists // Do not throw an error in case at least one conversion method exists
if (! method_exists($this, ($context === 'persist' ? 'retrieve' : 'persist') . $identifier)) { if (! method_exists($this, ($context === 'persist' ? 'retrieve' : 'persist') . $identifier)) {
throw new ProgrammingError( throw new ProgrammingError(
'Cannot find any conversion method for type "%s"' 'Cannot find any conversion method for type "%s"'
. '. Add a proper conversion method or remove the type definition', . '. Add a proper conversion method or remove the type definition',
$conversionRules[$name] $tableRules[$name]
); );
} }
Logger::debug( Logger::debug(
'Conversion method "%s" for type definition "%s" does not exist in repository "%s".', 'Conversion method "%s" for type definition "%s" does not exist in repository "%s".',
$context . $identifier, $context . $identifier,
$conversionRules[$name], $tableRules[$name],
$this->getName() $this->getName()
); );
} else { } else {
@ -623,7 +629,7 @@ abstract class Repository implements Selectable
if ($filter->isExpression()) { if ($filter->isExpression()) {
$column = $filter->getColumn(); $column = $filter->getColumn();
$filter->setColumn($this->requireFilterColumn($table, $column, $query)); $filter->setColumn($this->requireFilterColumn($table, $column, $query));
$filter->setExpression($this->persistColumn($column, $filter->getExpression())); $filter->setExpression($this->persistColumn($table, $column, $filter->getExpression()));
} elseif ($filter->isChain()) { } elseif ($filter->isChain()) {
foreach ($filter->filters() as $chainOrExpression) { foreach ($filter->filters() as $chainOrExpression) {
$this->requireFilter($table, $chainOrExpression, $query); $this->requireFilter($table, $chainOrExpression, $query);
@ -826,7 +832,7 @@ abstract class Repository implements Selectable
{ {
$resolved = array(); $resolved = array();
foreach ($data as $alias => $value) { foreach ($data as $alias => $value) {
$resolved[$this->requireStatementColumn($table, $alias)] = $this->persistColumn($alias, $value); $resolved[$this->requireStatementColumn($table, $alias)] = $this->persistColumn($table, $alias, $value);
} }
return $resolved; return $resolved;

View File

@ -153,7 +153,7 @@ class RepositoryQuery implements QueryInterface, Iterator
{ {
$this->query->where( $this->query->where(
$this->repository->requireFilterColumn($this->target, $column, $this), $this->repository->requireFilterColumn($this->target, $column, $this),
$this->repository->persistColumn($column, $value) $this->repository->persistColumn($this->target, $column, $value)
); );
return $this; return $this;
} }
@ -388,10 +388,10 @@ class RepositoryQuery implements QueryInterface, Iterator
} }
$result = $this->query->fetchOne(); $result = $this->query->fetchOne();
if ($result !== false && $this->repository->providesValueConversion()) { if ($result !== false && $this->repository->providesValueConversion($this->target)) {
$columns = $this->getColumns(); $columns = $this->getColumns();
$column = isset($columns[0]) ? $columns[0] : key($columns); $column = isset($columns[0]) ? $columns[0] : key($columns);
return $this->repository->retrieveColumn($column, $result); return $this->repository->retrieveColumn($this->target, $column, $result);
} }
return $result; return $result;
@ -409,13 +409,13 @@ class RepositoryQuery implements QueryInterface, Iterator
} }
$result = $this->query->fetchRow(); $result = $this->query->fetchRow();
if ($result !== false && $this->repository->providesValueConversion()) { if ($result !== false && $this->repository->providesValueConversion($this->target)) {
foreach ($this->getColumns() as $alias => $column) { foreach ($this->getColumns() as $alias => $column) {
if (! is_string($alias)) { if (! is_string($alias)) {
$alias = $column; $alias = $column;
} }
$result->$alias = $this->repository->retrieveColumn($alias, $result->$alias); $result->$alias = $this->repository->retrieveColumn($this->target, $alias, $result->$alias);
} }
} }
@ -434,12 +434,12 @@ class RepositoryQuery implements QueryInterface, Iterator
} }
$results = $this->query->fetchColumn(); $results = $this->query->fetchColumn();
if (! empty($results) && $this->repository->providesValueConversion()) { if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
$columns = $this->getColumns(); $columns = $this->getColumns();
$aliases = array_keys($columns); $aliases = array_keys($columns);
$column = is_int($aliases[0]) ? $columns[0] : $aliases[0]; $column = is_int($aliases[0]) ? $columns[0] : $aliases[0];
foreach ($results as & $value) { foreach ($results as & $value) {
$value = $this->repository->retrieveColumn($column, $value); $value = $this->repository->retrieveColumn($this->target, $column, $value);
} }
} }
@ -460,15 +460,15 @@ class RepositoryQuery implements QueryInterface, Iterator
} }
$results = $this->query->fetchPairs(); $results = $this->query->fetchPairs();
if (! empty($results) && $this->repository->providesValueConversion()) { if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
$columns = $this->getColumns(); $columns = $this->getColumns();
$aliases = array_keys($columns); $aliases = array_keys($columns);
$newResults = array(); $newResults = array();
foreach ($results as $colOneValue => $colTwoValue) { foreach ($results as $colOneValue => $colTwoValue) {
$colOne = $aliases[0] !== 0 ? $aliases[0] : $columns[0]; $colOne = $aliases[0] !== 0 ? $aliases[0] : $columns[0];
$colTwo = count($aliases) < 2 ? $colOne : ($aliases[1] !== 1 ? $aliases[1] : $columns[1]); $colTwo = count($aliases) < 2 ? $colOne : ($aliases[1] !== 1 ? $aliases[1] : $columns[1]);
$colOneValue = $this->repository->retrieveColumn($colOne, $colOneValue); $colOneValue = $this->repository->retrieveColumn($this->target, $colOne, $colOneValue);
$newResults[$colOneValue] = $this->repository->retrieveColumn($colTwo, $colTwoValue); $newResults[$colOneValue] = $this->repository->retrieveColumn($this->target, $colTwo, $colTwoValue);
} }
$results = $newResults; $results = $newResults;
@ -489,7 +489,7 @@ class RepositoryQuery implements QueryInterface, Iterator
} }
$results = $this->query->fetchAll(); $results = $this->query->fetchAll();
if (! empty($results) && $this->repository->providesValueConversion()) { if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
$columns = $this->getColumns(); $columns = $this->getColumns();
foreach ($results as $row) { foreach ($results as $row) {
foreach ($columns as $alias => $column) { foreach ($columns as $alias => $column) {
@ -497,7 +497,7 @@ class RepositoryQuery implements QueryInterface, Iterator
$alias = $column; $alias = $column;
} }
$row->$alias = $this->repository->retrieveColumn($alias, $row->$alias); $row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias);
} }
} }
} }
@ -545,13 +545,13 @@ class RepositoryQuery implements QueryInterface, Iterator
public function current() public function current()
{ {
$row = $this->iterator->current(); $row = $this->iterator->current();
if ($this->repository->providesValueConversion()) { if ($this->repository->providesValueConversion($this->target)) {
foreach ($this->getColumns() as $alias => $column) { foreach ($this->getColumns() as $alias => $column) {
if (! is_string($alias)) { if (! is_string($alias)) {
$alias = $column; $alias = $column;
} }
$row->$alias = $this->repository->retrieveColumn($alias, $row->$alias); $row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias);
} }
} }