From a4af09959a22cfcd7f187a0327ea859318ddc538 Mon Sep 17 00:00:00 2001 From: Valentine Chernyavsky Date: Mon, 26 Feb 2018 14:37:21 +0200 Subject: [PATCH 1/3] Defining modifiers for array based types (values, list, set) --- goDB/Exceptions/SubDataInvalidFormat.php | 20 ++++ goDB/Helpers/Templater.php | 119 ++++++++++++++++++----- tests/Helpers/Templater/ListTest.php | 5 + tests/Helpers/Templater/SetTest.php | 5 + tests/Helpers/Templater/ValuesTest.php | 5 + 5 files changed, 128 insertions(+), 26 deletions(-) create mode 100644 goDB/Exceptions/SubDataInvalidFormat.php diff --git a/goDB/Exceptions/SubDataInvalidFormat.php b/goDB/Exceptions/SubDataInvalidFormat.php new file mode 100644 index 0000000..9923c90 --- /dev/null +++ b/goDB/Exceptions/SubDataInvalidFormat.php @@ -0,0 +1,20 @@ +query; } $query = preg_replace_callback('~{(.*?)}~', array($this, 'tableClb'), $this->pattern); - $pattern = '~\?([a-z\?-]+)?(:([a-z0-9_-]*))?;?~i'; + $pattern = '~\?([a-z\?-]+)?(:([a-z0-9_-]*))?(\[[?:\s\w,-]+\])?;?~i'; $callback = array($this, 'placeholderClb'); $query = preg_replace_callback($pattern, $callback, $query); if ((!$this->named) && (count($this->data) > $this->counter)) { @@ -97,17 +99,67 @@ private function tableClb($matches) protected function placeholderClb($matches) { $placeholder = isset($matches[1]) ? $matches[1] : ''; + if ($placeholder == '?') { // "??" for question mark + return '?'; + } + + $parser = $this->getParser($matches); + + $key = $this->currentName; + if (!array_key_exists($key, $this->data)) { + if ($this->named) { + throw new DataNamed($key); + } else { + throw new DataNotEnough(count($this->data), $key); + } + } + $value = $this->data[$key]; + + $elementModifiers = []; + if (isset($matches[4])) { + if (!is_array($value)) { + throw new DataInvalidFormat($matches[1], 'required array'); + } + $subTemplate = clone $this; + $subTemplate->named = false; + $subTemplate->counter = 0; + $pattern = '~\?([a-z\?-]+)?(:([a-z0-9_-]*))?;?~i'; + preg_match_all($pattern, $matches[4], $subMatches, PREG_SET_ORDER); + foreach ($subMatches as $match) { + try { + $subParser = $subTemplate->getParser($match); + } catch (Logic $ex) { + throw new SubDataInvalidFormat($placeholder, $ex->getMessage(), $ex); + } + if ($subType = $subParser->getType()) { + throw new SubDataInvalidFormat($placeholder, 'Only modifiers can be used. Found type: ' . $subType); + } + $elementModifiers[$subTemplate->currentName] = $subParser->getModifiers(); + } + } + $modifiers = $parser->getModifiers(); + $method = 'replacement' . strtoupper($parser->getType()); + + return $this->$method($value, $modifiers, $elementModifiers); + } + + /** + * Get parser object + * + * @param array $matches + * @return ParserPH + * @throws \go\DB\Exceptions\Templater + */ + protected function getParser($matches) + { if (isset($matches[3])) { $name = $matches[3]; - if (empty($name)) { + if (empty($name) && $matches[2] == ':') { /* There is a named placeholder without name ("?set:") */ throw new UnknownPlaceholder($matches[0]); } } else { $name = null; - if ($placeholder == '?') { // "??" for question mark - return '?'; - } } if ($name) { if ($this->counter == 0) { @@ -116,26 +168,16 @@ protected function placeholderClb($matches) /* There is a named placeholder although already used regular */ throw new MixedPlaceholder($matches[0]); } - if (!array_key_exists($name, $this->data)) { - throw new DataNamed($name); - } - $value = $this->data[$name]; + $this->currentName = $name; } elseif ($this->named) { /* There is a regular placeholder although already used named */ throw new MixedPlaceholder($matches[0]); } else { - if (!array_key_exists($this->counter, $this->data)) { - /* Data for regular placeholders is ended */ - throw new DataNotEnough(count($this->data), $this->counter); - } - $value = $this->data[$this->counter]; + $this->currentName = $this->counter; } $this->counter++; - $parser = new ParserPH($placeholder); - $type = $parser->getType(); - $modifiers = $parser->getModifiers(); - $method = 'replacement'.strtoupper($type); - return $this->$method($value, $modifiers); + + return new ParserPH(isset($matches[1]) ? $matches[1] : ''); } /** @@ -198,9 +240,10 @@ protected function replacement($value, array $modifiers) * * @param array $value * @param array $modifiers + * @param array $elementModifiers * @return string */ - protected function replacementL($value, array $modifiers) + protected function replacementL($value, array $modifiers, array $elementModifiers = []) { if (!is_array($value)) { throw new DataInvalidFormat('list', 'required array (list of values)'); @@ -210,7 +253,15 @@ protected function replacementL($value, array $modifiers) if (is_array($element)) { throw new DataInvalidFormat('list', 'required scalar in item #'.$k); } - $values[] = $this->valueModification($element, $modifiers); + if (!empty($elementModifiers)) { + if (!isset($elementModifiers[$k])) { + throw new DataInvalidFormat('set', 'No modifier for key: ' . $k); + } + $elementMod = $elementModifiers[$k]; + } else { + $elementMod = $modifiers; + } + $values[] = $this->valueModification($element, $elementMod); } return implode(', ', $values); } @@ -220,9 +271,10 @@ protected function replacementL($value, array $modifiers) * * @param array $value * @param array $modifiers + * @param array $elementModifiers * @return string */ - protected function replacementS($value, array $modifiers) + protected function replacementS($value, array $modifiers, array $elementModifiers = []) { if (!is_array($value)) { throw new DataInvalidFormat('set', 'required array (column => value)'); @@ -237,14 +289,23 @@ protected function replacementS($value, array $modifiers) $element = $this->replacementC($element, $modifiers); } } else { - if (is_int($element)) { + if (is_int($element) && !isset($elementModifiers[$col])) { $element = $this->implementation->reprInt($this->connection, $element); } else { - $element = $this->valueModification($element, $modifiers); + if (!empty($elementModifiers)) { + if (!isset($elementModifiers[$col])) { + throw new DataInvalidFormat('set', 'No modifier for col: ' . $col); + } + $elementMod = $elementModifiers[$col]; + } else { + $elementMod = $modifiers; + } + $element = $this->valueModification($element, $elementMod); } } $set[] = $key.'='.$element; } + return implode(', ', $set); } @@ -253,16 +314,17 @@ protected function replacementS($value, array $modifiers) * * @param array $value * @param array $modifiers + * @param array $elementModifiers * @return string */ - private function replacementV($value, array $modifiers) + private function replacementV($value, array $modifiers, array $elementModifiers = []) { if (!is_array($value)) { throw new DataInvalidFormat('values', 'required array of arrays'); } $values = array(); foreach ($value as $v) { - $values[] = '('.$this->replacementL($v, $modifiers).')'; + $values[] = '('.$this->replacementL($v, $modifiers, $elementModifiers).')'; } return implode(', ', $values); } @@ -541,6 +603,11 @@ private function whereGroup($value, array $modifiers, $sep = 'AND') */ protected $data; + /** + * @var string + */ + protected $currentName = ''; + /** * @var string */ diff --git a/tests/Helpers/Templater/ListTest.php b/tests/Helpers/Templater/ListTest.php index f7ba104..394d49f 100644 --- a/tests/Helpers/Templater/ListTest.php +++ b/tests/Helpers/Templater/ListTest.php @@ -49,6 +49,11 @@ public function providerTemplater() [$list], 'INSERT INTO `table` VALUES (0, 1, NULL, 3)', ], + 'subtype' => [ + 'INSERT INTO `table` VALUES (?l[?int, ?string, ?n, ?int])', + [$list], + 'INSERT INTO `table` VALUES (0, "1", NULL, 3)', + ], ]; } } diff --git a/tests/Helpers/Templater/SetTest.php b/tests/Helpers/Templater/SetTest.php index 6cbf6e0..d655968 100644 --- a/tests/Helpers/Templater/SetTest.php +++ b/tests/Helpers/Templater/SetTest.php @@ -28,6 +28,11 @@ public function providerTemplater() [$set], 'INSERT INTO `table` SET `s`="str\"ing", `d`="3.5", `n`=NULL', ], + 'subset' => [ + 'INSERT INTO `table` SET ?s[?string:s, ?int:d, ?null:n]', + [$set], + 'INSERT INTO `table` SET `s`="str\"ing", `d`=3, `n`=NULL', + ], 'null' => [ 'INSERT INTO `table` SET ?set-null', [$set], diff --git a/tests/Helpers/Templater/ValuesTest.php b/tests/Helpers/Templater/ValuesTest.php index 1f09c40..54a84ae 100644 --- a/tests/Helpers/Templater/ValuesTest.php +++ b/tests/Helpers/Templater/ValuesTest.php @@ -27,6 +27,11 @@ public function providerTemplater() [$values], 'INSERT INTO `table` VALUES (0, 1, 2), ("one", NULL, "three")', ], + 'subset' => [ + 'INSERT INTO `table` VALUES ?values[?string, ?int-null, ?i]', + [$values], + 'INSERT INTO `table` VALUES ("0", 1, 2), ("one", NULL, 0)', + ], 'null' => [ 'INSERT INTO `table` VALUES ?vn', [$values], From fa15093d33620d721b66d161a84c6eb0452793ed Mon Sep 17 00:00:00 2001 From: Valentine Chernyavsky Date: Mon, 26 Feb 2018 15:36:56 +0200 Subject: [PATCH 2/3] sub-patterns processing moved to getElementModifiers --- goDB/Helpers/Templater.php | 63 ++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/goDB/Helpers/Templater.php b/goDB/Helpers/Templater.php index a5469ca..16d5c94 100644 --- a/goDB/Helpers/Templater.php +++ b/goDB/Helpers/Templater.php @@ -104,43 +104,52 @@ protected function placeholderClb($matches) } $parser = $this->getParser($matches); - - $key = $this->currentName; - if (!array_key_exists($key, $this->data)) { + $dataKey = $this->currentName; + if (!array_key_exists($dataKey, $this->data)) { if ($this->named) { - throw new DataNamed($key); + throw new DataNamed($dataKey); } else { - throw new DataNotEnough(count($this->data), $key); + throw new DataNotEnough(count($this->data), $dataKey); } } - $value = $this->data[$key]; + $value = $this->data[$dataKey]; - $elementModifiers = []; if (isset($matches[4])) { - if (!is_array($value)) { - throw new DataInvalidFormat($matches[1], 'required array'); + $elementModifiers = $this->getElementModifiers($placeholder, $matches[4]); + } else { + $elementModifiers = []; + } + $method = 'replacement' . strtoupper($parser->getType()); + + return $this->$method($value, $parser->getModifiers(), $elementModifiers); + } + + /** + * @param string $placeholder + * @param string $subPattern + * @return array + */ + protected function getElementModifiers($placeholder, $subPattern) + { + $subTemplate = clone $this; + $subTemplate->named = false; + $subTemplate->counter = 0; + $pattern = '~\?([a-z\?-]+)?(:([a-z0-9_-]*))?;?~i'; + $elementModifiers = []; + preg_match_all($pattern, $subPattern, $subMatches, PREG_SET_ORDER); + foreach ($subMatches as $match) { + try { + $subParser = $subTemplate->getParser($match); + } catch (Logic $ex) { + throw new SubDataInvalidFormat($placeholder, $ex->getMessage(), $ex); } - $subTemplate = clone $this; - $subTemplate->named = false; - $subTemplate->counter = 0; - $pattern = '~\?([a-z\?-]+)?(:([a-z0-9_-]*))?;?~i'; - preg_match_all($pattern, $matches[4], $subMatches, PREG_SET_ORDER); - foreach ($subMatches as $match) { - try { - $subParser = $subTemplate->getParser($match); - } catch (Logic $ex) { - throw new SubDataInvalidFormat($placeholder, $ex->getMessage(), $ex); - } - if ($subType = $subParser->getType()) { - throw new SubDataInvalidFormat($placeholder, 'Only modifiers can be used. Found type: ' . $subType); - } - $elementModifiers[$subTemplate->currentName] = $subParser->getModifiers(); + if ($subType = $subParser->getType()) { + throw new SubDataInvalidFormat($placeholder, 'Only modifiers can be used. Found type: ' . $subType); } + $elementModifiers[$subTemplate->currentName] = $subParser->getModifiers(); } - $modifiers = $parser->getModifiers(); - $method = 'replacement' . strtoupper($parser->getType()); - return $this->$method($value, $modifiers, $elementModifiers); + return $elementModifiers; } /** From 55afd851b7f49046e798dbfebe69163f5384514b Mon Sep 17 00:00:00 2001 From: Valentine Chernyavsky Date: Mon, 26 Feb 2018 18:44:11 +0200 Subject: [PATCH 3/3] Impoved test coverage. --- goDB/Helpers/Templater.php | 19 ++++--- tests/Helpers/Templater/ExceptionsTest.php | 58 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/goDB/Helpers/Templater.php b/goDB/Helpers/Templater.php index 16d5c94..6dc5094 100644 --- a/goDB/Helpers/Templater.php +++ b/goDB/Helpers/Templater.php @@ -264,7 +264,7 @@ protected function replacementL($value, array $modifiers, array $elementModifier } if (!empty($elementModifiers)) { if (!isset($elementModifiers[$k])) { - throw new DataInvalidFormat('set', 'No modifier for key: ' . $k); + throw new DataInvalidFormat('list', 'No modifier for key: ' . $k); } $elementMod = $elementModifiers[$k]; } else { @@ -298,18 +298,17 @@ protected function replacementS($value, array $modifiers, array $elementModifier $element = $this->replacementC($element, $modifiers); } } else { - if (is_int($element) && !isset($elementModifiers[$col])) { - $element = $this->implementation->reprInt($this->connection, $element); + if (!empty($elementModifiers)) { + if (!isset($elementModifiers[$col])) { + throw new DataInvalidFormat('set', 'No modifier for col: ' . $col); + } + $element = $this->valueModification($element, $elementModifiers[$col]); } else { - if (!empty($elementModifiers)) { - if (!isset($elementModifiers[$col])) { - throw new DataInvalidFormat('set', 'No modifier for col: ' . $col); - } - $elementMod = $elementModifiers[$col]; + if (is_int($element)) { + $element = $this->implementation->reprInt($this->connection, $element); } else { - $elementMod = $modifiers; + $element = $this->valueModification($element, $modifiers); } - $element = $this->valueModification($element, $elementMod); } } $set[] = $key.'='.$element; diff --git a/tests/Helpers/Templater/ExceptionsTest.php b/tests/Helpers/Templater/ExceptionsTest.php index 97db6a7..ab82567 100644 --- a/tests/Helpers/Templater/ExceptionsTest.php +++ b/tests/Helpers/Templater/ExceptionsTest.php @@ -236,4 +236,62 @@ public function providerExceptionDataInvalidFormat() ], ]; } + + /** + * @dataProvider providerExceptionSubDataInvalidFormat + * @param string $placeholder + * @param mixed $data + * @param string $exceptionClass + * @param string $message + */ + public function testExceptionSubDataInvalidFormat($placeholder, $data, $exceptionClass, $message) + { + $pattern = '?'.$placeholder; + $data = [$data]; + $templater = $this->createTemplater($pattern, $data); + $this->setExpectedException($exceptionClass, $message); + $templater->parse(); + } + + public function providerExceptionSubDataInvalidFormat() + { + return [ + 'type' => [ + 'v[?i, ?set-int]', + [1, 2], + 'go\DB\Exceptions\SubDataInvalidFormat', + 'Only modifiers can be used. Found type: s', + ], + 'internal' => [ + 'l[?i, ?wtf]', + [1, 2], + 'go\DB\Exceptions\SubDataInvalidFormat', + 'Invalid sub data for ?l: Unknown placeholder "wtf"', + ], + 'mixedSubPlaceholder' => [ + 'l[?i, ?i:foo]', + [1, 2], + 'go\DB\Exceptions\SubDataInvalidFormat', + 'Invalid sub data for ?l: Mixed placeholder "?i:foo"', + ], + 'mixedSubPlaceholder2' => [ + 'v[?i:bar, ?i]', + [1, 2], + 'go\DB\Exceptions\SubDataInvalidFormat', + 'Invalid sub data for ?v: Mixed placeholder "?i"', + ], + 'listElementModifierNotSet' => [ + 'l[?i, ?i]', + [1, 2, 3], + 'go\DB\Exceptions\DataInvalidFormat', + 'Data for ?list has invalid format: "No modifier for key: 2"', + ], + 'setElementModifierNotSet' => [ + 's[?i:foo, ?i:baZ]', + ['foo' => 1, 'bar' => 2], + 'go\DB\Exceptions\DataInvalidFormat', + 'Data for ?set has invalid format: "No modifier for col: bar"', + ], + ]; + } }