Skip to content

Deprecate Column::getType() in favor of Column::getTypeName() - #7490

Open
GromNaN wants to merge 1 commit into
doctrine:4.5.xfrom
GromNaN:feature/column-type-name
Open

Deprecate Column::getType() in favor of Column::getTypeName()#7490
GromNaN wants to merge 1 commit into
doctrine:4.5.xfrom
GromNaN:feature/column-type-name

Conversation

@GromNaN

@GromNaN GromNaN commented Aug 4, 2026

Copy link
Copy Markdown
Member

Follow-up to #7342 (removing static Type::* calls).

Motivation

Column::getType(): Type predates DBAL 4. Since Type::getName() was removed in favor of TypeRegistry::lookupName(), callers that only need the name of a column's type (ORM DatabaseDriver, RSM, schema comparison, reverse engineering, cache dumps) end up doing an instance -> name round-trip through the global static registry:

Type::getTypeRegistry()->lookupName($column->getType())

Two static Type::* calls to obtain something Column conceptually already knows.

Changes

  • New Column::setTypeName(string): self / Column::getTypeName(): string (throws TypesException if the name cannot be resolved). Internal _typeName is now the source of truth.
  • Column::setType(Type) deprecated (still populates _typeName eagerly, so unregistered Type instances now fail loudly at construction instead of silently later).
  • Column::getType() deprecated.
  • AbstractPlatform::getType(Column): Type protected helper introduced as the single hook where Type::getType() is called; future TypeRegistry injection has one place to land. OraclePlatform, PostgreSQLPlatform, DB2Platform and PostgreSQLSchemaManager migrated off Column::getType().
  • ColumnDiff::hasTypeChanged() compares type names instead of instance classes.

Migration

Before:

use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;

$column = new Column('id', Type::getType(Types::INTEGER));
$type   = $column->getType();

After:

use Doctrine\DBAL\Types\Types;

$column   = Column::editor()
    ->setUnquotedName('id')
    ->setTypeName(Types::INTEGER)
    ->create();
$typeName = $column->getTypeName();

@derrabus
derrabus changed the base branch from 4.4.x to 4.5.x August 4, 2026 09:39
@derrabus derrabus added the Types label Aug 4, 2026
@derrabus

derrabus commented Aug 4, 2026

Copy link
Copy Markdown
Member

Please rebase onto 4.5.x. We don't introduce deprecations or new public APIs in bugfix releases.

Comment thread src/Schema/Column.php Outdated
@GromNaN
GromNaN marked this pull request as draft August 4, 2026 09:50
@GromNaN
GromNaN force-pushed the feature/column-type-name branch 3 times, most recently from bc24537 to 72ba22d Compare August 4, 2026 20:56
@GromNaN
GromNaN marked this pull request as ready for review August 4, 2026 20:56
@GromNaN
GromNaN force-pushed the feature/column-type-name branch 2 times, most recently from be4c958 to 379f651 Compare August 4, 2026 21:21
@GromNaN
GromNaN requested a review from derrabus August 4, 2026 21:49
GromNaN added a commit to GromNaN/dbal that referenced this pull request Aug 5, 2026
It was inserted directly after the "Upgrade to 4.4" heading, so it documented a
4.5 deprecation under 4.4. Placed next to the related Column mutator notes.

Same fix as on the doctrine#7490 branch, where the note originates.
@GromNaN
GromNaN force-pushed the feature/column-type-name branch from fdb05f8 to 19bc7ff Compare August 19, 2026 09:17
`Column::getType()` returns a `Type` instance. In DBAL 4 the canonical
identifier of a type is its name, not its class or instance:
`Type::getName()` was removed in favor of `TypeRegistry::lookupName()`,
and consumers that only need the name (ORM `DatabaseDriver`, RSM,
schema comparison, reverse engineering, dumps to cache) end up doing a
useless instance -> name round-trip via the global static registry.

Expose the type name directly on `Column`:

- `Column::setTypeName(string): self` and `Column::getTypeName(): string`
  (throws `TypesException` if the name cannot be resolved). `_typeName`
  is the source of truth.
- `Column::setType(Type)` deprecated (still populates `_typeName` eagerly
  so unregistered types now fail early instead of silently).
- `Column::getType()` deprecated.
- `AbstractPlatform::getType(Column)` protected helper introduced as the
  single call site for `Type::getType()`, so a future `TypeRegistry`
  injection has one hook. Migrated `OraclePlatform`, `PostgreSQLPlatform`,
  `DB2Platform` and `PostgreSQLSchemaManager` off `Column::getType()`.
- `ColumnDiff::hasTypeChanged()` now compares type *names* instead of
  instance classes.
- Tests updated to construct columns via `setTypeName()`; two comparator
  tests that only made sense under class-based identity (`clone Type`,
  `overrideType`) collapsed into a single name-based equivalence test.
@GromNaN
GromNaN force-pushed the feature/column-type-name branch from 19bc7ff to 6b9d498 Compare August 19, 2026 09:18
GromNaN added a commit to GromNaN/dbal that referenced this pull request Aug 19, 2026
…instance-based lookups

All internal type resolution (Connection, Statement, AbstractPlatform, SchemaManagers,
MetadataProviders) now goes through Configuration::getTypeRegistry() instead of the
global Type::getType() / Type::hasType() / Type::getTypesMap() static methods.

Table receives an optional Configuration so addColumn() uses the instance registry
when available, falling back to Type::getType() for user code without a Configuration.
ColumnEditor::setTypeName() similarly falls back to Type::getType() for user code;
internal callers (MetadataProviders) now use setType() with the configuration registry.

Deprecate `Column::getType()` in favor of `Column::getTypeName()`

`Column::getType()` returns a `Type` instance. In DBAL 4 the canonical
identifier of a type is its name, not its class or instance:
`Type::getName()` was removed in favor of `TypeRegistry::lookupName()`,
and consumers that only need the name (ORM `DatabaseDriver`, RSM,
schema comparison, reverse engineering, dumps to cache) end up doing a
useless instance -> name round-trip via the global static registry.

Expose the type name directly on `Column`:

- `Column::setTypeName(string): self` and `Column::getTypeName(): string`
  (throws `TypesException` if the name cannot be resolved). `_typeName`
  is the source of truth.
- `Column::setType(Type)` deprecated (still populates `_typeName` eagerly
  so unregistered types now fail early instead of silently).
- `Column::getType()` deprecated.
- `AbstractPlatform::getType(Column)` protected helper introduced as the
  single call site for `Type::getType()`, so a future `TypeRegistry`
  injection has one hook. Migrated `OraclePlatform`, `PostgreSQLPlatform`,
  `DB2Platform` and `PostgreSQLSchemaManager` off `Column::getType()`.
- `ColumnDiff::hasTypeChanged()` now compares type *names* instead of
  instance classes.
- Tests updated to construct columns via `setTypeName()`; two comparator
  tests that only made sense under class-based identity (`clone Type`,
  `overrideType`) collapsed into a single name-based equivalence test.

Document per-connection type registries in UPGRADE.md

Covers Configuration::get/setTypeRegistry(), the fallback to the global
singleton for connections that do not set one, and the deliberate isolation
from Type::addType().

Also notes two things that are easy to trip over: new TypeRegistry() is now
pre-populated with the built-in types, and mocking Configuration requires
stubbing getTypeRegistry() because TypeRegistry is final.

Introduce the TypeProvider interface, addressing review feedback

Configuration now exposes get/setTypeProvider() typed against the new
Doctrine\DBAL\Types\TypeProvider instead of the final TypeRegistry, so the
type source can be extended or stubbed. The ORM testsuite previously had to
instantiate a real registry and touch unrelated tests because the final class
could not be doubled.

