Skip to content

Commit

Permalink
Merge branch '2.14.x' into 3.0.x
Browse files Browse the repository at this point in the history
* 2.14.x:
  Drop forceful loading of annotations (doctrine#10321)
  Document stdClass structures used by CommitOrderCalculator (doctrine#10315)
  Psalm 5.3.0 (doctrine#10317)
  PHPStan 1.9.4 (doctrine#10318)
  add apcu enabled check if apcu extension loaded (doctrine#10310) (doctrine#10311)
  Add TypedFieldMapper for automatic mapping of typed PHP fields to DBAL types (doctrine#10313)
  • Loading branch information
derrabus committed Dec 19, 2022
2 parents 57e18d0 + c4835cc commit 3a2bfce
Show file tree
Hide file tree
Showing 31 changed files with 952 additions and 147 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
php-version: "${{ matrix.php-version }}"
extensions: "apcu, pdo, ${{ matrix.extension }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"

- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
Expand Down Expand Up @@ -143,7 +143,7 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"

- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
Expand Down Expand Up @@ -211,7 +211,7 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"

- name: "Install dependencies with Composer"
Expand Down Expand Up @@ -280,7 +280,7 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"

- name: "Require specific DBAL version"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/phpbench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"

- name: "Cache dependencies installed with composer"
uses: "actions/cache@v3"
Expand Down
10 changes: 10 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,16 @@ Use `toIterable()` instead.

# Upgrade to 2.14

## Deprecated constants of `Doctrine\ORM\Internal\CommitOrderCalculator`

The following public constants have been deprecated:

* `CommitOrderCalculator::NOT_VISITED`
* `CommitOrderCalculator::IN_PROGRESS`
* `CommitOrderCalculator::VISITED`

These constants were used for internal purposes. Relying on them is discouraged.

## Deprecated `Doctrine\ORM\Query\AST\InExpression`

The AST parser will create a `InListExpression` or a `InSubselectExpression` when
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@
"require-dev": {
"doctrine/coding-standard": "^11.0",
"phpbench/phpbench": "^1.0",
"phpstan/phpstan": "1.9.3",
"phpstan/phpstan": "1.9.4",
"phpunit/phpunit": "^9.5",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.1",
"symfony/cache": "^5.4 || ^6.0",
"symfony/var-exporter": "^5.4 || ^6.2",
"vimeo/psalm": "5.2.0"
"vimeo/psalm": "5.3.0"
},
"suggest": {
"ext-dom": "Provides support for XSD validation for XML mapping files",
Expand Down
1 change: 1 addition & 0 deletions docs/en/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Advanced Topics
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
* :doc:`NamingStrategy <reference/namingstrategy>`
* :doc:`TypedFieldMapper <reference/typedfieldmapper>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
Expand Down
7 changes: 7 additions & 0 deletions docs/en/reference/basic-mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@ These are the "automatic" mapping rules:
As of version 2.11 Doctrine can also automatically map typed properties using a
PHP 8.1 enum to set the right ``type`` and ``enumType``.

.. versionadded:: 2.14

Since version 2.14 you can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.

:doc:`Read more about TypedFieldMapper <typedfieldmapper>`.

.. _reference-mapping-types:

Doctrine Mapping Types
Expand Down
176 changes: 176 additions & 0 deletions docs/en/reference/typedfieldmapper.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
Implementing a TypedFieldMapper
===============================

.. versionadded:: 2.14

You can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.

.. code-block:: php
<?php
$configuration->setTypedFieldMapper(new CustomTypedFieldMapper());
DefaultTypedFieldMapper
-----------------------

By default the ``Doctrine\ORM\Mapping\DefaultTypedFieldMapper`` is used, and you can pass an array of
PHP type => DBAL type mappings into its constructor to override the default behavior or add new mappings.

.. code-block:: php
<?php
use App\CustomIds\CustomIdObject;
use App\DBAL\Type\CustomIdObjectType;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
CustomIdObject::class => CustomIdObjectType::class,
]));
Then, an entity using the ``CustomIdObject`` typed field will be correctly assigned its DBAL type
(``CustomIdObjectType``) without the need of explicit declaration.

.. configuration-block::

.. code-block:: attribute
<?php
#[ORM\Entity]
#[ORM\Table(name: 'cms_users_typed_with_custom_typed_field')]
class UserTypedWithCustomTypedField
{
#[ORM\Column]
public CustomIdObject $customId;
// ...
}
.. code-block:: annotation
<?php
/**
* @Entity
* @Table(name="cms_users_typed_with_custom_typed_field")
*/
class UserTypedWithCustomTypedField
{
/** @Column */
public CustomIdObject $customId;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="UserTypedWithCustomTypedField">
<field name="customId"/>
<!-- -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
UserTypedWithCustomTypedField:
type: entity
fields:
customId: ~
It is perfectly valid to override even the "automatic" mapping rules mentioned above:

.. code-block:: php
<?php
use App\DBAL\Type\CustomIntType;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
'int' => CustomIntType::class,
]));
.. note::

If chained, once the first ``TypedFieldMapper`` assigns a type to a field, the ``DefaultTypedFieldMapper`` will
ignore its mapping and not override it anymore (if it is later in the chain). See below for chaining type mappers.


TypedFieldMapper interface
-------------------------
The interface ``Doctrine\ORM\Mapping\TypedFieldMapper`` allows you to implement your own
typed field mapping logic. It consists of just one function


.. code-block:: php
<?php
/**
* Validates & completes the given field mapping based on typed property.
*
* @param array{fieldName: string, enumType?: string, type?: mixed} $mapping The field mapping to validate & complete.
* @param \ReflectionProperty $field
*
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array;
ChainTypedFieldMapper
---------------------

The class ``Doctrine\ORM\Mapping\ChainTypedFieldMapper`` allows you to chain multiple ``TypedFieldMapper`` instances.
When being evaluated, the ``TypedFieldMapper::validateAndComplete`` is called in the order in which
the instances were supplied to the ``ChainTypedFieldMapper`` constructor.

.. code-block:: php
<?php
use App\DBAL\Type\CustomIntType;
$configuration->setTypedFieldMapper(
new ChainTypedFieldMapper(
DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
new CustomTypedFieldMapper()
)
);
Implementing a TypedFieldMapper
-------------------------------

If you want to assign all ``BackedEnum`` fields to your custom ``BackedEnumDBALType`` or you want to use different
DBAL types based on whether the entity field is nullable or not, you can achieve this by implementing your own
typed field mapper.

You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMapper``.

.. code-block:: php
<?php
final class CustomEnumTypedFieldMapper implements TypedFieldMapper
{
/**
* {@inheritdoc}
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
$type = $field->getType();
if (
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
) {
if (! $type->isBuiltin() && enum_exists($type->getName())) {
$mapping['type'] = BackedEnumDBALType::class;
}
}
return $mapping;
}
}
.. note::

Note that this case checks whether the mapping is already assigned, and if yes, it skips it. This is up to your
implementation. You can make a "greedy" mapper which will always override the mapping with its own type, or one
that behaves like ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.
19 changes: 18 additions & 1 deletion lib/Doctrine/ORM/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Doctrine\ORM\Mapping\EntityListenerResolver;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Mapping\TypedFieldMapper;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Filter\SQLFilter;
Expand Down Expand Up @@ -281,7 +282,7 @@ public function addCustomDatetimeFunction(string $name, string|callable $classNa
/**
* Gets the implementation class name of a registered custom date/time DQL function.
*
* @psalm-return class-string|callable|null $name
* @psalm-return class-string|callable|null
*/
public function getCustomDatetimeFunction(string $name): string|callable|null
{
Expand All @@ -308,6 +309,22 @@ public function setCustomDatetimeFunctions(array $functions): void
}
}

/**
* Sets a TypedFieldMapper for php typed fields to DBAL types auto-completion.
*/
public function setTypedFieldMapper(TypedFieldMapper|null $typedFieldMapper): void
{
$this->_attributes['typedFieldMapper'] = $typedFieldMapper;
}

/**
* Gets a TypedFieldMapper for php typed fields to DBAL types auto-completion.
*/
public function getTypedFieldMapper(): TypedFieldMapper|null
{
return $this->_attributes['typedFieldMapper'] ?? null;
}

/**
* Sets the custom hydrator modes in one pass.
*
Expand Down
34 changes: 34 additions & 0 deletions lib/Doctrine/ORM/Internal/CommitOrder/Edge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Internal\CommitOrder;

/** @internal */
final class Edge
{
/**
* @var string
* @readonly
*/
public $from;

/**
* @var string
* @readonly
*/
public $to;

/**
* @var int
* @readonly
*/
public $weight;

public function __construct(string $from, string $to, int $weight)
{
$this->from = $from;
$this->to = $to;
$this->weight = $weight;
}
}
38 changes: 38 additions & 0 deletions lib/Doctrine/ORM/Internal/CommitOrder/Vertex.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Internal\CommitOrder;

use Doctrine\ORM\Mapping\ClassMetadata;

/** @internal */
final class Vertex
{
/**
* @var string
* @readonly
*/
public $hash;

/**
* @var int
* @psalm-var VertexState::*
*/
public $state = VertexState::NOT_VISITED;

/**
* @var ClassMetadata
* @readonly
*/
public $value;

/** @var array<string, Edge> */
public $dependencyList = [];

public function __construct(string $hash, ClassMetadata $value)
{
$this->hash = $hash;
$this->value = $value;
}
}
Loading

0 comments on commit 3a2bfce

Please sign in to comment.