Skip to content

Commit 3b9bc11

Browse files
authored
Merge pull request #6646 from morozov/object-names
Introduce value objects representing database object names
2 parents b299d3b + 3349cdd commit 3b9bc11

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1339
-132
lines changed

UPGRADE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ awareness about deprecated code.
88

99
# Upgrade to 4.3
1010

11+
## Deprecated using invalid database object names
12+
13+
Using the following objects with an empty name is deprecated: `Column`, `View`, `Sequence`, `Identifier`.
14+
15+
Using the following objects with a qualified name is deprecated: `Column`, `ForeignKeyConstraint`, `Index`, `Schema`,
16+
`UniqueConstraint`. If the object name contains a dot, the name should be quoted.
17+
18+
Using the following objects with a name that has more than one qualifier is deprecated: `Sequence`, `Table`, `View`.
19+
The name should be unqualified or contain one qualifier.
20+
21+
The `AbstractAsset` class has been marked as internal.
22+
1123
## Deprecated configuration-related `Table` methods
1224

1325
The `Table::setSchemaConfig()` method and `$_schemaConfig` property have been deprecated. Pass a `TableConfiguration`

phpcs.xml.dist

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
<!-- see https://github.com/squizlabs/PHP_CodeSniffer/issues/2099 -->
7777
<rule ref="Squiz.Commenting.FunctionComment.InvalidNoReturn">
7878
<exclude-pattern>src/Platforms/AbstractPlatform.php</exclude-pattern>
79+
<exclude-pattern>src/Schema/AbstractAsset.php</exclude-pattern>
7980
<exclude-pattern>src/Schema/AbstractSchemaManager.php</exclude-pattern>
8081
<exclude-pattern>tests/Platforms/AbstractPlatformTestCase.php</exclude-pattern>
8182
</rule>
@@ -128,13 +129,14 @@
128129
<exclude-pattern>src/Driver/SQLSrv/Statement.php</exclude-pattern>
129130
</rule>
130131

131-
<!-- This could be considered a bug in the sniff or the shortcoming of the Platform API design
132+
<!-- False positives caused by the limitation of PHP_CodeSniffer
132133
see https://github.com/squizlabs/PHP_CodeSniffer/issues/3035 -->
133134
<rule ref="Generic.CodeAnalysis.UselessOverridingMethod.Found">
134-
<exclude-pattern>src/Platforms/SQLitePlatform.php</exclude-pattern>
135135
<exclude-pattern>src/Platforms/*/Comparator.php</exclude-pattern>
136136
<exclude-pattern>src/Driver/PDO/SQLSrv/Connection.php</exclude-pattern>
137137
<exclude-pattern>src/Driver/PDO/SQLSrv/Statement.php</exclude-pattern>
138+
<exclude-pattern>src/Schema/AbstractNamedObject.php</exclude-pattern>
139+
<exclude-pattern>src/Schema/Name/UnqualifiedName.php</exclude-pattern>
138140
</rule>
139141

140142
<!-- This issue is likely fixed in Slevomat Coding Standard 8.0.0 -->

psalm.xml.dist

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@
115115
-->
116116
<referencedMethod name="Doctrine\DBAL\Schema\Table::_getMaxIdentifierLength" />
117117
<referencedMethod name="Doctrine\DBAL\Schema\Table::setSchemaConfig" />
118+
119+
<!--
120+
https://github.com/doctrine/dbal/pull/6646
121+
TODO: remove in 5.0.0
122+
-->
123+
<referencedMethod name="Doctrine\DBAL\Schema\AbstractAsset::getNameParser" />
124+
<referencedMethod name="Doctrine\DBAL\Schema\AbstractAsset::setName" />
118125
</errorLevel>
119126
</DeprecatedMethod>
120127
<DeprecatedProperty>
@@ -206,6 +213,14 @@
206213
<file name="tests/Driver/PDO/*/DriverTest.php"/>
207214
<file name="tests/Functional/Driver/Mysqli/ConnectionTest.php"/>
208215
<file name="tests/Platforms/AbstractPlatformTestCase.php"/>
216+
217+
<!--
218+
https://github.com/doctrine/dbal/pull/6646
219+
TODO: remove in 5.0.0
220+
221+
This looks like a Psalm bug.
222+
-->
223+
<referencedFunction name="Doctrine\DBAL\Schema\AbstractAsset::setName"/>
209224
</errorLevel>
210225
</InvalidArgument>
211226
<InvalidArrayOffset>
@@ -312,6 +327,21 @@
312327
</PossiblyFalseReference>
313328
<PropertyNotSetInConstructor>
314329
<errorLevel type="suppress">
330+
<!--
331+
https://github.com/doctrine/dbal/pull/6646
332+
TODO: remove in 5.0.0
333+
-->
334+
<referencedProperty name="Doctrine\DBAL\Schema\AbstractAsset::$name"/>
335+
336+
<!--
337+
https://github.com/doctrine/dbal/pull/6646
338+
TODO: remove in 5.0.0
339+
340+
For some reason, referencing the property name doesn't work. Furthermore,
341+
the class doesn't reference the property.
342+
-->
343+
<file name="src/Schema/Table.php"/>
344+
315345
<!-- See https://github.com/psalm/psalm-plugin-phpunit/issues/107 -->
316346
<!-- See https://github.com/sebastianbergmann/phpunit/pull/4610 -->
317347
<directory name="tests"/>

