From a2218de77d2de905f18cae2f9fecd97639f7c9df Mon Sep 17 00:00:00 2001 From: Milan Felix Sulc Date: Mon, 27 Jun 2016 15:57:41 +0200 Subject: [PATCH] Feature: new parameters switching for dibi/nette/pdo (idea by @felix, trolling by @crempa) --- .../Dibi/DataSources/DibiDataSource.php | 21 +++-- .../DataSources/DibiWrapperDataSource.php | 23 ++--- .../MultiDibiWrapperDataSource.php | 25 +++--- .../MultiNetteDatabaseWrapperDataSource.php | 25 +++--- .../DataSources/NetteDatabaseDataSource.php | 23 ++--- .../NetteDatabaseWrapperDataSource.php | 23 ++--- .../AbstractDatabaseDataSource.php | 16 ---- src/DataSources/AbstractMultiDataSource.php | 19 ---- src/DataSources/PdoDataSource.php | 27 +++--- src/Model/Parameters/Parameters.php | 9 ++ src/Utils/Expander.php | 85 ++++++++---------- src/Utils/Switcher.php | 90 +++++++++++++++++++ .../Model/Parameters/ParametersTest.php | 2 +- tests/Report/Utils/ExpanderTest.php | 11 +-- tests/Report/Utils/SwitcherTest.php | 42 +++++++++ 15 files changed, 279 insertions(+), 162 deletions(-) create mode 100644 src/Utils/Switcher.php create mode 100644 tests/Report/Utils/SwitcherTest.php diff --git a/src/Bridges/Dibi/DataSources/DibiDataSource.php b/src/Bridges/Dibi/DataSources/DibiDataSource.php index 584eac3..ace911e 100644 --- a/src/Bridges/Dibi/DataSources/DibiDataSource.php +++ b/src/Bridges/Dibi/DataSources/DibiDataSource.php @@ -46,17 +46,20 @@ public function compile(Parameters $parameters) $sql = $this->getRealSql($parameters); try { - if ($this->isPure()) { - // Expand parameters - $expander = $parameters->createExpander(); - $sql = $expander->expand($sql); - // Execute native query - $resultset = $this->connection->nativeQuery($sql); + // Prepare parameters + if (!$parameters->isEmpty()) { + $switch = $parameters->createSwitcher(); + $switch->setPlaceholder('?'); + // Replace named parameters for ? and return + // accurate sequenced array of arguments + list ($sql, $args) = $switch->execute($sql); } else { - // Execute dibi query - $args = array_values($parameters->toArray()); - $resultset = $this->connection->query($sql, $args); + // Keep empty arguments + $args = []; } + + // Execute dibi query + $resultset = $this->connection->query($sql, $args); } catch (DibiException $e) { throw new SqlException($sql, NULL, $e); } diff --git a/src/Bridges/Dibi/DataSources/DibiWrapperDataSource.php b/src/Bridges/Dibi/DataSources/DibiWrapperDataSource.php index 6ef0df6..b00893e 100644 --- a/src/Bridges/Dibi/DataSources/DibiWrapperDataSource.php +++ b/src/Bridges/Dibi/DataSources/DibiWrapperDataSource.php @@ -6,8 +6,8 @@ use DibiException; use Tlapnet\Report\DataSources\AbstractDatabaseDataSource; use Tlapnet\Report\Exceptions\Runtime\DataSource\SqlException; -use Tlapnet\Report\Model\Result\Result; use Tlapnet\Report\Model\Parameters\Parameters; +use Tlapnet\Report\Model\Result\Result; class DibiWrapperDataSource extends AbstractDatabaseDataSource { @@ -43,17 +43,20 @@ public function compile(Parameters $parameters) $sql = $this->getRealSql($parameters); try { - if ($this->isPure()) { - // Expand parameters - $expander = $parameters->createExpander(); - $sql = $expander->expand($sql); - // Execute native query - $resultset = $this->connection->nativeQuery($sql); + // Prepare parameters + if (!$parameters->isEmpty()) { + $switch = $parameters->createSwitcher(); + $switch->setPlaceholder('?'); + // Replace named parameters for ? and return + // accurate sequenced array of arguments + list ($sql, $args) = $switch->execute($sql); } else { - // Execute dibi query - $args = array_values($parameters->toArray()); - $resultset = $this->connection->query($sql, $args); + // Keep empty arguments + $args = []; } + + // Execute dibi query + $resultset = $this->connection->query($sql, $args); } catch (DibiException $e) { throw new SqlException($sql, NULL, $e); } diff --git a/src/Bridges/Dibi/DataSources/MultiDibiWrapperDataSource.php b/src/Bridges/Dibi/DataSources/MultiDibiWrapperDataSource.php index ae7d98a..78622d0 100644 --- a/src/Bridges/Dibi/DataSources/MultiDibiWrapperDataSource.php +++ b/src/Bridges/Dibi/DataSources/MultiDibiWrapperDataSource.php @@ -5,9 +5,9 @@ use DibiConnection; use Tlapnet\Report\DataSources\AbstractMultiDataSource; use Tlapnet\Report\Exceptions\Runtime\DataSource\SqlException; +use Tlapnet\Report\Model\Parameters\Parameters; use Tlapnet\Report\Model\Result\MultiResult; use Tlapnet\Report\Model\Result\Result; -use Tlapnet\Report\Model\Parameters\Parameters; class MultiDibiWrapperDataSource extends AbstractMultiDataSource { @@ -34,9 +34,6 @@ public function __construct(DibiConnection $connection) */ public function compile(Parameters $parameters) { - $expander = $parameters->createExpander(); - $params = $parameters->toArray(); - // Create result $result = new MultiResult(); @@ -44,17 +41,21 @@ public function compile(Parameters $parameters) // Get sql from row $sql = $row->sql; - if ($this->isPure()) { - // Expand parameters - $sql = $expander->expand($sql); - // Execute native query - $resultset = $this->connection->nativeQuery($sql); + // Prepare parameters + if (!$parameters->isEmpty()) { + $switch = $parameters->createSwitcher(); + $switch->setPlaceholder('?'); + // Replace named parameters for ? and return + // accurate sequenced array of arguments + list ($sql, $args) = $switch->execute($sql); } else { - // Execute nette database query - $args = array_values($params); - $resultset = $this->connection->query($sql, $args); + // Keep empty arguments + $args = []; } + // Execute nette database query + $resultset = $this->connection->query($sql, $args); + // Fetch single data $single = $resultset->fetchSingle(); diff --git a/src/Bridges/Nette/Database/DataSources/MultiNetteDatabaseWrapperDataSource.php b/src/Bridges/Nette/Database/DataSources/MultiNetteDatabaseWrapperDataSource.php index da3c633..0ee86c5 100644 --- a/src/Bridges/Nette/Database/DataSources/MultiNetteDatabaseWrapperDataSource.php +++ b/src/Bridges/Nette/Database/DataSources/MultiNetteDatabaseWrapperDataSource.php @@ -5,9 +5,9 @@ use Nette\Database\Connection; use Tlapnet\Report\DataSources\AbstractMultiDataSource; use Tlapnet\Report\Exceptions\Runtime\DataSource\SqlException; +use Tlapnet\Report\Model\Parameters\Parameters; use Tlapnet\Report\Model\Result\MultiResult; use Tlapnet\Report\Model\Result\Result; -use Tlapnet\Report\Model\Parameters\Parameters; class MultiNetteDatabaseWrapperDataSource extends AbstractMultiDataSource { @@ -34,9 +34,6 @@ public function __construct(Connection $connection) */ public function compile(Parameters $parameters) { - $expander = $parameters->createExpander(); - $params = $parameters->toArray(); - // Create result $result = new MultiResult(); @@ -44,17 +41,21 @@ public function compile(Parameters $parameters) // Get sql from row $sql = $row->sql; - if ($this->isPure()) { - // Expand parameters - $sql = $expander->expand($sql); - // Execute native query - $resultset = $this->connection->query($sql); + // Prepare parameters + if (!$parameters->isEmpty()) { + $switch = $parameters->createSwitcher(); + $switch->setPlaceholder('?'); + // Replace named parameters for ? and return + // accurate sequenced array of arguments + list ($sql, $args) = $switch->execute($sql); } else { - // Execute nette database query - $args = array_values($params); - $resultset = $this->connection->queryArgs($sql, $args); + // Keep empty arguments + $args = []; } + // Execute nette database query + $resultset = $this->connection->queryArgs($sql, $args); + // Fetch single data $single = $resultset->fetchField(); diff --git a/src/Bridges/Nette/Database/DataSources/NetteDatabaseDataSource.php b/src/Bridges/Nette/Database/DataSources/NetteDatabaseDataSource.php index e24b0d3..f921f71 100644 --- a/src/Bridges/Nette/Database/DataSources/NetteDatabaseDataSource.php +++ b/src/Bridges/Nette/Database/DataSources/NetteDatabaseDataSource.php @@ -7,8 +7,8 @@ use Nette\Database\Helpers; use Tlapnet\Report\DataSources\AbstractDatabaseConnectionDataSource; use Tlapnet\Report\Exceptions\Runtime\DataSource\SqlException; -use Tlapnet\Report\Model\Result\Result; use Tlapnet\Report\Model\Parameters\Parameters; +use Tlapnet\Report\Model\Result\Result; use Tracy\Debugger; class NetteDatabaseDataSource extends AbstractDatabaseConnectionDataSource @@ -81,17 +81,20 @@ public function compile(Parameters $parameters) $sql = $this->getRealSql($parameters); try { - if ($this->isPure()) { - // Expand parameters - $expander = $parameters->createExpander(); - $sql = $expander->expand($sql); - // Execute native query - $resultset = $this->connection->query($sql); + // Prepare parameters + if (!$parameters->isEmpty()) { + $switch = $parameters->createSwitcher(); + $switch->setPlaceholder('?'); + // Replace named parameters for ? and return + // accurate sequenced array of arguments + list ($sql, $args) = $switch->execute($sql); } else { - // Execute nette database query - $args = array_values($parameters->toArray()); - $resultset = $this->connection->queryArgs($sql, $args); + // Keep empty arguments + $args = []; } + + // Execute nette database query + $resultset = $this->connection->queryArgs($sql, $args); } catch (DriverException $e) { throw new SqlException($sql, NULL, $e); } diff --git a/src/Bridges/Nette/Database/DataSources/NetteDatabaseWrapperDataSource.php b/src/Bridges/Nette/Database/DataSources/NetteDatabaseWrapperDataSource.php index 0b03653..f59913b 100644 --- a/src/Bridges/Nette/Database/DataSources/NetteDatabaseWrapperDataSource.php +++ b/src/Bridges/Nette/Database/DataSources/NetteDatabaseWrapperDataSource.php @@ -7,8 +7,8 @@ use Nette\Database\Helpers; use Tlapnet\Report\DataSources\AbstractDatabaseDataSource; use Tlapnet\Report\Exceptions\Runtime\DataSource\SqlException; -use Tlapnet\Report\Model\Result\Result; use Tlapnet\Report\Model\Parameters\Parameters; +use Tlapnet\Report\Model\Result\Result; use Tracy\Debugger; class NetteDatabaseWrapperDataSource extends AbstractDatabaseDataSource @@ -60,17 +60,20 @@ public function compile(Parameters $parameters) $sql = $this->getRealSql($parameters); try { - if ($this->isPure()) { - // Expand parameters - $expander = $parameters->createExpander(); - $sql = $expander->expand($sql); - // Execute native query - $resultset = $this->connection->query($sql); + // Prepare parameters + if (!$parameters->isEmpty()) { + $switch = $parameters->createSwitcher(); + $switch->setPlaceholder('?'); + // Replace named parameters for ? and return + // accurate sequenced array of arguments + list ($sql, $args) = $switch->execute($sql); } else { - // Execute nette database query - $args = array_values($parameters->toArray()); - $resultset = $this->connection->queryArgs($sql, $args); + // Keep empty arguments + $args = []; } + + // Execute nette database query + $resultset = $this->connection->queryArgs($sql, $args); } catch (DriverException $e) { throw new SqlException($sql, NULL, $e); } diff --git a/src/DataSources/AbstractDatabaseDataSource.php b/src/DataSources/AbstractDatabaseDataSource.php index 2cb6cad..cff3cad 100644 --- a/src/DataSources/AbstractDatabaseDataSource.php +++ b/src/DataSources/AbstractDatabaseDataSource.php @@ -49,22 +49,6 @@ public function setDefaultSql($sql) $this->defaultSql = (string)$sql; } - /** - * @return boolean - */ - public function isPure() - { - return $this->pure; - } - - /** - * @param boolean $pure - */ - public function setPure($pure) - { - $this->pure = (bool)$pure; - } - /** * @param Parameters $parameters * @return string diff --git a/src/DataSources/AbstractMultiDataSource.php b/src/DataSources/AbstractMultiDataSource.php index 713b8c2..13d0c96 100644 --- a/src/DataSources/AbstractMultiDataSource.php +++ b/src/DataSources/AbstractMultiDataSource.php @@ -8,9 +8,6 @@ abstract class AbstractMultiDataSource implements DataSource /** @var array */ protected $rows = []; - /** @var bool */ - protected $pure = TRUE; - /** * @param string $sql */ @@ -22,20 +19,4 @@ public function addRow($title, $sql) ]; } - /** - * @return boolean - */ - public function isPure() - { - return $this->pure; - } - - /** - * @param boolean $pure - */ - public function setPure($pure) - { - $this->pure = (bool)$pure; - } - } diff --git a/src/DataSources/PdoDataSource.php b/src/DataSources/PdoDataSource.php index 203bc55..5cb1bd3 100644 --- a/src/DataSources/PdoDataSource.php +++ b/src/DataSources/PdoDataSource.php @@ -4,8 +4,8 @@ use PDO; use Tlapnet\Report\Exceptions\Runtime\DataSource\SqlException; -use Tlapnet\Report\Model\Result\Result; use Tlapnet\Report\Model\Parameters\Parameters; +use Tlapnet\Report\Model\Result\Result; class PdoDataSource extends AbstractDatabaseConnectionDataSource { @@ -38,20 +38,27 @@ public function compile(Parameters $parameters) // Connect to DB if (!$this->pdo) $this->connect(); - // Expand parameters - $expander = $parameters->createExpander(); - // Get SQL $sql = $this->getRealSql($parameters); - // Replace placeholders - $query = $expander->expand($sql); - try { - $statement = $this->pdo->prepare($query); - $statement->execute(); + // Prepare parameters + if (!$parameters->isEmpty()) { + $switch = $parameters->createSwitcher(); + $switch->setPlaceholder('?'); + // Replace named parameters for ? and return + // accurate sequenced array of arguments + list ($sql, $args) = $switch->execute($sql); + } else { + // Keep empty arguments + $args = []; + } + + // Execute native pdo query + $statement = $this->pdo->prepare($sql); + $statement->execute($args); } catch (\PDOException $e) { - throw new SqlException($query, NULL, $e); + throw new SqlException($sql, NULL, $e); } $result = new Result($statement->fetchAll()); diff --git a/src/Model/Parameters/Parameters.php b/src/Model/Parameters/Parameters.php index f3027e1..ebe8211 100644 --- a/src/Model/Parameters/Parameters.php +++ b/src/Model/Parameters/Parameters.php @@ -7,6 +7,7 @@ use Tlapnet\Report\Model\Subreport\Attachable; use Tlapnet\Report\Utils\Expander; use Tlapnet\Report\Utils\Suggestions; +use Tlapnet\Report\Utils\Switcher; class Parameters implements Attachable { @@ -125,4 +126,12 @@ public function createExpander() return new Expander($this->toArray()); } + /** + * @return Switcher + */ + public function createSwitcher() + { + return new Switcher($this->toArray()); + } + } diff --git a/src/Utils/Expander.php b/src/Utils/Expander.php index 9dd7222..af27291 100644 --- a/src/Utils/Expander.php +++ b/src/Utils/Expander.php @@ -2,11 +2,13 @@ namespace Tlapnet\Report\Utils; +use Tlapnet\Report\Exceptions\Logic\InvalidStateException; + final class Expander { - // Delimiter - const DELIMITER = '%'; + /** @var string */ + private $pattern = '#\{([\w\d\_\-]+)\}#'; /** @var array */ private $parameters; @@ -20,15 +22,27 @@ public function __construct(array $parameters) } /** - * @param mixed $input + * @param string $pattern + */ + public function setPattern($pattern) + { + $this->pattern = $pattern; + } + + /** + * API ********************************************************************* + */ + + /** + * @param array|string $input * @return mixed */ - public function expand($input) + public function execute($input) { if (is_array($input)) { - return $this->arr($input); + return $this->doArray($input); } else if (is_string($input)) { - return $this->str($input); + return $this->doSingle($input); } else { return $input; } @@ -38,21 +52,12 @@ public function expand($input) * @param array $array * @return array */ - public function arr(array $array) + public function doArray(array $array) { $output = []; foreach ($array as $k => $v) { - $key = str_replace( - $this->getPlaceholders(), - $this->getReplacements(), - $k - ); - - $value = str_replace( - $this->getPlaceholders(), - $this->getReplacements(), - $v - ); + $key = $this->doSingle($k); + $value = $this->doSingle($v); $output[$key] = $value; } @@ -64,39 +69,23 @@ public function arr(array $array) * @param string $str * @return string */ - public function str($str) - { - return str_replace( - $this->getPlaceholders(), - $this->getReplacements(), - $str - ); - } - - /** - * @return array - */ - protected function getPlaceholders() + public function doSingle($str) { - $placeholders = []; - foreach ($this->parameters as $name => $value) { - $placeholders[] = self::DELIMITER . $name . self::DELIMITER; - } + return preg_replace_callback($this->pattern, function ($matches) { + if (count($matches) > 2) { + throw new InvalidStateException('Invalid pattern, only one group is allowed'); + } - return $placeholders; - } + list ($whole, $param) = $matches; - /** - * @return array - */ - protected function getReplacements() - { - $replacements = []; - foreach ($this->parameters as $name => $value) { - $replacements[] = $value; - } + // Try to replace part with a parameter + if (isset($this->parameters[$param])) { + return $this->parameters[$param]; + } - return $replacements; + // Return original part + return $whole; + }, $str); } -} \ No newline at end of file +} diff --git a/src/Utils/Switcher.php b/src/Utils/Switcher.php new file mode 100644 index 0000000..ccea1f4 --- /dev/null +++ b/src/Utils/Switcher.php @@ -0,0 +1,90 @@ +parameters = $parameters; + } + + /** + * @param string $pattern + */ + public function setPattern($pattern) + { + $this->pattern = $pattern; + } + + /** + * @param string $placeholder + */ + public function setPlaceholder($placeholder) + { + $this->placeholder = $placeholder; + } + + /** + * API ********************************************************************* + */ + + /** + * @param string $input + * @return array [input, args] + */ + public function execute($input) + { + if (is_string($input)) { + return $this->doString($input); + } else { + return $input; + } + } + + /** + * @param string $str + * @return array [input, args] + */ + public function doString($str) + { + $args = []; + $str = preg_replace_callback($this->pattern, function ($matches) use (&$args) { + if (count($matches) > 2) { + throw new InvalidStateException('Invalid pattern, only one group is allowed'); + } + + list ($whole, $param) = $matches; + + // Replace with placeholder, if we have given parameter + if (isset($this->parameters[$param])) { + // Append to array + $args[] = $this->parameters[$param]; + + // Return placeholder + return $this->placeholder; + } + + // Return original part + return $whole; + }, $str); + + return [$str, $args]; + } + +} \ No newline at end of file diff --git a/tests/Report/Model/Parameters/ParametersTest.php b/tests/Report/Model/Parameters/ParametersTest.php index eea5fea..3083554 100644 --- a/tests/Report/Model/Parameters/ParametersTest.php +++ b/tests/Report/Model/Parameters/ParametersTest.php @@ -83,7 +83,7 @@ public function testCreateExpander() $expander = $p->createExpander(); $this->assertEquals(Expander::class, get_class($expander)); - $this->assertEquals('bar', $expander->str('%foo%')); + $this->assertEquals('bar', $expander->doSingle('{foo}')); } } diff --git a/tests/Report/Utils/ExpanderTest.php b/tests/Report/Utils/ExpanderTest.php index d64e276..1e1d42d 100644 --- a/tests/Report/Utils/ExpanderTest.php +++ b/tests/Report/Utils/ExpanderTest.php @@ -13,7 +13,7 @@ public function testStr() $params = ['foo' => 'bar']; $expander = new Expander($params); - $this->assertEquals('example/bar', $expander->str('example/%foo%')); + $this->assertEquals('example/bar', $expander->doSingle('example/{foo}')); } public function testArr() @@ -21,7 +21,7 @@ public function testArr() $params = ['foo' => 'bar']; $expander = new Expander($params); - $this->assertEquals(['a' => 'bar', 'bar' => 'b'], $expander->arr(['a' => '%foo%', '%foo%' => 'b'])); + $this->assertEquals(['a' => 'bar', 'bar' => 'b'], $expander->doArray(['a' => '{foo}', '{foo}' => 'b'])); } public function testExpand() @@ -29,11 +29,12 @@ public function testExpand() $params = ['foo' => 'bar']; $expander = new Expander($params); - $this->assertEquals('example/bar', $expander->expand('example/%foo%')); - $this->assertEquals(['a' => 'bar', 'bar' => 'b'], $expander->expand(['a' => '%foo%', '%foo%' => 'b'])); + $this->assertEquals('example/bar', $expander->execute('example/{foo}')); + $this->assertEquals(['a' => 'bar', 'bar' => 'b'], $expander->execute(['a' => '{foo}', '{foo}' => 'b'])); + // Unsupported type $stdClass = new \stdClass(); - $this->assertEquals($stdClass, $expander->expand($stdClass)); + $this->assertEquals($stdClass, $expander->execute($stdClass)); } } diff --git a/tests/Report/Utils/SwitcherTest.php b/tests/Report/Utils/SwitcherTest.php new file mode 100644 index 0000000..ed7c90e --- /dev/null +++ b/tests/Report/Utils/SwitcherTest.php @@ -0,0 +1,42 @@ + 'bar']; + $expander = new Switcher($params); + $this->assertEquals(['example/?', [0 => 'bar']], $expander->execute('example/{foo}')); + + // More items + $params = ['foo' => 'bar', 'a' => 'foobar', 'b' => 2]; + $expander = new Switcher($params); + $this->assertEquals( + ['example/?/?/?', + [ + 0 => 'bar', + 1 => 2, + 2 => 'foobar', + ], + ], + $expander->execute('example/{foo}/{b}/{a}') + ); + + // Not found + $params = ['foo' => 'bar']; + $expander = new Switcher($params); + $this->assertEquals(['example/{foobar}', []], $expander->execute('example/{foobar}')); + + // Unsupported type + $stdClass = new \stdClass(); + $this->assertEquals($stdClass, $expander->execute($stdClass)); + } + +}