diff --git a/src/Expression/ExpressionBuilder.php b/src/Expression/AbstractExpressionBuilder.php similarity index 94% rename from src/Expression/ExpressionBuilder.php rename to src/Expression/AbstractExpressionBuilder.php index 9adc2f5b2..1436d0984 100644 --- a/src/Expression/ExpressionBuilder.php +++ b/src/Expression/AbstractExpressionBuilder.php @@ -4,9 +4,10 @@ namespace Yiisoft\Db\Expression; +use Yiisoft\Db\Command\Param; use Yiisoft\Db\Connection\ConnectionInterface; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; -use Yiisoft\Db\Syntax\SqlParser; +use Yiisoft\Db\Syntax\AbstractSqlParser; use function array_merge; use function count; @@ -25,7 +26,7 @@ * * @psalm-import-type ParamsType from ConnectionInterface */ -class ExpressionBuilder implements ExpressionBuilderInterface +abstract class AbstractExpressionBuilder implements ExpressionBuilderInterface { public function __construct(private QueryBuilderInterface|null $queryBuilder = null) { @@ -132,7 +133,7 @@ private function buildParamExpressions(array $expressionParams, array &$params): /** @var non-empty-string $name */ foreach ($expressionParams as $name => $value) { - if (!$value instanceof ExpressionInterface) { + if (!$value instanceof ExpressionInterface || $value instanceof Param) { continue; } @@ -230,14 +231,11 @@ private function replacePlaceholders(string $sql, array $replacements): string } /** - * Creates an instance of {@see SqlParser} for the given SQL statement. + * Creates an instance of {@see AbstractSqlParser} for the given SQL statement. * * @param string $sql SQL statement to be parsed. * - * @return SqlParser SQL parser instance. + * @return AbstractSqlParser SQL parser instance. */ - protected function createSqlParser(string $sql): SqlParser - { - return new SqlParser($sql); - } + abstract protected function createSqlParser(string $sql): AbstractSqlParser; } diff --git a/src/QueryBuilder/AbstractDQLQueryBuilder.php b/src/QueryBuilder/AbstractDQLQueryBuilder.php index 15aec88d7..fd3951540 100644 --- a/src/QueryBuilder/AbstractDQLQueryBuilder.php +++ b/src/QueryBuilder/AbstractDQLQueryBuilder.php @@ -11,7 +11,6 @@ use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\Expression; -use Yiisoft\Db\Expression\ExpressionBuilder; use Yiisoft\Db\Expression\ExpressionBuilderInterface; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\QueryBuilder\Condition\HashCondition; @@ -503,7 +502,6 @@ protected function defaultExpressionBuilders(): array return [ Query::class => QueryExpressionBuilder::class, Param::class => ParamBuilder::class, - Expression::class => ExpressionBuilder::class, Condition\AbstractConjunctionCondition::class => Condition\Builder\ConjunctionConditionBuilder::class, Condition\NotCondition::class => Condition\Builder\NotConditionBuilder::class, Condition\AndCondition::class => Condition\Builder\ConjunctionConditionBuilder::class, diff --git a/src/Syntax/SqlParser.php b/src/Syntax/AbstractSqlParser.php similarity index 80% rename from src/Syntax/SqlParser.php rename to src/Syntax/AbstractSqlParser.php index 509be0f4d..ae6d6b250 100644 --- a/src/Syntax/SqlParser.php +++ b/src/Syntax/AbstractSqlParser.php @@ -12,7 +12,7 @@ * * This class provides methods to parse SQL statements and extract placeholders from them. */ -class SqlParser +abstract class AbstractSqlParser { /** * @var int Length of SQL statement. @@ -39,37 +39,7 @@ public function __construct(protected string $sql) * * @return string|null The next placeholder or null if it is not found. */ - public function getNextPlaceholder(int|null &$position = null): string|null - { - $result = null; - $length = $this->length - 1; - - while ($this->position < $length) { - $pos = $this->position++; - - match ($this->sql[$pos]) { - ':' => ($word = $this->parseWord()) === '' - ? $this->skipChars(':') - : $result = ':' . $word, - '"', "'" => $this->skipQuotedWithoutEscape($this->sql[$pos]), - '-' => $this->sql[$this->position] === '-' - ? ++$this->position && $this->skipToAfterChar("\n") - : null, - '/' => $this->sql[$this->position] === '*' - ? ++$this->position && $this->skipToAfterString('*/') - : null, - default => null, - }; - - if ($result !== null) { - $position = $pos; - - return $result; - } - } - - return null; - } + abstract public function getNextPlaceholder(int|null &$position = null): string|null; /** * Parses and returns word symbols. Equals to `\w+` in regular expressions. diff --git a/tests/AbstractSqlParserTest.php b/tests/AbstractSqlParserTest.php index f1c77101d..af466d309 100644 --- a/tests/AbstractSqlParserTest.php +++ b/tests/AbstractSqlParserTest.php @@ -5,17 +5,14 @@ namespace Yiisoft\Db\Tests; use PHPUnit\Framework\TestCase; -use Yiisoft\Db\Syntax\SqlParser; +use Yiisoft\Db\Syntax\AbstractSqlParser; use Yiisoft\Db\Tests\Support\TestTrait; abstract class AbstractSqlParserTest extends TestCase { use TestTrait; - protected function createSqlParser(string $sql): SqlParser - { - return new SqlParser($sql); - } + abstract protected function createSqlParser(string $sql): AbstractSqlParser; /** @dataProvider \Yiisoft\Db\Tests\Provider\SqlParserProvider::getNextPlaceholder */ public function testGetNextPlaceholder(string $sql, string|null $expectedPlaceholder, int|null $expectedPosition): void @@ -29,7 +26,7 @@ public function testGetNextPlaceholder(string $sql, string|null $expectedPlaceho /** @dataProvider \Yiisoft\Db\Tests\Provider\SqlParserProvider::getAllPlaceholders */ public function testGetAllPlaceholders(string $sql, array $expectedPlaceholders, array $expectedPositions): void { - $parser = new SqlParser($sql); + $parser = $this->createSqlParser($sql); $placeholders = []; $positions = []; diff --git a/tests/Db/Syntax/SqlParserTest.php b/tests/Db/Syntax/SqlParserTest.php deleted file mode 100644 index d6b7284bf..000000000 --- a/tests/Db/Syntax/SqlParserTest.php +++ /dev/null @@ -1,14 +0,0 @@ - new Expression('LOWER(:val || :val_0)', ['val' => 'A', 'val_0' => 'B']), - 'val_0' => 'C', + 'val_0' => new Param('C', DataType::STRING), ], )], '[[name]] != :val || :val_0', [ - 'val_0' => 'F', + 'val_0' => new Param('F', DataType::STRING), 'val' => new Expression('UPPER(:val || :val_0)', ['val' => 'D', 'val_0' => 'E']), ], DbHelper::replaceQuotes( @@ -1301,10 +1303,10 @@ public static function update(): array [ 'val_2' => 'A', 'val_0_1' => 'B', - 'val_0_0' => 'C', + 'val_0_0' => new Param('C', DataType::STRING), 'val_1' => 'D', 'val_0_2' => 'E', - 'val_0' => 'F', + 'val_0' => new Param('F', DataType::STRING), ], ], 'Expressions with indexed params' => [ diff --git a/tests/Support/Stub/DQLQueryBuilder.php b/tests/Support/Stub/DQLQueryBuilder.php index cb3d793fa..00e8350a5 100644 --- a/tests/Support/Stub/DQLQueryBuilder.php +++ b/tests/Support/Stub/DQLQueryBuilder.php @@ -4,8 +4,16 @@ namespace Yiisoft\Db\Tests\Support\Stub; +use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\QueryBuilder\AbstractDQLQueryBuilder; final class DQLQueryBuilder extends AbstractDQLQueryBuilder { + protected function defaultExpressionBuilders(): array + { + return [ + ...parent::defaultExpressionBuilders(), + Expression::class => ExpressionBuilder::class, + ]; + } } diff --git a/tests/Support/Stub/ExpressionBuilder.php b/tests/Support/Stub/ExpressionBuilder.php new file mode 100644 index 000000000..add541b6b --- /dev/null +++ b/tests/Support/Stub/ExpressionBuilder.php @@ -0,0 +1,15 @@ +length - 1; + + while ($this->position < $length) { + $pos = $this->position++; + + match ($this->sql[$pos]) { + ':' => ($word = $this->parseWord()) === '' + ? $this->skipChars(':') + : $result = ':' . $word, + '"', "'" => $this->skipQuotedWithoutEscape($this->sql[$pos]), + '-' => $this->sql[$this->position] === '-' + ? ++$this->position && $this->skipToAfterChar("\n") + : null, + '/' => $this->sql[$this->position] === '*' + ? ++$this->position && $this->skipToAfterString('*/') + : null, + default => null, + }; + + if ($result !== null) { + $position = $pos; + + return $result; + } + } + + return null; + } +}