src/Schema/AbstractAsset.php

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
namespace Doctrine\DBAL\Schema;
66

77
use Doctrine\DBAL\Platforms\AbstractPlatform;
8+
use Doctrine\DBAL\Schema\Exception\NotImplemented;
9+
use Doctrine\DBAL\Schema\Name\GenericName;
10+
use Doctrine\DBAL\Schema\Name\Identifier;
11+
use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName;
812
use Doctrine\DBAL\Schema\Name\Parser;
9-
use Doctrine\DBAL\Schema\Name\Parser\Identifier;
13+
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
1014
use Doctrine\Deprecations\Deprecation;
15+
use Throwable;
1116

1217
use function array_map;
1318
use function count;
@@ -28,11 +33,20 @@
2833
* This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables
2934
* array($tableName => Table($tableName)); if you want to rename the table, you have to make sure this does not get
3035
* recreated during schema migration.
36+
*
37+
* @internal This class should be extended only by DBAL itself.
38+
*
39+
* @template N of Name
3140
*/
3241
abstract class AbstractAsset
3342
{
3443
protected string $_name = '';
3544

45+
/**
46+
* Indicates whether the object name has been initialized.
47+
*/
48+
protected bool $isNameInitialized = false;
49+
3650
/**
3751
* Namespace of the asset. If none isset the default namespace is assumed.
3852
*/
@@ -61,13 +75,39 @@ public function __construct(?string $name = null)
6175
$this->_setName($name);
6276
}
6377