The interface extends PSR-11 ContainerInterface and Traversable, since a type
registry is a container of types that callers may also enumerate. get() is
redeclared to narrow the return type to Type; without that, every call site
would degrade to mixed. register() and override() stay off the interface, so
Type::getTypeRegistry() keeps returning the concrete class for Type::addType().
Because interface inheritance is resolved eagerly, psr/container moves back to
a hard requirement.

getMap() is replaced by iteration: TypeRegistry implements IteratorAggregate
with a generator that yields from each source in turn rather than merging them,
so iteration allocates nothing extra and stopping early leaves the remaining
types uninstantiated.

Also from the review:

- Drop the unset() in get()'s finally. It was redundant, since $instances is
  checked first and shadows the service ID, and being in finally it also ran on
  failure: a transient container error permanently dropped the type, so has()
  flipped to false and a retry reported an unknown type instead of retrying.
- Deprecate TypeRegistry::lookupName() and Type::lookupName(). They cannot be
  removed yet because the deprecated Column::setType(), ColumnEditor::setType()
  and ORM's TypedExpression branch still need to derive a name from an instance.
  Both go in 5.0, along with the one-instance-one-name restriction.
- Add a runtime deprecation to Column::getType(). It uses
  triggerIfCalledFromOutside because toArray() calls it internally when
  $skipType is false, and that path already triggers its own deprecation.

Remove a stray blank line in UPGRADE.md

Stop extending ContainerInterface in TypeProvider

Extending PSR-11 made psr/container a hard requirement, because interface
inheritance is resolved eagerly. That is not worth it yet: nothing in DBAL
consumes a TypeProvider as a container, and the interface can still be widened
later without breaking implementors.

psr/container therefore returns to require-dev. It stays a soft dependency:
TypeRegistry still accepts a container and catches ContainerExceptionInterface,
but those are parameter and catch positions, which PHP only resolves when a
container is actually passed. Verified by running the array-based path with an
autoloader that fails on any Psr\Container\* lookup.

Move the Column::getType() upgrade note to the 4.5 section

It was inserted directly after the "Upgrade to 4.4" heading, so it documented a
4.5 deprecation under 4.4. Placed next to the related Column mutator notes.

Same fix as on the doctrine#7490 branch, where the note originates.

Mention the connection's TypeProvider in UnknownColumnType

The message told users to register the type with Type::addType(), which does
not help a connection that has its own TypeProvider: such a connection does not
see globally registered types, so following the advice led nowhere.

Also normalises Foo#bar() to Foo::bar(); that notation appeared nowhere else in
src/.

Deprecate the static Type methods

They all operate on the process-wide registry, which behaves unexpectedly once a
connection has its own type provider: a type registered with Type::addType() is
invisible to that connection, and Type::getType() resolves against the global
registry rather than the connection's. Nothing signalled that, so the failure
surfaced later as an unrelated UnknownColumnType.

All seven now carry an @deprecated docblock and a runtime trigger.
getTypeRegistry() and getType() use triggerIfCalledFromOutside, because
Configuration::getTypeProvider(), AbstractPlatform and the deprecated
Column::getType() call them internally; that keeps the supported default path
silent and avoids reporting one user call twice. Verified that each static fires
exactly once, that Column::getType() still reports once rather than twice, and
that a plain connection stays silent through insert and schema introspection.

UnknownColumnType no longer recommends Type::addType(), which this change
deprecates and which would not have fixed the error for a connection with its
own provider.

DBAL 5 will have no static type provider.
GromNaN added a commit to GromNaN/dbal that referenced this pull request Aug 19, 2026
…instance-based lookups

All internal type resolution (Connection, Statement, AbstractPlatform, SchemaManagers,
MetadataProviders) now goes through Configuration::getTypeRegistry() instead of the
global Type::getType() / Type::hasType() / Type::getTypesMap() static methods.

Table receives an optional Configuration so addColumn() uses the instance registry
when available, falling back to Type::getType() for user code without a Configuration.
ColumnEditor::setTypeName() similarly falls back to Type::getType() for user code;
internal callers (MetadataProviders) now use setType() with the configuration registry.

