From 7d1ea958c3e81138fea10dec2abc3061b3aa237b Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 7 Sep 2026 19:50:47 +1000 Subject: [PATCH 1/3] Render length columns without a length instead of throwing ValueError Every AbstractLengthColumn subclass accepts a null length, but the fixed '%s %s(%s)' specification left vsprintf() with more placeholders than values whenever no length was set. Build the specification at render time instead: the length is inserted directly after the type only when one is set. Char, Binary, Decimal, Floating and Double now render bare without a length. Varchar and Varbinary, which SQL-92 and MySQL both reject without a length, throw InvalidArgumentException naming the column. A decimal scale without digits also throws instead of rendering "(,2)". Text and Blob keep ignoring any configured length. Fixes #176 Signed-off-by: Simon Mundy --- docs/book/sql-ddl/columns.md | 20 ++++++ src/Sql/Ddl/Column/AbstractLengthColumn.php | 37 ++++++++-- .../Ddl/Column/AbstractPrecisionColumn.php | 20 +++++- src/Sql/Ddl/Column/Blob.php | 13 +++- src/Sql/Ddl/Column/Text.php | 13 +++- src/Sql/Ddl/Column/Varbinary.php | 2 + src/Sql/Ddl/Column/Varchar.php | 2 + .../Ddl/Column/AbstractLengthColumnTest.php | 57 +++++++++++++++ .../Column/AbstractPrecisionColumnTest.php | 69 +++++++++++++++++++ test/unit/Sql/Ddl/Column/BinaryTest.php | 36 ++++++++++ test/unit/Sql/Ddl/Column/BlobTest.php | 27 ++++++++ test/unit/Sql/Ddl/Column/CharTest.php | 36 ++++++++++ .../Sql/Ddl/Column/ColumnAssertionsTrait.php | 33 +++++++++ test/unit/Sql/Ddl/Column/DecimalTest.php | 52 ++++++++++++++ test/unit/Sql/Ddl/Column/DoubleTest.php | 39 +++++++++++ test/unit/Sql/Ddl/Column/FloatingTest.php | 39 +++++++++++ test/unit/Sql/Ddl/Column/TextTest.php | 27 ++++++++ test/unit/Sql/Ddl/Column/VarbinaryTest.php | 27 ++++++++ test/unit/Sql/Ddl/Column/VarcharTest.php | 56 ++++++++------- 19 files changed, 569 insertions(+), 36 deletions(-) create mode 100644 test/unit/Sql/Ddl/Column/ColumnAssertionsTrait.php diff --git a/docs/book/sql-ddl/columns.md b/docs/book/sql-ddl/columns.md index 2d60e9ae..b34c9b47 100644 --- a/docs/book/sql-ddl/columns.md +++ b/docs/book/sql-ddl/columns.md @@ -86,6 +86,10 @@ $column->setDecimal(3); // Change scale - `setDecimal(int $decimal): self` - Set scale - `getDecimal(): int` - Get scale +Digits and scale are both optional. Without digits the column renders as a bare `DECIMAL`, +leaving the precision to the database default. A scale without digits cannot be rendered and +throws `PhpDb\Sql\Exception\InvalidArgumentException`. + ### Floating Floating-point numbers. @@ -102,6 +106,10 @@ $column->setDecimal(4); **Constructor:** `__construct($name, $digits, $decimal)` +Digits and scale are both optional and render the same way as for `Decimal`: without digits the +column renders as a bare `FLOAT`, and a scale without digits throws +`PhpDb\Sql\Exception\InvalidArgumentException`. + > The class is named `Floating` rather than `Float` because `float` is a reserved > keyword in PHP. @@ -142,6 +150,9 @@ __construct( - `setDefault(string|int|null $default): static` - `setOption(string $name, mixed $value): static` +A length is required. SQL-92 and MySQL both reject a bare `VARCHAR`, so rendering a `Varchar` +without a length throws `PhpDb\Sql\Exception\InvalidArgumentException` naming the column. + ### Char Fixed-length character string. @@ -153,6 +164,9 @@ $column = new Char('country_code', 2); // ISO country codes $column = new Char('status', 1); // Single character status ``` +The length is optional. Without one the column renders as a bare `CHAR`, which databases treat as +`CHAR(1)`. + **Constructor:** ```php @@ -203,6 +217,9 @@ use PhpDb\Sql\Ddl\Column\Binary; $column = new Binary('hash', 32); // 32-byte hash ``` +The length is optional. Without one the column renders as a bare `BINARY`, which databases treat as +`BINARY(1)`. + **Constructor:** ```php @@ -225,6 +242,9 @@ use PhpDb\Sql\Ddl\Column\Varbinary; $column = new Varbinary('file_data', 65535); ``` +A length is required. As with `Varchar`, rendering a `Varbinary` without a length throws +`PhpDb\Sql\Exception\InvalidArgumentException` naming the column. + **Constructor:** ```php diff --git a/src/Sql/Ddl/Column/AbstractLengthColumn.php b/src/Sql/Ddl/Column/AbstractLengthColumn.php index 3eeb65b4..eae34660 100644 --- a/src/Sql/Ddl/Column/AbstractLengthColumn.php +++ b/src/Sql/Ddl/Column/AbstractLengthColumn.php @@ -6,12 +6,19 @@ use Override; use PhpDb\Sql\Argument\Literal; +use PhpDb\Sql\Exception\InvalidArgumentException; use function array_splice; +use function sprintf; +use function strlen; +use function substr; abstract class AbstractLengthColumn extends Column { - protected string $specification = '%s %s(%s)'; + /** + * Whether the type is invalid without a length, as VARCHAR and VARBINARY are in SQL-92 and MySQL. + */ + protected bool $lengthRequired = false; protected ?int $length = null; @@ -27,16 +34,38 @@ public function __construct( parent::__construct($name, $nullable, $default, $options); } - /** @inheritDoc */ + /** + * Renders the length in parentheses directly after the type when one is set. Without a length + * the column renders bare, unless the type requires one. + * + * @inheritDoc + * @throws InvalidArgumentException When the type requires a length and none is set. + */ #[Override] public function getExpressionData(): array { + $lengthExpression = $this->getLengthExpression(); + $hasLength = '' !== $lengthExpression && '0' !== $lengthExpression; + + if (! $hasLength && $this->lengthRequired) { + throw new InvalidArgumentException(sprintf( + 'Column "%s" of type %s requires a length', + $this->name, + $this->type, + )); + } + $expressionData = parent::getExpressionData(); - if ($this->getLengthExpression() !== '' && $this->getLengthExpression() !== '0') { - array_splice($expressionData['values'], 2, 0, [new Literal($this->getLengthExpression())]); + if (! $hasLength) { + return $expressionData; } + $attributes = substr($expressionData['spec'], strlen($this->specification)); + + $expressionData['spec'] = "{$this->specification}(%s){$attributes}"; + array_splice($expressionData['values'], 2, 0, [new Literal($lengthExpression)]); + return $expressionData; } diff --git a/src/Sql/Ddl/Column/AbstractPrecisionColumn.php b/src/Sql/Ddl/Column/AbstractPrecisionColumn.php index 2930f209..5d37f29b 100644 --- a/src/Sql/Ddl/Column/AbstractPrecisionColumn.php +++ b/src/Sql/Ddl/Column/AbstractPrecisionColumn.php @@ -5,6 +5,9 @@ namespace PhpDb\Sql\Ddl\Column; use Override; +use PhpDb\Sql\Exception\InvalidArgumentException; + +use function sprintf; abstract class AbstractPrecisionColumn extends AbstractLengthColumn { @@ -45,13 +48,24 @@ public function setDigits(?int $digits): static return $this->setLength($digits); } + /** + * @throws InvalidArgumentException When a decimal scale is set without digits. + */ #[Override] protected function getLengthExpression(): string { - if (null !== $this->decimal) { - return "{$this->length},{$this->decimal}"; + if (null === $this->decimal) { + return (string) $this->length; + } + + if (null === $this->length) { + throw new InvalidArgumentException(sprintf( + 'Column "%s" of type %s has a decimal scale but no digits', + $this->name, + $this->type, + )); } - return (string) $this->length; + return "{$this->length},{$this->decimal}"; } } diff --git a/src/Sql/Ddl/Column/Blob.php b/src/Sql/Ddl/Column/Blob.php index 9980211f..929d6cbe 100644 --- a/src/Sql/Ddl/Column/Blob.php +++ b/src/Sql/Ddl/Column/Blob.php @@ -4,10 +4,19 @@ namespace PhpDb\Sql\Ddl\Column; +use Override; + class Blob extends AbstractLengthColumn { - protected string $specification = '%s %s'; - /** @var string Change type to blob */ protected string $type = 'BLOB'; + + /** + * BLOB takes no length, so a configured length is kept but never rendered. + */ + #[Override] + protected function getLengthExpression(): string + { + return ''; + } } diff --git a/src/Sql/Ddl/Column/Text.php b/src/Sql/Ddl/Column/Text.php index af608c3d..ec989744 100644 --- a/src/Sql/Ddl/Column/Text.php +++ b/src/Sql/Ddl/Column/Text.php @@ -4,9 +4,18 @@ namespace PhpDb\Sql\Ddl\Column; +use Override; + class Text extends AbstractLengthColumn { - protected string $specification = '%s %s'; - protected string $type = 'TEXT'; + + /** + * TEXT takes no length, so a configured length is kept but never rendered. + */ + #[Override] + protected function getLengthExpression(): string + { + return ''; + } } diff --git a/src/Sql/Ddl/Column/Varbinary.php b/src/Sql/Ddl/Column/Varbinary.php index 93cc54c1..8d98f08d 100644 --- a/src/Sql/Ddl/Column/Varbinary.php +++ b/src/Sql/Ddl/Column/Varbinary.php @@ -6,5 +6,7 @@ class Varbinary extends AbstractLengthColumn { + protected bool $lengthRequired = true; + protected string $type = 'VARBINARY'; } diff --git a/src/Sql/Ddl/Column/Varchar.php b/src/Sql/Ddl/Column/Varchar.php index 8c1c23f7..62681e7a 100644 --- a/src/Sql/Ddl/Column/Varchar.php +++ b/src/Sql/Ddl/Column/Varchar.php @@ -6,5 +6,7 @@ class Varchar extends AbstractLengthColumn { + protected bool $lengthRequired = true; + protected string $type = 'VARCHAR'; } diff --git a/test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php b/test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php index 5fe37436..373c48a4 100644 --- a/test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php +++ b/test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php @@ -8,14 +8,71 @@ use PhpDb\Sql\Argument\Literal; use PhpDb\Sql\Ddl\Column\AbstractLengthColumn; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\TestCase; #[CoversMethod(AbstractLengthColumn::class, 'setLength')] #[CoversMethod(AbstractLengthColumn::class, 'getLength')] +#[CoversMethod(AbstractLengthColumn::class, 'getLengthExpression')] #[CoversMethod(AbstractLengthColumn::class, 'getExpressionData')] +#[Group('unit')] final class AbstractLengthColumnTest extends TestCase { + use ColumnAssertionsTrait; + + /** + * @throws Exception + */ + #[Test] + public function getExpressionDataOmitsLengthPlaceholderWhenLengthIsNotSet(): void + { + $column = $this->getMockBuilder(AbstractLengthColumn::class) + ->setConstructorArgs(['foo']) + ->onlyMethods([]) + ->getMock(); + + $expressionData = $column->getExpressionData(); + + static::assertSame('%s %s NOT NULL', $expressionData['spec']); + static::assertEquals( + [ + new Identifier('foo'), + new Literal('INTEGER'), + ], + $expressionData['values'], + ); + } + + /** + * @throws Exception + */ + #[Test] + public function rendersLengthDirectlyAfterType(): void + { + $column = $this->getMockBuilder(AbstractLengthColumn::class) + ->setConstructorArgs(['foo', 4, true]) + ->onlyMethods([]) + ->getMock(); + + static::assertColumnRenders('"foo" INTEGER(4) NULL DEFAULT NULL', $column); + } + + /** + * @throws Exception + */ + #[Test] + public function rendersWithoutParenthesesWhenLengthIsNotSet(): void + { + $column = $this->getMockBuilder(AbstractLengthColumn::class) + ->setConstructorArgs(['foo']) + ->onlyMethods([]) + ->getMock(); + + static::assertColumnRenders('"foo" INTEGER NOT NULL', $column); + } + /** * @throws Exception */ diff --git a/test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php b/test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php index 459d7748..743fc301 100644 --- a/test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php +++ b/test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php @@ -6,7 +6,10 @@ use PhpDb\Sql\Argument; use PhpDb\Sql\Ddl\Column\AbstractPrecisionColumn; +use PhpDb\Sql\Exception\InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\TestCase; @@ -14,9 +17,58 @@ #[CoversMethod(AbstractPrecisionColumn::class, 'getDigits')] #[CoversMethod(AbstractPrecisionColumn::class, 'setDecimal')] #[CoversMethod(AbstractPrecisionColumn::class, 'getDecimal')] +#[CoversMethod(AbstractPrecisionColumn::class, 'getLengthExpression')] #[CoversMethod(AbstractPrecisionColumn::class, 'getExpressionData')] +#[Group('unit')] final class AbstractPrecisionColumnTest extends TestCase { + /** + * @throws Exception + */ + #[Test] + public function getExpressionDataOmitsLengthPlaceholderWhenPrecisionIsNotSet(): void + { + $column = $this->getMockBuilder(AbstractPrecisionColumn::class) + ->setConstructorArgs(['foo']) + ->onlyMethods([]) + ->getMock(); + + $expressionData = $column->getExpressionData(); + + static::assertSame('%s %s NOT NULL', $expressionData['spec']); + static::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('INTEGER'), + ], + $expressionData['values'], + ); + } + + /** + * @throws Exception + */ + #[Test] + public function getExpressionDataUsesDigitsOnlyWhenDecimalIsNotSet(): void + { + $column = $this->getMockBuilder(AbstractPrecisionColumn::class) + ->setConstructorArgs(['foo', 10]) + ->onlyMethods([]) + ->getMock(); + + $expressionData = $column->getExpressionData(); + + static::assertSame('%s %s(%s) NOT NULL', $expressionData['spec']); + static::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('INTEGER'), + Argument::literal('10'), + ], + $expressionData['values'], + ); + } + /** * @throws Exception */ @@ -91,4 +143,21 @@ public function testSetDigits(): void self::assertSame($column, $column->setDigits(12)); self::assertEquals(12, $column->getDigits()); } + + /** + * @throws Exception + */ + #[Test] + public function throwsWhenDecimalIsSetWithoutDigits(): void + { + $column = $this->getMockBuilder(AbstractPrecisionColumn::class) + ->setConstructorArgs(['foo', null, 2]) + ->onlyMethods([]) + ->getMock(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Column "foo" of type INTEGER has a decimal scale but no digits'); + + $column->getExpressionData(); + } } diff --git a/test/unit/Sql/Ddl/Column/BinaryTest.php b/test/unit/Sql/Ddl/Column/BinaryTest.php index 26e29fa5..6677118d 100644 --- a/test/unit/Sql/Ddl/Column/BinaryTest.php +++ b/test/unit/Sql/Ddl/Column/BinaryTest.php @@ -5,13 +5,49 @@ namespace PhpDbTest\Sql\Ddl\Column; use PhpDb\Sql\Argument; +use PhpDb\Sql\Ddl\Column\AbstractLengthColumn; use PhpDb\Sql\Ddl\Column\Binary; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Binary::class, 'getExpressionData')] +#[CoversMethod(AbstractLengthColumn::class, 'getExpressionData')] +#[Group('unit')] final class BinaryTest extends TestCase { + use ColumnAssertionsTrait; + + #[Test] + public function getExpressionDataOmitsLengthPlaceholderWhenLengthIsNotSet(): void + { + $expressionData = (new Binary('hash'))->getExpressionData(); + + static::assertSame('%s %s NOT NULL', $expressionData['spec']); + static::assertEquals( + [ + Argument::identifier('hash'), + Argument::literal('BINARY'), + ], + $expressionData['values'], + ); + } + + #[Test] + public function rendersLengthDirectlyAfterType(): void + { + static::assertColumnRenders('"hash" BINARY(32) NOT NULL', new Binary('hash', 32)); + } + + #[Test] + #[DataProvider('missingLengthProvider')] + public function rendersWithoutParenthesesWhenLengthIsNotSet(?int $length): void + { + static::assertColumnRenders('"hash" BINARY NOT NULL', new Binary('hash', $length)); + } + public function testGetExpressionData(): void { $column = new Binary('foo', 10_000_000); diff --git a/test/unit/Sql/Ddl/Column/BlobTest.php b/test/unit/Sql/Ddl/Column/BlobTest.php index 0cf51bc2..5e6dfee3 100644 --- a/test/unit/Sql/Ddl/Column/BlobTest.php +++ b/test/unit/Sql/Ddl/Column/BlobTest.php @@ -8,11 +8,38 @@ use PhpDb\Sql\Argument\Literal; use PhpDb\Sql\Ddl\Column\Blob; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Blob::class, 'getExpressionData')] +#[CoversMethod(Blob::class, 'getLengthExpression')] +#[Group('unit')] final class BlobTest extends TestCase { + use ColumnAssertionsTrait; + + #[Test] + public function getExpressionDataOmitsConfiguredLength(): void + { + $expressionData = (new Blob('foo', 16_777_215))->getExpressionData(); + + static::assertSame('%s %s NOT NULL', $expressionData['spec']); + static::assertEquals( + [ + new Identifier('foo'), + new Literal('BLOB'), + ], + $expressionData['values'], + ); + } + + #[Test] + public function rendersWithoutConfiguredLength(): void + { + static::assertColumnRenders('"foo" BLOB NOT NULL', new Blob('foo', 16_777_215)); + } + public function testGetExpressionData(): void { $column = new Blob('foo'); diff --git a/test/unit/Sql/Ddl/Column/CharTest.php b/test/unit/Sql/Ddl/Column/CharTest.php index cfcc1790..d6ff2f43 100644 --- a/test/unit/Sql/Ddl/Column/CharTest.php +++ b/test/unit/Sql/Ddl/Column/CharTest.php @@ -6,13 +6,49 @@ use PhpDb\Sql\Argument\Identifier; use PhpDb\Sql\Argument\Literal; +use PhpDb\Sql\Ddl\Column\AbstractLengthColumn; use PhpDb\Sql\Ddl\Column\Char; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Char::class, 'getExpressionData')] +#[CoversMethod(AbstractLengthColumn::class, 'getExpressionData')] +#[Group('unit')] final class CharTest extends TestCase { + use ColumnAssertionsTrait; + + #[Test] + public function getExpressionDataOmitsLengthPlaceholderWhenLengthIsNotSet(): void + { + $expressionData = (new Char('code'))->getExpressionData(); + + static::assertSame('%s %s NOT NULL', $expressionData['spec']); + static::assertEquals( + [ + new Identifier('code'), + new Literal('CHAR'), + ], + $expressionData['values'], + ); + } + + #[Test] + public function rendersLengthDirectlyAfterType(): void + { + static::assertColumnRenders('"code" CHAR(2) NOT NULL', new Char('code', 2)); + } + + #[Test] + #[DataProvider('missingLengthProvider')] + public function rendersWithoutParenthesesWhenLengthIsNotSet(?int $length): void + { + static::assertColumnRenders('"code" CHAR NOT NULL', new Char('code', $length)); + } + public function testGetExpressionData(): void { $column = new Char('foo', 20); diff --git a/test/unit/Sql/Ddl/Column/ColumnAssertionsTrait.php b/test/unit/Sql/Ddl/Column/ColumnAssertionsTrait.php new file mode 100644 index 00000000..57de9e25 --- /dev/null +++ b/test/unit/Sql/Ddl/Column/ColumnAssertionsTrait.php @@ -0,0 +1,33 @@ + + */ + public static function missingLengthProvider(): array + { + return [ + 'null length' => [null], + 'zero length' => [0], + ]; + } + + /** + * Asserts what a column renders to inside CREATE TABLE on the default SQL-92 platform. + */ + private static function assertColumnRenders(string $expected, ColumnInterface $column): void + { + $createTable = new CreateTable('t'); + $createTable->addColumn($column); + + self::assertSame("CREATE TABLE \"t\" ( \n {$expected} \n)", $createTable->getSqlString()); + } +} diff --git a/test/unit/Sql/Ddl/Column/DecimalTest.php b/test/unit/Sql/Ddl/Column/DecimalTest.php index bf89a47b..5f9aaba8 100644 --- a/test/unit/Sql/Ddl/Column/DecimalTest.php +++ b/test/unit/Sql/Ddl/Column/DecimalTest.php @@ -5,9 +5,13 @@ namespace PhpDbTest\Sql\Ddl\Column; use PhpDb\Sql\Argument; +use PhpDb\Sql\Ddl\Column\AbstractLengthColumn; use PhpDb\Sql\Ddl\Column\AbstractPrecisionColumn; use PhpDb\Sql\Ddl\Column\Decimal; +use PhpDb\Sql\Exception\InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Decimal::class, 'getExpressionData')] @@ -17,8 +21,45 @@ #[CoversMethod(AbstractPrecisionColumn::class, 'setDecimal')] #[CoversMethod(AbstractPrecisionColumn::class, 'getDecimal')] #[CoversMethod(AbstractPrecisionColumn::class, 'getLengthExpression')] +#[CoversMethod(AbstractLengthColumn::class, 'getExpressionData')] +#[Group('unit')] final class DecimalTest extends TestCase { + use ColumnAssertionsTrait; + + #[Test] + public function getExpressionDataOmitsLengthPlaceholderWhenPrecisionIsNotSet(): void + { + $expressionData = (new Decimal('amount'))->getExpressionData(); + + static::assertSame('%s %s NOT NULL', $expressionData['spec']); + static::assertEquals( + [ + Argument::identifier('amount'), + Argument::literal('DECIMAL'), + ], + $expressionData['values'], + ); + } + + #[Test] + public function rendersDigitsAndDecimalDirectlyAfterType(): void + { + static::assertColumnRenders('"price" DECIMAL(10,2) NOT NULL', new Decimal('price', 10, 2)); + } + + #[Test] + public function rendersDigitsOnlyWhenDecimalIsNotSet(): void + { + static::assertColumnRenders('"price" DECIMAL(10) NOT NULL', new Decimal('price', 10)); + } + + #[Test] + public function rendersWithoutParenthesesWhenPrecisionIsNotSet(): void + { + static::assertColumnRenders('"price" DECIMAL NOT NULL', new Decimal('price')); + } + public function testConstructorSetsDigitsAndDecimal(): void { $column = new Decimal('price', 10, 2); @@ -83,4 +124,15 @@ public function testSetDigitsAndGetDigits(): void self::assertSame($column, $result); // Fluent interface self::assertEquals(15, $column->getDigits()); } + + #[Test] + public function throwsWhenDecimalIsSetWithoutDigits(): void + { + $column = new Decimal('price', null, 2); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Column "price" of type DECIMAL has a decimal scale but no digits'); + + $column->getExpressionData(); + } } diff --git a/test/unit/Sql/Ddl/Column/DoubleTest.php b/test/unit/Sql/Ddl/Column/DoubleTest.php index 7901a81a..548b1b2c 100644 --- a/test/unit/Sql/Ddl/Column/DoubleTest.php +++ b/test/unit/Sql/Ddl/Column/DoubleTest.php @@ -5,13 +5,41 @@ namespace PhpDbTest\Sql\Ddl\Column; use PhpDb\Sql\Argument; +use PhpDb\Sql\Ddl\Column\AbstractLengthColumn; +use PhpDb\Sql\Ddl\Column\AbstractPrecisionColumn; use PhpDb\Sql\Ddl\Column\Double; +use PhpDb\Sql\Exception\InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Double::class, 'getExpressionData')] +#[CoversMethod(AbstractPrecisionColumn::class, 'getLengthExpression')] +#[CoversMethod(AbstractLengthColumn::class, 'getExpressionData')] +#[Group('unit')] final class DoubleTest extends TestCase { + use ColumnAssertionsTrait; + + #[Test] + public function rendersDigitsAndDecimalDirectlyAfterType(): void + { + static::assertColumnRenders('"f" DOUBLE(10,5) NOT NULL', new Double('f', 10, 5)); + } + + #[Test] + public function rendersDigitsOnlyWhenDecimalIsNotSet(): void + { + static::assertColumnRenders('"f" DOUBLE(10) NOT NULL', new Double('f', 10)); + } + + #[Test] + public function rendersWithoutParenthesesWhenPrecisionIsNotSet(): void + { + static::assertColumnRenders('"f" DOUBLE NOT NULL', new Double('f')); + } + public function testGetExpressionData(): void { $column = new Double('foo', 10, 5); @@ -28,4 +56,15 @@ public function testGetExpressionData(): void $expressionData['values'], ); } + + #[Test] + public function throwsWhenDecimalIsSetWithoutDigits(): void + { + $column = new Double('f', null, 2); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Column "f" of type DOUBLE has a decimal scale but no digits'); + + $column->getExpressionData(); + } } diff --git a/test/unit/Sql/Ddl/Column/FloatingTest.php b/test/unit/Sql/Ddl/Column/FloatingTest.php index 14f439d2..cfafef7b 100644 --- a/test/unit/Sql/Ddl/Column/FloatingTest.php +++ b/test/unit/Sql/Ddl/Column/FloatingTest.php @@ -5,13 +5,41 @@ namespace PhpDbTest\Sql\Ddl\Column; use PhpDb\Sql\Argument; +use PhpDb\Sql\Ddl\Column\AbstractLengthColumn; +use PhpDb\Sql\Ddl\Column\AbstractPrecisionColumn; use PhpDb\Sql\Ddl\Column\Floating; +use PhpDb\Sql\Exception\InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Floating::class, 'getExpressionData')] +#[CoversMethod(AbstractPrecisionColumn::class, 'getLengthExpression')] +#[CoversMethod(AbstractLengthColumn::class, 'getExpressionData')] +#[Group('unit')] final class FloatingTest extends TestCase { + use ColumnAssertionsTrait; + + #[Test] + public function rendersDigitsAndDecimalDirectlyAfterType(): void + { + static::assertColumnRenders('"f" FLOAT(10,5) NOT NULL', new Floating('f', 10, 5)); + } + + #[Test] + public function rendersDigitsOnlyWhenDecimalIsNotSet(): void + { + static::assertColumnRenders('"f" FLOAT(10) NOT NULL', new Floating('f', 10)); + } + + #[Test] + public function rendersWithoutParenthesesWhenPrecisionIsNotSet(): void + { + static::assertColumnRenders('"f" FLOAT NOT NULL', new Floating('f')); + } + public function testGetExpressionData(): void { $column = new Floating('foo', 10, 5); @@ -28,4 +56,15 @@ public function testGetExpressionData(): void $expressionData['values'], ); } + + #[Test] + public function throwsWhenDecimalIsSetWithoutDigits(): void + { + $column = new Floating('f', null, 2); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Column "f" of type FLOAT has a decimal scale but no digits'); + + $column->getExpressionData(); + } } diff --git a/test/unit/Sql/Ddl/Column/TextTest.php b/test/unit/Sql/Ddl/Column/TextTest.php index 548a7100..f718c3b9 100644 --- a/test/unit/Sql/Ddl/Column/TextTest.php +++ b/test/unit/Sql/Ddl/Column/TextTest.php @@ -7,11 +7,38 @@ use PhpDb\Sql\Argument; use PhpDb\Sql\Ddl\Column\Text; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Text::class, 'getExpressionData')] +#[CoversMethod(Text::class, 'getLengthExpression')] +#[Group('unit')] final class TextTest extends TestCase { + use ColumnAssertionsTrait; + + #[Test] + public function getExpressionDataOmitsConfiguredLength(): void + { + $expressionData = (new Text('foo', 65_535))->getExpressionData(); + + static::assertSame('%s %s NOT NULL', $expressionData['spec']); + static::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('TEXT'), + ], + $expressionData['values'], + ); + } + + #[Test] + public function rendersWithoutConfiguredLength(): void + { + static::assertColumnRenders('"foo" TEXT NOT NULL', new Text('foo', 65_535)); + } + public function testGetExpressionData(): void { $column = new Text('foo'); diff --git a/test/unit/Sql/Ddl/Column/VarbinaryTest.php b/test/unit/Sql/Ddl/Column/VarbinaryTest.php index 33a28336..bd660b58 100644 --- a/test/unit/Sql/Ddl/Column/VarbinaryTest.php +++ b/test/unit/Sql/Ddl/Column/VarbinaryTest.php @@ -5,13 +5,28 @@ namespace PhpDbTest\Sql\Ddl\Column; use PhpDb\Sql\Argument; +use PhpDb\Sql\Ddl\Column\AbstractLengthColumn; use PhpDb\Sql\Ddl\Column\Varbinary; +use PhpDb\Sql\Exception\InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Varbinary::class, 'getExpressionData')] +#[CoversMethod(AbstractLengthColumn::class, 'getExpressionData')] +#[Group('unit')] final class VarbinaryTest extends TestCase { + use ColumnAssertionsTrait; + + #[Test] + public function rendersLengthDirectlyAfterType(): void + { + static::assertColumnRenders('"data" VARBINARY(20) NOT NULL', new Varbinary('data', 20)); + } + public function testGetExpressionData(): void { $column = new Varbinary('foo', 20); @@ -28,4 +43,16 @@ public function testGetExpressionData(): void $expressionData['values'], ); } + + #[Test] + #[DataProvider('missingLengthProvider')] + public function throwsWhenRenderedWithoutLength(?int $length): void + { + $column = new Varbinary('data', $length); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Column "data" of type VARBINARY requires a length'); + + $column->getExpressionData(); + } } diff --git a/test/unit/Sql/Ddl/Column/VarcharTest.php b/test/unit/Sql/Ddl/Column/VarcharTest.php index 869a0d3a..5f9ffcc9 100644 --- a/test/unit/Sql/Ddl/Column/VarcharTest.php +++ b/test/unit/Sql/Ddl/Column/VarcharTest.php @@ -7,7 +7,11 @@ use PhpDb\Sql\Argument; use PhpDb\Sql\Ddl\Column\AbstractLengthColumn; use PhpDb\Sql\Ddl\Column\Varchar; +use PhpDb\Sql\Exception\InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; #[CoversMethod(Varchar::class, 'getExpressionData')] @@ -16,8 +20,23 @@ #[CoversMethod(AbstractLengthColumn::class, 'getLength')] #[CoversMethod(AbstractLengthColumn::class, 'getLengthExpression')] #[CoversMethod(AbstractLengthColumn::class, 'getExpressionData')] +#[Group('unit')] final class VarcharTest extends TestCase { + use ColumnAssertionsTrait; + + #[Test] + public function rendersLengthBeforeNullabilityAndDefault(): void + { + static::assertColumnRenders('"name" VARCHAR(20) NULL DEFAULT NULL', new Varchar('name', 20, true)); + } + + #[Test] + public function rendersLengthDirectlyAfterType(): void + { + static::assertColumnRenders('"name" VARCHAR(20) NOT NULL', new Varchar('name', 20)); + } + public function testGetExpressionData(): void { $column = new Varchar('foo', 20); @@ -50,31 +69,6 @@ public function testGetExpressionData(): void ); } - public function testGetExpressionDataWithNullLength(): void - { - $column = new Varchar('name'); - - $expressionData = $column->getExpressionData(); - - // When length is null, getLengthExpression() returns empty string - // The condition in getExpressionData checks: getLengthExpression() !== '' && !== '0' - // Empty string fails the first check, so length value is NOT added - // But specification still has (%s) placeholder - need to verify actual behavior - $spec = $expressionData['spec']; - $values = $expressionData['values']; - - // The specification format is defined in AbstractLengthColumn as '%s %s(%s)' - // But when length value is not added, we need to check if placeholder remains - self::assertEquals('%s %s(%s) NOT NULL', $spec); - self::assertEquals( - [ - Argument::identifier('name'), - Argument::literal('VARCHAR'), - ], - $values, - ); - } - public function testInheritanceFromAbstractLengthColumn(): void { $column = new Varchar('test'); @@ -89,4 +83,16 @@ public function testSetLengthAndGetLength(): void self::assertSame($column, $result); // Fluent interface self::assertEquals(100, $column->getLength()); } + + #[Test] + #[DataProvider('missingLengthProvider')] + public function throwsWhenRenderedWithoutLength(?int $length): void + { + $column = new Varchar('name', $length); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Column "name" of type VARCHAR requires a length'); + + $column->getExpressionData(); + } } From 1a35abfdb15a39140217bb3158e853c9cbd15fa7 Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 7 Sep 2026 21:14:53 +1000 Subject: [PATCH 2/3] Keep the QA toolchain green under Mago 1.47.6 CI installs the newest Mago, and 1.47.5 changed how the analyzer handles nested array shapes: prepareDataHierarchy() re-binds a reference into the MetadataData shape on every loop iteration, which no longer completes in useful time. Re-type the reference as a plain array so the shape is not re-derived per iteration, and adjust the expected findings accordingly. Regenerate the analyzer baseline for the reworded findings in Select, PredicateSet, Profiler and AbstractSql (the issues are unchanged, the messages are not), and reduce the continuation indent of two multi-line conditions in AbstractTableGateway to match the 1.47.4+ formatter. Signed-off-by: Simon Mundy --- analyzer-baseline.toml | 54 +++++------------------ src/Metadata/Source/AbstractSource.php | 10 +++-- src/TableGateway/AbstractTableGateway.php | 8 ++-- 3 files changed, 22 insertions(+), 50 deletions(-) diff --git a/analyzer-baseline.toml b/analyzer-baseline.toml index 26938f84..ae0b6959 100644 --- a/analyzer-baseline.toml +++ b/analyzer-baseline.toml @@ -1173,7 +1173,7 @@ count = 1 [[issues]] file = "src/Adapter/Profiler/Profiler.php" code = "invalid-property-assignment-value" -message = '''Invalid type for property `$profiles`: expected `array`, but got `array`.''' +message = '''Invalid type for property `$profiles`: expected `array`, but got `non-empty-array`.''' count = 1 [[issues]] @@ -1194,24 +1194,6 @@ code = "missing-property-type" message = "Property `$profiles` is missing a type hint." count = 1 -[[issues]] -file = "src/Adapter/Profiler/Profiler.php" -code = "possibly-null-operand" -message = "Right operand in arithmetic operation might be `null` (type `float|null`)." -count = 1 - -[[issues]] -file = "src/Adapter/Profiler/Profiler.php" -code = "possibly-undefined-int-array-index" -message = '''Possibly undefined array key `int` accessed on `array`.''' -count = 1 - -[[issues]] -file = "src/Adapter/Profiler/Profiler.php" -code = "possibly-undefined-string-array-index" -message = '''Possibly undefined array key accessed on `array{'elapse': float|null, 'end': float, 'parameters': PhpDb\Adapter\ParameterContainer|null, 'sql': string, 'start': float}|array{'end': float}`.''' -count = 1 - [[issues]] file = "src/Adapter/Profiler/Profiler.php" code = "unhandled-thrown-type" @@ -2289,7 +2271,7 @@ count = 1 [[issues]] file = "src/Sql/AbstractSql.php" code = "less-specific-nested-argument-type" -message = "Argument type mismatch for argument #2 of `vsprintf`: expected `array`, but provided type `array{}|non-empty-list` is less specific." +message = "Argument type mismatch for argument #2 of `vsprintf`: expected `array`, but provided type `list` is less specific." count = 1 [[issues]] @@ -3321,7 +3303,13 @@ count = 1 [[issues]] file = "src/Sql/Ddl/CreateTable.php" code = "possibly-undefined-string-array-index" -message = "Possibly undefined array key `string('combinedBy')` accessed on `array|string>`." +message = "Possibly undefined array key `string('combinedBy')` accessed on `array>`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('combinedBy')` accessed on `array`." count = 1 [[issues]] @@ -4353,7 +4341,7 @@ count = 1 [[issues]] file = "src/Sql/Predicate/PredicateSet.php" code = "less-specific-nested-argument-type" -message = "Argument type mismatch for argument #2 of `implode`: expected `array|null`, but provided type `array{}|non-empty-list` is less specific." +message = "Argument type mismatch for argument #2 of `implode`: expected `array|null`, but provided type `list` is less specific." count = 1 [[issues]] @@ -4365,7 +4353,7 @@ count = 1 [[issues]] file = "src/Sql/Predicate/PredicateSet.php" code = "less-specific-nested-return-statement" -message = '''Returned type `array{'spec': string, 'values': array{}|list}` is less specific than the declared return type `array{'spec': string, 'values': array}` for function `PhpDb\Sql\Predicate\PredicateSet::getExpressionData` due to nested 'mixed'.''' +message = '''Returned type `array{'spec': string, 'values': list}` is less specific than the declared return type `array{'spec': string, 'values': array}` for function `PhpDb\Sql\Predicate\PredicateSet::getExpressionData` due to nested 'mixed'.''' count = 1 [[issues]] @@ -4623,7 +4611,7 @@ count = 1 [[issues]] file = "src/Sql/Select.php" code = "invalid-property-assignment-value" -message = "Invalid type for property `$specifications`: expected `array>|array`, but got `array|string>`." +message = "Invalid type for property `$specifications`: expected `array>|array`, but got `non-empty-array|string>`." count = 1 [[issues]] @@ -5898,30 +5886,12 @@ code = "class-must-be-final" message = 'Class `PhpDb\TableGateway\Feature\GlobalAdapterFeature` should be declared `final`.' count = 1 -[[issues]] -file = "src/TableGateway/Feature/GlobalAdapterFeature.php" -code = "invalid-return-statement" -message = 'Invalid return type for function `PhpDb\TableGateway\Feature\GlobalAdapterFeature::getStaticAdapter`: expected `PhpDb\Adapter\AdapterInterface`, but found `PhpDb\Adapter\AdapterInterface|null`.' -count = 1 - [[issues]] file = "src/TableGateway/Feature/GlobalAdapterFeature.php" code = "missing-constructor" message = 'Class `PhpDb\TableGateway\Feature\GlobalAdapterFeature` has typed properties without default values but no constructor to initialize them.' count = 1 -[[issues]] -file = "src/TableGateway/Feature/GlobalAdapterFeature.php" -code = "nullable-return-statement" -message = 'Function `PhpDb\TableGateway\Feature\GlobalAdapterFeature::getStaticAdapter` is declared to return `PhpDb\Adapter\AdapterInterface` but possibly returns a nullable value (inferred as `PhpDb\Adapter\AdapterInterface|null`).' -count = 1 - -[[issues]] -file = "src/TableGateway/Feature/GlobalAdapterFeature.php" -code = "possibly-undefined-string-array-index" -message = '''Possibly undefined array key `class-string('PhpDb\TableGateway\Feature\GlobalAdapterFeature')` accessed on `array`.''' -count = 1 - [[issues]] file = "src/TableGateway/Feature/MasterSlaveFeature.php" code = "class-must-be-final" diff --git a/src/Metadata/Source/AbstractSource.php b/src/Metadata/Source/AbstractSource.php index 1c6c333e..7552e872 100644 --- a/src/Metadata/Source/AbstractSource.php +++ b/src/Metadata/Source/AbstractSource.php @@ -693,15 +693,17 @@ protected function loadTriggerData(string $schema): void * Prepare data hierarchy * * The by-reference walk builds arbitrary depths of the hierarchy, which - * cannot be expressed against the MetadataData shape. + * cannot be expressed against the MetadataData shape. The reference is + * re-typed as a plain array so the analyzer does not re-derive the shape + * on every iteration, which does not terminate in useful time on Mago + * 1.47.5 and later. * - * @mago-expect analysis:possibly-undefined-string-array-index - * @mago-expect analysis:possibly-undefined-int-array-index - * @mago-expect analysis:possibly-null-array-access + * @mago-expect analysis:mixed-assignment * @mago-expect lint:no-isset */ protected function prepareDataHierarchy(string $type, string ...$keys): void { + /** @var array $data */ $data = &$this->data; foreach ([$type, ...$keys] as $key) { if (! isset($data[$key])) { diff --git a/src/TableGateway/AbstractTableGateway.php b/src/TableGateway/AbstractTableGateway.php index 808a1a72..73c38bbf 100644 --- a/src/TableGateway/AbstractTableGateway.php +++ b/src/TableGateway/AbstractTableGateway.php @@ -347,8 +347,8 @@ protected function executeSelect(Select $select): ResultSetInterface if ( isset($selectState['columns']) - && [Select::SQL_STAR] === $selectState['columns'] - && [] !== $this->columns + && [Select::SQL_STAR] === $selectState['columns'] + && [] !== $this->columns ) { $select->columns($this->columns); } @@ -430,8 +430,8 @@ public function __clone(): void $this->table = clone $this->table; } elseif ( is_array($this->table) - && count($this->table) === 1 - && is_object(reset($this->table)) + && count($this->table) === 1 + && is_object(reset($this->table)) ) { foreach ($this->table as &$tableObject) { $tableObject = clone $tableObject; From a94a1b9e4a544e1860674b6b3b24dcdb63a038dd Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 28 Sep 2026 11:25:04 +1000 Subject: [PATCH 3/3] Fixed Mago baselines --- analyzer-baseline.toml | 34 +++++++++++----------------------- lint-baseline.toml | 22 ++-------------------- 2 files changed, 13 insertions(+), 43 deletions(-) diff --git a/analyzer-baseline.toml b/analyzer-baseline.toml index ae0b6959..58f2a6a0 100644 --- a/analyzer-baseline.toml +++ b/analyzer-baseline.toml @@ -2226,12 +2226,6 @@ code = "invalid-iterator" message = "The expression provided to `foreach` is not iterable. It resolved to type `mixed`, which is not iterable." count = 1 -[[issues]] -file = "src/Sql/AbstractSql.php" -code = "invalid-member-selector" -message = "Invalid type `array-key` used as a member selector; a string is expected." -count = 1 - [[issues]] file = "src/Sql/AbstractSql.php" code = "invalid-operand" @@ -2460,6 +2454,12 @@ code = "string-member-selector" message = "This member selector uses a non-literal string type (`non-empty-string`); its specific value cannot be statically determined." count = 1 +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "string-member-selector" +message = "This member selector uses a non-literal string type (`string`); its specific value cannot be statically determined." +count = 1 + [[issues]] file = "src/Sql/AbstractSql.php" code = "unhandled-thrown-type" @@ -3990,12 +3990,6 @@ code = "class-must-be-final" message = 'Class `PhpDb\Sql\Platform\Platform` should be declared `final`.' count = 1 -[[issues]] -file = "src/Sql/Platform/Platform.php" -code = "imprecise-type" -message = "Type `array` in return type of `getDecorators` is imprecise, equivalent to `array`." -count = 1 - [[issues]] file = "src/Sql/Platform/Platform.php" code = "invalid-iterator" @@ -4608,12 +4602,6 @@ code = "invalid-property-assignment-value" message = 'Invalid type for property `$joins`: expected `PhpDb\Sql\Join`, but got `null`.' count = 1 -[[issues]] -file = "src/Sql/Select.php" -code = "invalid-property-assignment-value" -message = "Invalid type for property `$specifications`: expected `array>|array`, but got `non-empty-array|string>`." -count = 1 - [[issues]] file = "src/Sql/Select.php" code = "invalid-property-assignment-value" @@ -5440,19 +5428,19 @@ count = 1 file = "src/TableGateway/AbstractTableGateway.php" code = "mixed-argument" message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Delete::from`: expected `PhpDb\Sql\TableIdentifier|array|string`, but found `mixed`.' -count = 2 +count = 1 [[issues]] file = "src/TableGateway/AbstractTableGateway.php" code = "mixed-argument" message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Insert::into`: expected `PhpDb\Sql\TableIdentifier|array|string`, but found `mixed`.' -count = 2 +count = 1 [[issues]] file = "src/TableGateway/AbstractTableGateway.php" code = "mixed-argument" message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Update::table`: expected `PhpDb\Sql\TableIdentifier|array|string`, but found `mixed`.' -count = 2 +count = 1 [[issues]] file = "src/TableGateway/AbstractTableGateway.php" @@ -5482,7 +5470,7 @@ count = 1 file = "src/TableGateway/AbstractTableGateway.php" code = "mixed-array-access" message = "Unsafe array access on type `mixed`." -count = 6 +count = 4 [[issues]] file = "src/TableGateway/AbstractTableGateway.php" @@ -5596,7 +5584,7 @@ count = 1 file = "src/TableGateway/AbstractTableGateway.php" code = "possibly-undefined-string-array-index" message = "Possibly undefined array key `string('table')` accessed on `array`." -count = 2 +count = 1 [[issues]] file = "src/TableGateway/AbstractTableGateway.php" diff --git a/lint-baseline.toml b/lint-baseline.toml index d4aa363a..df1bb821 100644 --- a/lint-baseline.toml +++ b/lint-baseline.toml @@ -1374,18 +1374,6 @@ code = "assertion-style" message = "Inconsistent assertions style." count = 10 -[[issues]] -file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" -code = "no-redundant-variable" -message = "Variable `$container` is assigned but never used." -count = 1 - -[[issues]] -file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" -code = "no-redundant-variable" -message = "Variable `$pluginManagerConfig` is assigned but never used." -count = 1 - [[issues]] file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" code = "prefer-test-attribute" @@ -5146,13 +5134,7 @@ count = 1 file = "test/unit/Sql/Ddl/Column/VarcharTest.php" code = "assertion-style" message = "Inconsistent assertions style." -count = 9 - -[[issues]] -file = "test/unit/Sql/Ddl/Column/VarcharTest.php" -code = "prefer-test-attribute" -message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithNullLength`." -count = 1 +count = 7 [[issues]] file = "test/unit/Sql/Ddl/Column/VarcharTest.php" @@ -5176,7 +5158,7 @@ count = 1 file = "test/unit/Sql/Ddl/Column/VarcharTest.php" code = "strict-assertions" message = "Use strict assertions in PHPUnit tests." -count = 4 +count = 3 [[issues]] file = "test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php"