Repository: Add support for client side value conversion

refs #8826
This commit is contained in:
Johannes Meyer 2015-05-07 14:49:13 +02:00
parent f83d16acb2
commit f383ddd00a
2 changed files with 209 additions and 9 deletions

View File

@ -3,6 +3,7 @@
namespace Icinga\Repository;
use Icinga\Application\Logger;
use Icinga\Data\Selectable;
use Icinga\Exception\ProgrammingError;
use Icinga\Exception\QueryException;
@ -95,6 +96,21 @@ abstract class Repository implements Selectable
*/
protected $sortRules;
/**
* The value conversion rules to apply on a query
*
* 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
* is the type identifier, the repository attempts to find a conversion method for the alias/column first and,
* if none is found, then for the type. If an entry only provides a value, which is the alias/column, the
* repository only attempts to find a conversion method for the alias/column. The name of a conversion method
* is expected to be declared using lowerCamelCase. (e.g. user_name will be translated to persistUserName and
* groupname will be translated to retrieveGroupname)
*
* @var array
*/
protected $conversionRules;
/**
* An array to map table names to aliases
*
@ -268,6 +284,32 @@ abstract class Repository implements Selectable
return array();
}
/**
* Return the value conversion rules to apply on a query
*
* Calls $this->initializeConversionRules() in case $this->conversionRules is null.
*
* @return array
*/
public function getConversionRules()
{
if ($this->conversionRules === null) {
$this->conversionRules = $this->initializeConversionRules();
}
return $this->conversionRules;
}
/**
* Overwrite this in your repository implementation in case you need to initialize the conversion rules lazily
*
* @return array
*/
protected function initializeConversionRules()
{
return array();
}
/**
* Return an array to map table names to aliases
*
@ -335,6 +377,100 @@ abstract class Repository implements Selectable
return $query;
}
/**
* Return whether this repository is capable of converting values
*
* @return bool
*/
public function providesValueConversion()
{
$conversionRules = $this->getConversionRules();
return !empty($conversionRules);
}
/**
* Convert a value supposed to be transmitted to the data source
*
* @param string $name The alias or column name
* @param mixed $value The value to convert
*
* @return mixed If conversion was possible, the converted value, otherwise the unchanged value
*/
public function persistColumn($name, $value)
{
$converter = $this->getConverter($name, 'persist');
if ($converter !== null) {
$value = $this->$converter($value);
}
return $value;
}
/**
* Convert a value which was fetched from the data source
*
* @param string $name The alias or column name
* @param mixed $value The value to convert
*
* @return mixed If conversion was possible, the converted value, otherwise the unchanged value
*/
public function retrieveColumn($name, $value)
{
$converter = $this->getConverter($name, 'retrieve');
if ($converter !== null) {
$value = $this->$converter($value);
}
return $value;
}
/**
* Return the name of the conversion method for the given alias or column name and context
*
* @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($name, $context)
{
$conversionRules = $this->getConversionRules();
// Check for a conversion method for the alias/column first
if (array_key_exists($name, $conversionRules) || in_array($name, $conversionRules)) {
$methodName = $context . join('', array_map('ucfirst', explode('_', $name)));
if (method_exists($this, $methodName)) {
return $methodName;
}
}
// The conversion method for the type is just a fallback, but it is required to exist if defined
if (isset($conversionRules[$name])) {
$identifier = join('', array_map('ucfirst', explode('_', $conversionRules[$name])));
if (! method_exists($this, $context . $identifier)) {
// Do not throw an error in case at least one conversion method exists
if (! method_exists($this, ($context === 'persist' ? 'retrieve' : 'persist') . $identifier)) {
throw new ProgrammingError(
'Cannot find any conversion method for type "%s"'
. '. Add a proper conversion method or remove the type definition',
$conversionRules[$name]
);
}
Logger::debug(
'Conversion method "%s" for type definition "%s" does not exist in repository "%s".',
$context . $identifier,
$conversionRules[$name],
$this->getName()
);
} else {
return $context . $identifier;
}
}
}
/**
* Return this repository's query columns mapped to their respective aliases
*

View File

@ -132,7 +132,10 @@ class RepositoryQuery implements QueryInterface
*/
public function where($column, $value = null)
{
$this->query->where($this->repository->requireFilterColumn($column), $value);
$this->query->where(
$this->repository->requireFilterColumn($column),
$this->repository->persistColumn($column, $value)
);
return $this;
}
@ -182,13 +185,15 @@ class RepositoryQuery implements QueryInterface
return $this;
}
/*+
/**
* Recurse the given filter and notify the repository about each required filter column
*/
protected function requireFilterColumns(Filter $filter)
{
if ($filter->isExpression()) {
$filter->setColumn($this->repository->requireFilterColumn($filter->getColumn()));
$column = $filter->getColumn();
$filter->setColumn($this->repository->requireFilterColumn($column));
$filter->setExpression($this->repository->persistColumn($column, $filter->getExpression()));
} elseif ($filter->isChain()) {
foreach ($filter->filters() as $chainOrExpression) {
$this->requireFilterColumns($chainOrExpression);
@ -392,7 +397,14 @@ class RepositoryQuery implements QueryInterface
$this->order();
}
return $this->query->fetchOne();
$result = $this->query->fetchOne();
if ($this->repository->providesValueConversion()) {
$columns = $this->getColumns();
$column = isset($columns[0]) ? $columns[0] : key($columns);
return $this->repository->retrieveColumn($column, $result);
}
return $result;
}
/**
@ -406,7 +418,18 @@ class RepositoryQuery implements QueryInterface
$this->order();
}
return $this->query->fetchRow();
$result = $this->query->fetchRow();
if ($this->repository->providesValueConversion()) {
foreach ($this->getColumns() as $alias => $column) {
if (! is_string($alias)) {
$alias = $column;
}
$result->$alias = $this->repository->retrieveColumn($alias, $result->$alias);
}
}
return $result;
}
/**
@ -422,11 +445,23 @@ class RepositoryQuery implements QueryInterface
$this->order();
}
return $this->query->fetchColumn($columnIndex);
$results = $this->query->fetchColumn($columnIndex);
if ($this->repository->providesValueConversion()) {
$columns = $this->getColumns();
$aliases = array_keys($columns);
$column = is_int($aliases[$columnIndex]) ? $columns[$columnIndex] : $aliases[$columnIndex];
foreach ($results as & $value) {
$value = $this->repository->retrieveColumn($column, $value);
}
}
return $results;
}
/**
* Fetch and return all rows of this query's result as a flattened key/value based array
* Fetch and return all rows of this query's result set as an array of key-value pairs
*
* The first column is the key, the second column is the value.
*
* @return array
*/
@ -436,7 +471,22 @@ class RepositoryQuery implements QueryInterface
$this->order();
}
return $this->query->fetchPairs();
$results = $this->query->fetchPairs();
if ($this->repository->providesValueConversion()) {
$columns = $this->getColumns();
$aliases = array_keys($columns);
$newResults = array();
foreach ($results as $colOneValue => $colTwoValue) {
$colOne = $aliases[0] !== 0 ? $aliases[0] : $columns[0];
$colTwo = count($aliases) < 2 ? $colOne : ($aliases[1] !== 1 ? $aliases[1] : $columns[1]);
$colOneValue = $this->repository->retrieveColumn($colOne, $colOneValue);
$newResults[$colOneValue] = $this->repository->retrieveColumn($colTwo, $colTwoValue);
}
$results = $newResults;
}
return $results;
}
/**
@ -450,7 +500,21 @@ class RepositoryQuery implements QueryInterface
$this->order();
}
return $this->query->fetchAll();
$results = $this->query->fetchAll();
if ($this->repository->providesValueConversion()) {
$columns = $this->getColumns();
foreach ($results as $row) {
foreach ($columns as $alias => $column) {
if (! is_string($alias)) {
$alias = $column;
}
$row->$alias = $this->repository->retrieveColumn($alias, $row->$alias);
}
}
}
return $results;
}
/**