Deprecate `Column::getType()` in favor of `Column::getTypeName()`

`Column::getType()` returns a `Type` instance. In DBAL 4 the canonical
identifier of a type is its name, not its class or instance:
`Type::getName()` was removed in favor of `TypeRegistry::lookupName()`,
and consumers that only need the name (ORM `DatabaseDriver`, RSM,
schema comparison, reverse engineering, dumps to cache) end up doing a
useless instance -> name round-trip via the global static registry.

Expose the type name directly on `Column`:

- `Column::setTypeName(string): self` and `Column::getTypeName(): string`
  (throws `TypesException` if the name cannot be resolved). `_typeName`
  is the source of truth.
- `Column::setType(Type)` deprecated (still populates `_typeName` eagerly
  so unregistered types now fail early instead of silently).
- `Column::getType()` deprecated.
- `AbstractPlatform::getType(Column)` protected helper introduced as the
  single call site for `Type::getType()`, so a future `TypeRegistry`
  injection has one hook. Migrated `OraclePlatform`, `PostgreSQLPlatform`,
  `DB2Platform` and `PostgreSQLSchemaManager` off `Column::getType()`.
- `ColumnDiff::hasTypeChanged()` now compares type *names* instead of
  instance classes.
- Tests updated to construct columns via `setTypeName()`; two comparator
  tests that only made sense under class-based identity (`clone Type`,
  `overrideType`) collapsed into a single name-based equivalence test.

Document per-connection type registries in UPGRADE.md

Covers Configuration::get/setTypeRegistry(), the fallback to the global
singleton for connections that do not set one, and the deliberate isolation
from Type::addType().

Also notes two things that are easy to trip over: new TypeRegistry() is now
pre-populated with the built-in types, and mocking Configuration requires
stubbing getTypeRegistry() because TypeRegistry is final.

Introduce the TypeProvider interface, addressing review feedback

Configuration now exposes get/setTypeProvider() typed against the new
Doctrine\DBAL\Types\TypeProvider instead of the final TypeRegistry, so the
type source can be extended or stubbed. The ORM testsuite previously had to
instantiate a real registry and touch unrelated tests because the final class
could not be doubled.

The interface extends PSR-11 ContainerInterface and Traversable, since a type
registry is a container of types that callers may also enumerate. get() is
redeclared to narrow the return type to Type; without that, every call site
would degrade to mixed. register() and override() stay off the interface, so
Type::getTypeRegistry() keeps returning the concrete class for Type::addType().
Because interface inheritance is resolved eagerly, psr/container moves back to
a hard requirement.

getMap() is replaced by iteration: TypeRegistry implements IteratorAggregate
with a generator that yields from each source in turn rather than merging them,
so iteration allocates nothing extra and stopping early leaves the remaining
types uninstantiated.

Also from the review:

- Drop the unset() in get()'s finally. It was redundant, since $instances is
  checked first and shadows the service ID, and being in finally it also ran on
  failure: a transient container error permanently dropped the type, so has()
  flipped to false and a retry reported an unknown type instead of retrying.
- Deprecate TypeRegistry::lookupName() and Type::lookupName(). They cannot be
  removed yet because the deprecated Column::setType(), ColumnEditor::setType()
  and ORM's TypedExpression branch still need to derive a name from an instance.
  Both go in 5.0, along with the one-instance-one-name restriction.
- Add a runtime deprecation to Column::getType(). It uses
  triggerIfCalledFromOutside because toArray() calls it internally when
  $skipType is false, and that path already triggers its own deprecation.

Remove a stray blank line in UPGRADE.md

Stop extending ContainerInterface in TypeProvider

Extending PSR-11 made psr/container a hard requirement, because interface
inheritance is resolved eagerly. That is not worth it yet: nothing in DBAL
consumes a TypeProvider as a container, and the interface can still be widened
later without breaking implementors.

