From 9ede93ee26cc1f52f73761ffbd03d26ad60ce583 Mon Sep 17 00:00:00 2001 From: Ivan Russu <2ivanrussu@gmail.com> Date: Sun, 10 Aug 2025 20:08:01 +0500 Subject: [PATCH] EloquentDataSource - ignore placeholders inside SQL comments and strings Previously, `createRunnableQuery()` incorrectly replaced placeholders (`?` or `:name`) found inside SQL comments or string literals. This caused: - For named bindings: "Undefined array key 0" when a `?` appeared in a comment. - For positional bindings: placeholders in comments being replaced with bindings. Changes: - Detect positional vs named bindings before replacement. - Updated regex to skip: * Single-line comments (`-- ...`) * Multi-line comments (`/* ... */`) * Single-quoted strings (`'...'`) * Double-quoted strings (`"..."`) - Ensures placeholders inside these constructs are ignored. This prevents incorrect binding substitution and avoids exceptions when queries contain comments or strings with question marks. --- Clockwork/DataSource/EloquentDataSource.php | 47 +++++++++++++-------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/Clockwork/DataSource/EloquentDataSource.php b/Clockwork/DataSource/EloquentDataSource.php index 97176f75..be27f436 100644 --- a/Clockwork/DataSource/EloquentDataSource.php +++ b/Clockwork/DataSource/EloquentDataSource.php @@ -268,25 +268,38 @@ protected function createRunnableQuery($query, $bindings, $connection) { // add bindings to query $bindings = $this->databaseManager->connection($connection)->prepareBindings($bindings); - + + $bindingsArePositional = array_keys($bindings) === range(0, count($bindings) - 1); + + /** + * Regexp explanation: + * ('(?:''|[^'])*') - single quoted string (handles escaped single quotes by doubling) + * ("(?:""|[^"])*") - double quoted string (handles escaped double quotes by doubling) + * (--.*) - single-line comment + * (\/\*[\s\S]*?\*\/) - multi-line comment + * (*SKIP)(*FAIL) - skip all matches above + * \? - positional placeholder + * :\w+ - named placeholder + */ + $pattern = $bindingsArePositional + ? "/('(?:''|[^'])*'|\"(?:\"\"|[^\"])*\"|--.*|\/\*[\s\S]*?\*\/)(*SKIP)(*FAIL)|\?/" + : "/('(?:''|[^'])*'|\"(?:\"\"|[^\"])*\"|--.*|\/\*[\s\S]*?\*\/)(*SKIP)(*FAIL)|:\w+/"; + + $index = 0; - $query = preg_replace_callback('/\'[^\']*\'|[^?]\?|\W:[a-z]+/', function ($matches) use ($bindings, $connection, &$index) { - $match = $matches[0]; - - if ($match[0] == '\'') { // quoted string - return $match; - } elseif ($match[1] == '?' && isset($bindings[$index])) { // question-mark binding - $binding = $this->quoteBinding($bindings[$index++], $connection); - } elseif ($match[1] == ':' && isset($bindings[substr($match, 2)])) { // named binding - $binding = $this->quoteBinding($bindings[substr($match, 2)], $connection); - } else { - return $match; - } - + $query = preg_replace_callback($pattern, function ($matches) use ($bindings, $connection, &$index, $bindingsArePositional) { + $binding = $bindingsArePositional + ? $bindings[$index++] + : $bindings[ltrim($matches[$index++], ':')]; + + $binding = $this->quoteBinding($binding, $connection); + // convert binary bindings to hexadecimal representation - if (! preg_match('//u', (string) $binding)) $binding = '0x' . bin2hex($binding); - - return $match[0] . ((string) $binding); + if (!preg_match('//u', (string)$binding)) { + $binding = '0x' . bin2hex($binding); + } + + return (string)$binding; }, $query); // highlight keywords