78+
/**
79+
* Returns a parser for parsing the object name.
80+
*
81+
* @deprecated Parse the name in the constructor instead.
82+
*
83+
* @return Parser<N>
84+
*/
85+
protected function getNameParser(): Parser
86+
{
87+
throw NotImplemented::fromMethod(static::class, __FUNCTION__);
88+
}
89+
90+
/**
91+
* Sets the object name.
92+
*
93+
* @deprecated Set the name in the constructor instead.
94+
*
95+
* @param ?N $name
96+
*/
97+
protected function setName(?Name $name): void
98+
{
99+
throw NotImplemented::fromMethod(static::class, __FUNCTION__);
100+
}
101+
64102
/**
65103
* Sets the name of this asset.
66104
*
67105
* @deprecated Use the constructor instead.
68106
*/
69107
protected function _setName(string $name): void
70108
{
109+
$this->isNameInitialized = false;
110+
71111
Deprecation::triggerIfCalledFromOutside(
72112
'doctrine/dbal',
73113
'https://github.com/doctrine/dbal/pull/6610',
@@ -93,11 +133,9 @@ protected function _setName(string $name): void
93133
$this->validateFuture = false;
94134

95135
if ($input !== '') {
96-
$parser = new Parser();
97-
98136
try {
99-
$identifiers = $parser->parse($input);
100-
} catch (Parser\Exception $e) {
137+
$parsedName = $this->getNameParser()->parse($input);
138+
} catch (Throwable $e) {
101139
Deprecation::trigger(
102140
'doctrine/dbal',
103141
'https://github.com/doctrine/dbal/pull/6592',
@@ -108,14 +146,46 @@ protected function _setName(string $name): void
108146
return;
109147
}
110148
} else {
111-
$identifiers = [];
149+
$parsedName = null;
112150
}
113151

114-
switch (count($identifiers)) {
115-
case 0:
116-
$this->identifiers = [];
152+
try {
153+
$this->setName($parsedName);
154+
} catch (Throwable $e) {
155+
Deprecation::trigger(
156+
'doctrine/dbal',
157+
'https://github.com/doctrine/dbal/pull/6646',
158+
'Using invalid database object names is deprecated: %s.',
159+
$e->getMessage(),
160+
);
117161

118-
return;
162+
return;
163+
}
164+
165+
$this->isNameInitialized = true;
166+
167+
if ($parsedName === null) {
168+
$this->identifiers = [];
169+
170+
return;
171+
}
172+
173+
if ($parsedName instanceof UnqualifiedName) {
174+
$identifiers = [$parsedName->getIdentifier()];
175+
} elseif ($parsedName instanceof OptionallyQualifiedName) {
176+
$unqualifiedName = $parsedName->getUnqualifiedName();
177+
$qualifier = $parsedName->getQualifier();
178+
179+
$identifiers = $qualifier !== null
180+
? [$qualifier, $unqualifiedName]
181+
: [$unqualifiedName];
182+
} elseif ($parsedName instanceof GenericName) {
183+
$identifiers = $parsedName->getIdentifiers();
184+
} else {
185+
return;
186+
}
187+
188+
switch (count($identifiers)) {
119189
case 1:
120190
$namespace = null;
121191
$name = $identifiers[0];
@@ -127,13 +197,6 @@ protected function _setName(string $name): void
127197
break;
128198

129199
default:
130-
Deprecation::trigger(
131-
'doctrine/dbal',
132-
'https://github.com/doctrine/dbal/pull/6592',
133-
'An object name may consist of at most 2 identifiers (<namespace>.<name>), %d given.',
134-
count($identifiers),
135-
);
136-
137200
return;
138201
}
139202

src/Schema/AbstractNamedObject.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\DBAL\Schema;
6+
7+
use Doctrine\DBAL\Schema\Exception\InvalidName;
8+
use Doctrine\DBAL\Schema\Exception\NameIsNotInitialized;
9+
10+
/**
11+
* An abstract {@see NamedObject}.
12+
*
13+
* @template N of Name
14+
* @extends AbstractAsset<N>
15+
* @implements NamedObject<N>
16+
*/
17+
abstract class AbstractNamedObject extends AbstractAsset implements NamedObject
18+
{
19+
/**
20+
* The name of the database object.
21+
*
22+
* Until the validity of the name is enforced, this property isn't guaranteed to be always initialized. The property
23+
* can be accessed only if {@see $isNameInitialized} is set to true.
24+
*
25+
* @var N
26+
*/
27+
protected Name $name;
28+
29+
public function __construct(string $name)
30+
{
31+
parent::__construct($name);
32+
}
33+
34+
/**
35+
* Returns the object name.
36+
*
37+
* @return N
38+
*
39+
* @throws NameIsNotInitialized
40+
*/
41+
public function getObjectName(): Name
42+
{
43+
if (! $this->isNameInitialized) {
44+
throw NameIsNotInitialized::new();
45+
}
46+
47+
return $this->name;
48+
}
49+
50+
protected function setName(?Name $name): void
51+
{
52+
if ($name === null) {
53+
throw InvalidName::fromEmpty();
54+
}
55+
56+
$this->name = $name;
57+
}
58+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\DBAL\Schema;
6+
7+
use Doctrine\DBAL\Schema\Exception\NameIsNotInitialized;
8+
9+
/**
10+
* An abstract {@see OptionallyNamedObject}.
11+
*
12+
* @template N of Name
13+
* @extends AbstractAsset<N>
14+
* @implements OptionallyNamedObject<N>
15+
*/
16+
abstract class AbstractOptionallyNamedObject extends AbstractAsset implements OptionallyNamedObject
17+
{
18+
/**
19+
* The name of the database object.
20+
*
21+
* Until the validity of the name is enforced, this property isn't guaranteed to be always initialized. The property
22+
* can be accessed only if {@see $isNameInitialized} is set to true.
23+
*
24+
* @var ?N
25+
*/
26+
protected ?Name $name;
27+
28+
public function __construct(?string $name)
29+
{
30+
parent::__construct($name ?? '');
31+
}
32+
33+
public function getObjectName(): ?Name
34+
{
35+
if (! $this->isNameInitialized) {
36+
throw NameIsNotInitialized::new();
37+
}
38+
39+
return $this->name;
40+
}
41+
42+
protected function setName(?Name $name): void
43+
{
44+
$this->name = $name;
45+
}
46+
}

src/Schema/Column.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@
55
namespace Doctrine\DBAL\Schema;
66

77
use Doctrine\DBAL\Schema\Exception\UnknownColumnOption;
8+
use Doctrine\DBAL\Schema\Name\Parser\UnqualifiedNameParser;
9+
use Doctrine\DBAL\Schema\Name\Parsers;
10+
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
811
use Doctrine\DBAL\Types\Type;
912

1013
use function array_merge;
1114
use function method_exists;
1215

1316
/**
1417
* Object representation of a database column.
18+
*
19+
* @extends AbstractNamedObject<UnqualifiedName>
1520
*/
16-
class Column extends AbstractAsset
21+
class Column extends AbstractNamedObject
1722
{
1823
protected Type $_type;
1924

@@ -56,6 +61,11 @@ public function __construct(string $name, Type $type, array $options = [])
5661
$this->setOptions($options);
5762
}
5863

64+
protected function getNameParser(): UnqualifiedNameParser
65+
{
66+
return Parsers::getUnqualifiedNameParser();
67+
}
68+
5969
/** @param array<string, mixed> $options */
6070
public function setOptions(array $options): self
6171
{

0 commit comments

Comments
 (0)