psr/container therefore returns to require-dev. It stays a soft dependency:
TypeRegistry still accepts a container and catches ContainerExceptionInterface,
but those are parameter and catch positions, which PHP only resolves when a
container is actually passed. Verified by running the array-based path with an
autoloader that fails on any Psr\Container\* lookup.

Move the Column::getType() upgrade note to the 4.5 section

It was inserted directly after the "Upgrade to 4.4" heading, so it documented a
4.5 deprecation under 4.4. Placed next to the related Column mutator notes.

Same fix as on the doctrine#7490 branch, where the note originates.

Mention the connection's TypeProvider in UnknownColumnType

The message told users to register the type with Type::addType(), which does
not help a connection that has its own TypeProvider: such a connection does not
see globally registered types, so following the advice led nowhere.

Also normalises Foo#bar() to Foo::bar(); that notation appeared nowhere else in
src/.

Deprecate the static Type methods

They all operate on the process-wide registry, which behaves unexpectedly once a
connection has its own type provider: a type registered with Type::addType() is
invisible to that connection, and Type::getType() resolves against the global
registry rather than the connection's. Nothing signalled that, so the failure
surfaced later as an unrelated UnknownColumnType.

All seven now carry an @deprecated docblock and a runtime trigger.
getTypeRegistry() and getType() use triggerIfCalledFromOutside, because
Configuration::getTypeProvider(), AbstractPlatform and the deprecated
Column::getType() call them internally; that keeps the supported default path
silent and avoids reporting one user call twice. Verified that each static fires
exactly once, that Column::getType() still reports once rather than twice, and
that a plain connection stays silent through insert and schema introspection.

UnknownColumnType no longer recommends Type::addType(), which this change
deprecates and which would not have fixed the error for a connection with its
own provider.

DBAL 5 will have no static type provider.
GromNaN added a commit to GromNaN/dbal that referenced this pull request Aug 19, 2026
…instance-based lookups

All internal type resolution (Connection, Statement, AbstractPlatform, SchemaManagers,
MetadataProviders) now goes through Configuration::getTypeRegistry() instead of the
global Type::getType() / Type::hasType() / Type::getTypesMap() static methods.

Table receives an optional Configuration so addColumn() uses the instance registry
when available, falling back to Type::getType() for user code without a Configuration.
ColumnEditor::setTypeName() similarly falls back to Type::getType() for user code;
internal callers (MetadataProviders) now use setType() with the configuration registry.

Deprecate `Column::getType()` in favor of `Column::getTypeName()`

`Column::getType()` returns a `Type` instance. In DBAL 4 the canonical
identifier of a type is its name, not its class or instance:
`Type::getName()` was removed in favor of `TypeRegistry::lookupName()`,
and consumers that only need the name (ORM `DatabaseDriver`, RSM,
schema comparison, reverse engineering, dumps to cache) end up doing a
useless instance -> name round-trip via the global static registry.

Expose the type name directly on `Column`:

- `Column::setTypeName(string): self` and `Column::getTypeName(): string`
  (throws `TypesException` if the name cannot be resolved). `_typeName`
  is the source of truth.
- `Column::setType(Type)` deprecated (still populates `_typeName` eagerly
  so unregistered types now fail early instead of silently).
- `Column::getType()` deprecated.
- `AbstractPlatform::getType(Column)` protected helper introduced as the
  single call site for `Type::getType()`, so a future `TypeRegistry`
  injection has one hook. Migrated `OraclePlatform`, `PostgreSQLPlatform`,
  `DB2Platform` and `PostgreSQLSchemaManager` off `Column::getType()`.
- `ColumnDiff::hasTypeChanged()` now compares type *names* instead of
  instance classes.
- Tests updated to construct columns via `setTypeName()`; two comparator
  tests that only made sense under class-based identity (`clone Type`,
  `overrideType`) collapsed into a single name-based equivalence test.

Document per-connection type registries in UPGRADE.md

Covers Configuration::get/setTypeRegistry(), the fallback to the global
singleton for connections that do not set one, and the deliberate isolation
from Type::addType().

Also notes two things that are easy to trip over: new TypeRegistry() is now
pre-populated with the built-in types, and mocking Configuration requires
stubbing getTypeRegistry() because TypeRegistry is final.

Introduce the TypeProvider interface, addressing review feedback

Configuration now exposes get/setTypeProvider() typed against the new
Doctrine\DBAL\Types\TypeProvider instead of the final TypeRegistry, so the
type source can be extended or stubbed. The ORM testsuite previously had to
instantiate a real registry and touch unrelated tests because the final class
could not be doubled.

The interface extends PSR-11 ContainerInterface and Traversable, since a type
registry is a container of types that callers may also enumerate. get() is
redeclared to narrow the return type to Type; without that, every call site
would degrade to mixed. register() and override() stay off the interface, so
Type::getTypeRegistry() keeps returning the concrete class for Type::addType().
Because interface inheritance is resolved eagerly, psr/container moves back to
a hard requirement.

getMap() is replaced by iteration: TypeRegistry implements IteratorAggregate
with a generator that yields from each source in turn rather than merging them,
so iteration allocates nothing extra and stopping early leaves the remaining
types uninstantiated.

Also from the review:

- Drop the unset() in get()'s finally. It was redundant, since $instances is
  checked first and shadows the service ID, and being in finally it also ran on
  failure: a transient container error permanently dropped the type, so has()
  flipped to false and a retry reported an unknown type instead of retrying.
- Deprecate TypeRegistry::lookupName() and Type::lookupName(). They cannot be
  removed yet because the deprecated Column::setType(), ColumnEditor::setType()
  and ORM's TypedExpression branch still need to derive a name from an instance.
  Both go in 5.0, along with the one-instance-one-name restriction.
- Add a runtime deprecation to Column::getType(). It uses
  triggerIfCalledFromOutside because toArray() calls it internally when
  $skipType is false, and that path already triggers its own deprecation.

Remove a stray blank line in UPGRADE.md

Stop extending ContainerInterface in TypeProvider

Extending PSR-11 made psr/container a hard requirement, because interface
inheritance is resolved eagerly. That is not worth it yet: nothing in DBAL
consumes a TypeProvider as a container, and the interface can still be widened
later without breaking implementors.

psr/container therefore returns to require-dev. It stays a soft dependency:
TypeRegistry still accepts a container and catches ContainerExceptionInterface,
but those are parameter and catch positions, which PHP only resolves when a
container is actually passed. Verified by running the array-based path with an
autoloader that fails on any Psr\Container\* lookup.

Move the Column::getType() upgrade note to the 4.5 section

It was inserted directly after the "Upgrade to 4.4" heading, so it documented a
4.5 deprecation under 4.4. Placed next to the related Column mutator notes.

Same fix as on the doctrine#7490 branch, where the note originates.

Mention the connection's TypeProvider in UnknownColumnType

The message told users to register the type with Type::addType(), which does
not help a connection that has its own TypeProvider: such a connection does not
see globally registered types, so following the advice led nowhere.

Also normalises Foo#bar() to Foo::bar(); that notation appeared nowhere else in
src/.

Deprecate the static Type methods

They all operate on the process-wide registry, which behaves unexpectedly once a
connection has its own type provider: a type registered with Type::addType() is
invisible to that connection, and Type::getType() resolves against the global
registry rather than the connection's. Nothing signalled that, so the failure
surfaced later as an unrelated UnknownColumnType.

All seven now carry an @deprecated docblock and a runtime trigger.
getTypeRegistry() and getType() use triggerIfCalledFromOutside, because
Configuration::getTypeProvider(), AbstractPlatform and the deprecated
Column::getType() call them internally; that keeps the supported default path
silent and avoids reporting one user call twice. Verified that each static fires
exactly once, that Column::getType() still reports once rather than twice, and
that a plain connection stays silent through insert and schema introspection.

UnknownColumnType no longer recommends Type::addType(), which this change
deprecates and which would not have fixed the error for a connection with its
own provider.

DBAL 5 will have no static type provider.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants