Allow doctrine/doctrine-bundle ^3.0 and doctrine/dbal ^4.0 (#19064)
Some checks are pending
Continuous Integration (Minimal) / Static checks (push) Waiting to run
Continuous Integration (Minimal) / Tests (MariaDB) (push) Blocked by required conditions
Continuous Integration (Minimal) / Tests (MySQL) (push) Blocked by required conditions
Continuous Integration (Minimal) / Javascript Tests (MySQL) (push) Blocked by required conditions
Continuous Integration (Minimal) / Tests (PostgreSQL) (push) Blocked by required conditions
Continuous Integration (Minimal) / Frontend (push) Blocked by required conditions
Continuous Integration (Minimal) / Packages (push) Blocked by required conditions

| Q               | A
|-----------------|-----
| Branch?         | 2.3
| Bug fix?        | no
| New feature?    | yes
| BC breaks?      | no
| Deprecations? | no<!-- don't forget to update the UPGRADE-*.md file
-->
| Related tickets | duplicates
https://github.com/Sylius/Sylius/pull/18754, partially
https://github.com/Sylius/Sylius/issues/18760
| License         | MIT

This PR is a duplication of #18754 (which targets the `symfony-8`
branch). It reuses that PR's commits to bring the broadened Doctrine
support into `2.3`.

### What it does

Widens the supported Doctrine stack, **opt-in via `||`, no minimum is
bumped**:

  - `doctrine/doctrine-bundle` `^2.13 || ^3.0`
  - `doctrine/dbal` `^3.9 || ^4.0`
  - `doctrine/persistence` `^3.3 || ^4.0`
  - `doctrine/data-fixtures` `^1.7 || ^2.2` (dev)

Plus the compatibility work the newer stack requires:

- DBAL 3.x/4.x compatibility layers (platform detection,
`getSchemaManager()` → `createSchemaManager()`,
    `SqlitePlatform`/`SQLitePlatform`, `QueryBuilder::select()`);
- a custom `ObjectType` registered as `object` (the built-in `object`
type was removed in DBAL 4),
covering the only two `type="object"` mappings:
`PaymentSecurityToken.details` and `PaymentRequest.payload`;
- config updates: removed `auto_generate_proxy_classes` (gone in
DoctrineBundle 3), PSR-6 cache pools,
and forced `SEQUENCE` identity generation on PostgreSQL to keep the
schema backward compatible;
  - CI matrix entries to test both DoctrineBundle 2 and 3.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added support for DoctrineBundle 3 and DBAL 4.
  * Added a custom DBAL “object” type to keep payment data compatible.
* **Documentation**
* Added Sylius 2.3 upgrade notes, including Doctrine cache pool
migration and PostgreSQL identity/sequence behavior.
* **Bug Fixes**
* Updated Doctrine ORM cache configuration to use Symfony cache pools
(production and test setups).
* Fixed timestamp handling to consistently store mutable `DateTime`
values where required.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Jan Góralski 2026-06-18 10:56:49 +02:00 committed by GitHub
commit f745ee5aa0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 543 additions and 249 deletions

View file

@ -44,7 +44,7 @@ jobs:
phpunit-cli-api: phpunit-cli-api:
needs: get-matrix needs: get-matrix
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "PHPUnit, CLI, API, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MariaDB ${{ matrix.mariadb }}, State Machine Adapter ${{ matrix.state_machine_adapter }}${{ matrix.api_platform && format(', ApiPlatform {0}', matrix.api_platform) || '' }}" name: "PHPUnit, CLI, API, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MariaDB ${{ matrix.mariadb }}, State Machine Adapter ${{ matrix.state_machine_adapter }}${{ matrix.doctrine_bundle && format(', DoctrineBundle {0}', matrix.doctrine_bundle) || '' }}${{ matrix.api_platform && format(', ApiPlatform {0}', matrix.api_platform) || '' }}"
timeout-minutes: 20 timeout-minutes: 20
strategy: strategy:
fail-fast: false fail-fast: false
@ -73,6 +73,14 @@ jobs:
composer require winzou/state-machine:^0.4 --no-update composer require winzou/state-machine:^0.4 --no-update
composer require winzou/state-machine-bundle:^0.6 --no-update composer require winzou/state-machine-bundle:^0.6 --no-update
- name: Require Doctrine Bundle
if: ${{ matrix.doctrine_bundle != '' && matrix.doctrine_bundle != null }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/doctrine-bundle:${{ matrix.doctrine_bundle }}"
- name: Require DBAL 3.x for DoctrineBundle 2.x
if: ${{ matrix.doctrine_bundle == '^2.0' }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/dbal:^3.9"
- name: Prepare manifest.json files - name: Prepare manifest.json files
run: | run: |
mkdir -p public/build/admin mkdir -p public/build/admin
@ -145,7 +153,7 @@ jobs:
behat-ui: behat-ui:
needs: get-matrix needs: get-matrix
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "UI, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MariaDB ${{ matrix.mariadb }}, State Machine Adapter ${{ matrix.state_machine_adapter }}${{ matrix.api_platform && format(', ApiPlatform {0}', matrix.api_platform) || '' }}" name: "UI, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MariaDB ${{ matrix.mariadb }}, State Machine Adapter ${{ matrix.state_machine_adapter }}${{ matrix.doctrine_bundle && format(', DoctrineBundle {0}', matrix.doctrine_bundle) || '' }}${{ matrix.api_platform && format(', ApiPlatform {0}', matrix.api_platform) || '' }}"
timeout-minutes: 20 timeout-minutes: 20
strategy: strategy:
fail-fast: false fail-fast: false
@ -174,6 +182,14 @@ jobs:
composer require winzou/state-machine:^0.4 --no-update composer require winzou/state-machine:^0.4 --no-update
composer require winzou/state-machine-bundle:^0.6 --no-update composer require winzou/state-machine-bundle:^0.6 --no-update
- name: Require Doctrine Bundle
if: ${{ matrix.doctrine_bundle != '' && matrix.doctrine_bundle != null }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/doctrine-bundle:${{ matrix.doctrine_bundle }}"
- name: Require DBAL 3.x for DoctrineBundle 2.x
if: ${{ matrix.doctrine_bundle == '^2.0' }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/dbal:^3.9"
- name: Prepare manifest.json files - name: Prepare manifest.json files
run: | run: |
mkdir -p public/build/admin mkdir -p public/build/admin

View file

@ -44,7 +44,7 @@ jobs:
phpunit-cli-api: phpunit-cli-api:
needs: get-matrix needs: get-matrix
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "PHPUnit, CLI, API, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}" name: "PHPUnit, CLI, API, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}${{ matrix.doctrine_bundle && format(', DoctrineBundle {0}', matrix.doctrine_bundle) || '' }}"
timeout-minutes: 20 timeout-minutes: 20
strategy: strategy:
fail-fast: false fail-fast: false
@ -70,6 +70,14 @@ jobs:
if: matrix.twig == '^2.12' if: matrix.twig == '^2.12'
run: composer require --no-update --no-scripts --no-interaction "twig/twig:${{ matrix.twig }}" run: composer require --no-update --no-scripts --no-interaction "twig/twig:${{ matrix.twig }}"
- name: Require Doctrine Bundle
if: ${{ matrix.doctrine_bundle != '' && matrix.doctrine_bundle != null }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/doctrine-bundle:${{ matrix.doctrine_bundle }}"
- name: Require DBAL 3.x for DoctrineBundle 2.x
if: ${{ matrix.doctrine_bundle == '^2.0' }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/dbal:^3.9"
- name: Prepare manifest.json files - name: Prepare manifest.json files
run: | run: |
mkdir -p public/build/admin mkdir -p public/build/admin
@ -85,8 +93,8 @@ jobs:
uses: SyliusLabs/BuildTestAppAction@v4 uses: SyliusLabs/BuildTestAppAction@v4
with: with:
build_type: "sylius" build_type: "sylius"
cache_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-symfony-${{ matrix.symfony }}" cache_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-symfony-${{ matrix.symfony }}-doctrine-${{ matrix.doctrine_bundle || 'default' }}"
cache_restore_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-symfony-${{ matrix.symfony }}" cache_restore_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-symfony-${{ matrix.symfony }}-doctrine-${{ matrix.doctrine_bundle || 'default' }}"
e2e: "yes" e2e: "yes"
database: "mysql:${{ matrix.mysql }}" database: "mysql:${{ matrix.mysql }}"
php_version: ${{ matrix.php }} php_version: ${{ matrix.php }}
@ -131,7 +139,7 @@ jobs:
behat-ui: behat-ui:
needs: get-matrix needs: get-matrix
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "UI, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}" name: "UI, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}${{ matrix.doctrine_bundle && format(', DoctrineBundle {0}', matrix.doctrine_bundle) || '' }}"
timeout-minutes: 20 timeout-minutes: 20
strategy: strategy:
fail-fast: false fail-fast: false
@ -157,6 +165,14 @@ jobs:
if: matrix.twig == '^2.12' if: matrix.twig == '^2.12'
run: composer require --no-update --no-scripts --no-interaction "twig/twig:${{ matrix.twig }}" run: composer require --no-update --no-scripts --no-interaction "twig/twig:${{ matrix.twig }}"
- name: Require Doctrine Bundle
if: ${{ matrix.doctrine_bundle != '' && matrix.doctrine_bundle != null }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/doctrine-bundle:${{ matrix.doctrine_bundle }}"
- name: Require DBAL 3.x for DoctrineBundle 2.x
if: ${{ matrix.doctrine_bundle == '^2.0' }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/dbal:^3.9"
- name: Prepare manifest.json files - name: Prepare manifest.json files
run: | run: |
mkdir -p public/build/admin mkdir -p public/build/admin
@ -172,8 +188,8 @@ jobs:
uses: SyliusLabs/BuildTestAppAction@v4 uses: SyliusLabs/BuildTestAppAction@v4
with: with:
build_type: "sylius" build_type: "sylius"
cache_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-symfony-${{ matrix.symfony }}" cache_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-symfony-${{ matrix.symfony }}-doctrine-${{ matrix.doctrine_bundle || 'default' }}"
cache_restore_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-symfony-${{ matrix.symfony }}" cache_restore_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-symfony-${{ matrix.symfony }}-doctrine-${{ matrix.doctrine_bundle || 'default' }}"
e2e: "yes" e2e: "yes"
database: "mysql:${{ matrix.mysql }}" database: "mysql:${{ matrix.mysql }}"
php_version: ${{ matrix.php }} php_version: ${{ matrix.php }}

View file

@ -44,7 +44,7 @@ jobs:
phpunit-cli-api: phpunit-cli-api:
needs: get-matrix needs: get-matrix
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "PHPUnit, CLI, API, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, PostgreSQL ${{ matrix.postgres }}" name: "PHPUnit, CLI, API, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, PostgreSQL ${{ matrix.postgres }}${{ matrix.doctrine_bundle && format(', DoctrineBundle {0}', matrix.doctrine_bundle) || '' }}"
timeout-minutes: 20 timeout-minutes: 20
strategy: strategy:
fail-fast: false fail-fast: false
@ -66,6 +66,14 @@ jobs:
if: "${{ inputs.branch == '' }}" if: "${{ inputs.branch == '' }}"
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Require Doctrine Bundle
if: ${{ matrix.doctrine_bundle != '' && matrix.doctrine_bundle != null }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/doctrine-bundle:${{ matrix.doctrine_bundle }}"
- name: Require DBAL 3.x for DoctrineBundle 2.x
if: ${{ matrix.doctrine_bundle == '^2.0' }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/dbal:^3.9"
- name: Prepare manifest.json files - name: Prepare manifest.json files
run: | run: |
mkdir -p public/build/admin mkdir -p public/build/admin
@ -81,8 +89,8 @@ jobs:
uses: SyliusLabs/BuildTestAppAction@v4 uses: SyliusLabs/BuildTestAppAction@v4
with: with:
build_type: "sylius" build_type: "sylius"
cache_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-" cache_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-doctrine-${{ matrix.doctrine_bundle || 'default' }}-"
cache_restore_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-" cache_restore_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-doctrine-${{ matrix.doctrine_bundle || 'default' }}-"
e2e: "yes" e2e: "yes"
database: "postgres:${{ matrix.postgres }}" database: "postgres:${{ matrix.postgres }}"
php_version: ${{ matrix.php }} php_version: ${{ matrix.php }}
@ -119,7 +127,7 @@ jobs:
behat-ui: behat-ui:
needs: get-matrix needs: get-matrix
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "UI, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, PostgreSQL ${{ matrix.postgres }}" name: "UI, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, PostgreSQL ${{ matrix.postgres }}${{ matrix.doctrine_bundle && format(', DoctrineBundle {0}', matrix.doctrine_bundle) || '' }}"
timeout-minutes: 20 timeout-minutes: 20
strategy: strategy:
fail-fast: false fail-fast: false
@ -141,6 +149,14 @@ jobs:
if: "${{ inputs.branch == '' }}" if: "${{ inputs.branch == '' }}"
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Require Doctrine Bundle
if: ${{ matrix.doctrine_bundle != '' && matrix.doctrine_bundle != null }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/doctrine-bundle:${{ matrix.doctrine_bundle }}"
- name: Require DBAL 3.x for DoctrineBundle 2.x
if: ${{ matrix.doctrine_bundle == '^2.0' }}
run: composer require --no-update --no-scripts --no-interaction "doctrine/dbal:^3.9"
- name: Prepare manifest.json files - name: Prepare manifest.json files
run: | run: |
mkdir -p public/build/admin mkdir -p public/build/admin
@ -156,8 +172,8 @@ jobs:
uses: SyliusLabs/BuildTestAppAction@v4 uses: SyliusLabs/BuildTestAppAction@v4
with: with:
build_type: "sylius" build_type: "sylius"
cache_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-" cache_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-doctrine-${{ matrix.doctrine_bundle || 'default' }}-"
cache_restore_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-" cache_restore_key: "${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-doctrine-${{ matrix.doctrine_bundle || 'default' }}-"
e2e: "yes" e2e: "yes"
database: "postgres:${{ matrix.postgres }}" database: "postgres:${{ matrix.postgres }}"
php_version: ${{ matrix.php }} php_version: ${{ matrix.php }}

View file

@ -22,14 +22,14 @@
"php": "8.4", "php": "8.4",
"symfony": "~6.4.0", "symfony": "~6.4.0",
"mariadb": "10.11.13", "mariadb": "10.11.13",
"dbal": "^3.0", "doctrine_bundle": "^2.0",
"state_machine_adapter": "winzou_state_machine" "state_machine_adapter": "winzou_state_machine"
}, },
{ {
"php": "8.5", "php": "8.5",
"symfony": "~7.4.0", "symfony": "~7.4.0",
"mariadb": "11.4.7", "mariadb": "11.4.7",
"dbal": "^3.0", "doctrine_bundle": "^3.0",
"state_machine_adapter": "symfony_workflow" "state_machine_adapter": "symfony_workflow"
} }
] ]
@ -40,13 +40,15 @@
"php": "8.4", "php": "8.4",
"symfony": "~6.4.0", "symfony": "~6.4.0",
"mysql": "8.0", "mysql": "8.0",
"twig": "^3.3" "twig": "^3.3",
"doctrine_bundle": "^2.0"
}, },
{ {
"php": "8.5", "php": "8.5",
"symfony": "~7.4.0", "symfony": "~7.4.0",
"mysql": "8.4", "mysql": "8.4",
"twig": "^3.3" "twig": "^3.3",
"doctrine_bundle": "^3.0"
} }
] ]
}, },
@ -103,12 +105,14 @@
{ {
"php": "8.4", "php": "8.4",
"symfony": "~6.4.0", "symfony": "~6.4.0",
"postgres": "15.13" "postgres": "15.13",
"doctrine_bundle": "^2.0"
}, },
{ {
"php": "8.5", "php": "8.5",
"symfony": "~7.4.0", "symfony": "~7.4.0",
"postgres": "17.5" "postgres": "17.5",
"doctrine_bundle": "^3.0"
} }
] ]
}, },
@ -155,6 +159,10 @@
"mariadb": [ "mariadb": [
"11.4.7" "11.4.7"
], ],
"doctrine_bundle": [
"^2.0",
"^3.0"
],
"state_machine_adapter": [ "state_machine_adapter": [
"symfony_workflow" "symfony_workflow"
], ],
@ -163,6 +171,7 @@
"php": "8.3", "php": "8.3",
"symfony": "~7.4.0", "symfony": "~7.4.0",
"mariadb": "11.4.7", "mariadb": "11.4.7",
"doctrine_bundle": "^2.0",
"state_machine_adapter": "winzou_state_machine" "state_machine_adapter": "winzou_state_machine"
} }
] ]
@ -182,12 +191,17 @@
"twig": [ "twig": [
"^3.3" "^3.3"
], ],
"doctrine_bundle": [
"^2.0",
"^3.0"
],
"include": [ "include": [
{ {
"php": "8.3", "php": "8.3",
"symfony": "~7.4.0", "symfony": "~7.4.0",
"mysql": "8.0", "mysql": "8.0",
"twig": "^3.3" "twig": "^3.3",
"doctrine_bundle": "^2.0"
} }
] ]
}, },
@ -253,6 +267,20 @@
"15.13", "15.13",
"16.9", "16.9",
"17.5" "17.5"
],
"include": [
{
"php": "8.4",
"symfony": "~6.4.0",
"postgres": "15.13",
"doctrine_bundle": "^2.0"
},
{
"php": "8.5",
"symfony": "~7.4.0",
"postgres": "17.5",
"doctrine_bundle": "^3.0"
}
] ]
}, },
"frontend": { "frontend": {

View file

@ -12,6 +12,38 @@
order_by_identifier: true order_by_identifier: true
``` ```
2. Configuration changes related to the broadened Doctrine support (see the *Dependencies* section).
These only matter once you opt into **DoctrineBundle 3** / **DBAL 4**:
- The `auto_generate_proxy_classes: "%kernel.debug%"` option was removed from Sylius' Doctrine configuration
(it no longer exists in DoctrineBundle 3, and since PHP 8.4 Doctrine uses native lazy objects). If you are still
on DoctrineBundle 2 and rely on this behavior, set it explicitly in your own application configuration.
- The ORM metadata/query/result cache configuration was switched from wrapped `Doctrine\Common\Cache` services
to **PSR-6 cache pools** (`type: pool`). This change lives in the application configuration (`config/packages/{prod,test}/doctrine.yaml`),
which is **not** updated automatically on existing installations, update those files in your project accordingly:
```diff
doctrine:
orm:
entity_managers:
default:
metadata_cache_driver:
- type: service
- id: doctrine.system_cache_provider
+ type: pool
+ pool: doctrine.system_cache_pool
```
As part of this switch, the `doctrine.result_cache_provider` and `doctrine.system_cache_provider` services
(defined in Sylius' application configuration, wrapping the PSR-6 pools via `Doctrine\Common\Cache\Psr6\DoctrineProvider`)
were **removed**. If you referenced them by id, use the cache pools directly (`doctrine.system_cache_pool`,
`doctrine.result_cache_pool`) with `type: pool` as shown above.
- On **PostgreSQL**, Sylius now forces the `SEQUENCE` identity generation strategy
(`identity_generation_preferences` for `PostgreSQLPlatform`) to keep the database schema backward compatible
with existing installations.
## Dependencies ## Dependencies
1. The `behat/transliterator` package has been **deprecated** and will be removed in Sylius 3.0. 1. The `behat/transliterator` package has been **deprecated** and will be removed in Sylius 3.0.
@ -68,6 +100,14 @@
If your application depends on these packages directly, require them explicitly in your `composer.json`. If your application depends on these packages directly, require them explicitly in your `composer.json`.
4. The supported Doctrine version ranges have been **broadened** to allow the newer stack
(`doctrine/doctrine-bundle` `^2.13 || ^3.0`, `doctrine/dbal` `^3.9 || ^4.0`,
`doctrine/persistence` `^3.3 || ^4.0`, `doctrine/data-fixtures` `^1.7 || ^2.2`).
DBAL 4 removes the built-in `object` and `array` column types. Since Sylius maps two fields
as `type="object"` (`PaymentSecurityToken.details` and `PaymentRequest.payload`), it registers
a custom `Sylius\Bundle\PaymentBundle\Doctrine\DBAL\Type\ObjectType` to keep them working.
## Deprecations ## Deprecations
1. Passing a `Sylius\Component\Core\Calculator\ProductVariantPricesCalculatorInterface` directly to the following catalog-facing classes is deprecated since Sylius 2.3. 1. Passing a `Sylius\Component\Core\Calculator\ProductVariantPricesCalculatorInterface` directly to the following catalog-facing classes is deprecated since Sylius 2.3.

View file

@ -58,6 +58,7 @@
"Behat\\Testwork\\Tester\\Setup\\Setup", "Behat\\Testwork\\Tester\\Setup\\Setup",
"Behat\\Testwork\\Tester\\Setup\\Teardown", "Behat\\Testwork\\Tester\\Setup\\Teardown",
"Composer\\InstalledVersions", "Composer\\InstalledVersions",
"Doctrine\\DBAL\\Platforms\\MariaDb1027Platform",
"Doctrine\\DBAL\\Platforms\\MySqlPlatform", "Doctrine\\DBAL\\Platforms\\MySqlPlatform",
"enshrined\\svgSanitize\\Sanitizer", "enshrined\\svgSanitize\\Sanitizer",
"FriendsOfBehat\\SymfonyExtension\\Mink\\Mink", "FriendsOfBehat\\SymfonyExtension\\Mink\\Mink",

View file

@ -38,14 +38,14 @@
"behat/transliterator": "^1.5", "behat/transliterator": "^1.5",
"doctrine/collections": "^2.2", "doctrine/collections": "^2.2",
"doctrine/common": "^3.2", "doctrine/common": "^3.2",
"doctrine/dbal": "^3.9", "doctrine/dbal": "^3.9 || ^4.0",
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/event-manager": "^2.0", "doctrine/event-manager": "^2.0",
"doctrine/inflector": "^2.0", "doctrine/inflector": "^2.0",
"doctrine/migrations": "^3.8", "doctrine/migrations": "^3.8",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"doctrine/persistence": "^3.3", "doctrine/persistence": "^3.3 || ^4.0",
"egulias/email-validator": "^4.0", "egulias/email-validator": "^4.0",
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",
"gedmo/doctrine-extensions": "^3.20", "gedmo/doctrine-extensions": "^3.20",
@ -192,7 +192,7 @@
"dmore/behat-chrome-extension": "^1.4", "dmore/behat-chrome-extension": "^1.4",
"dmore/chrome-mink-driver": "^2.9", "dmore/chrome-mink-driver": "^2.9",
"doctrine/cache": "^2.2", "doctrine/cache": "^2.2",
"doctrine/data-fixtures": "^1.7", "doctrine/data-fixtures": "^1.7 || ^2.2",
"friends-of-behat/mink": "^1.11", "friends-of-behat/mink": "^1.11",
"friends-of-behat/mink-browserkit-driver": "^1.6", "friends-of-behat/mink-browserkit-driver": "^1.6",
"friends-of-behat/mink-debug-extension": "^2.1", "friends-of-behat/mink-debug-extension": "^2.1",
@ -201,7 +201,7 @@
"friends-of-behat/symfony-extension": "^2.6.2", "friends-of-behat/symfony-extension": "^2.6.2",
"friends-of-behat/variadic-extension": "^1.6", "friends-of-behat/variadic-extension": "^1.6",
"hwi/oauth-bundle": "^2.2", "hwi/oauth-bundle": "^2.2",
"lchrusciel/api-test-case": "^5.3", "lchrusciel/api-test-case": "^5.3.5",
"matthiasnoback/symfony-config-test": "^6.0", "matthiasnoback/symfony-config-test": "^6.0",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"nyholm/psr7": "^1.8", "nyholm/psr7": "^1.8",

View file

@ -3,29 +3,14 @@ doctrine:
entity_managers: entity_managers:
default: default:
metadata_cache_driver: metadata_cache_driver:
type: service type: pool
id: doctrine.system_cache_provider pool: doctrine.system_cache_pool
query_cache_driver: query_cache_driver:
type: service type: pool
id: doctrine.system_cache_provider pool: doctrine.system_cache_pool
result_cache_driver: result_cache_driver:
type: service type: pool
id: doctrine.result_cache_provider pool: doctrine.result_cache_pool
services:
doctrine.result_cache_provider:
class: Doctrine\Common\Cache\Psr6\DoctrineProvider
public: false
factory: ['Doctrine\Common\Cache\Psr6\DoctrineProvider', 'wrap']
arguments:
- '@doctrine.result_cache_pool'
doctrine.system_cache_provider:
class: Doctrine\Common\Cache\Psr6\DoctrineProvider
public: false
factory: [ 'Doctrine\Common\Cache\Psr6\DoctrineProvider', 'wrap' ]
arguments:
- '@doctrine.system_cache_pool'
framework: framework:
cache: cache:

View file

@ -3,28 +3,14 @@ doctrine:
entity_managers: entity_managers:
default: default:
metadata_cache_driver: metadata_cache_driver:
type: service type: pool
id: doctrine.system_cache_provider pool: doctrine.system_cache_pool
query_cache_driver: query_cache_driver:
type: service type: pool
id: doctrine.system_cache_provider pool: doctrine.system_cache_pool
result_cache_driver: result_cache_driver:
type: service type: pool
id: doctrine.result_cache_provider pool: doctrine.result_cache_pool
services:
doctrine.result_cache_provider:
class: Doctrine\Common\Cache\Psr6\DoctrineProvider
public: false
factory: ['Doctrine\Common\Cache\Psr6\DoctrineProvider', 'wrap']
arguments:
- '@doctrine.result_cache_pool'
doctrine.system_cache_provider:
class: Doctrine\Common\Cache\Psr6\DoctrineProvider
public: false
factory: [ 'Doctrine\Common\Cache\Psr6\DoctrineProvider', 'wrap' ]
arguments:
- '@doctrine.system_cache_pool'
framework: framework:
cache: cache:

View file

@ -59,3 +59,10 @@ parameters:
identifier: arguments.count identifier: arguments.count
count: 1 count: 1
path: src/Sylius/Bundle/ShopBundle/Router/LocaleStrippingRouter.php path: src/Sylius/Bundle/ShopBundle/Router/LocaleStrippingRouter.php
# DBAL 3.x/4.x compatibility - getSchemaManager() removed in DBAL 4.x
-
message: '/Call to an undefined method Doctrine\\DBAL\\Connection::getSchemaManager\(\)\./'
identifier: method.notFound
paths:
- src/Sylius/Bundle/CoreBundle/Migrations/

View file

@ -27,7 +27,10 @@ final class DoctrineORMContext implements Context
#[BeforeScenario] #[BeforeScenario]
public function purgeDatabase() public function purgeDatabase()
{ {
$this->entityManager->getConnection()->getConfiguration()->setSQLLogger(null); $configuration = $this->entityManager->getConnection()->getConfiguration();
if (method_exists($configuration, 'setSQLLogger')) {
$configuration->setSQLLogger(null);
}
$purger = new ORMPurger($this->entityManager); $purger = new ORMPurger($this->entityManager);
$purger->purge(); $purger->purge();
$this->entityManager->clear(); $this->entityManager->clear();

View file

@ -816,7 +816,7 @@ final readonly class OrderContext implements Context
$customer->setFirstname('John'); $customer->setFirstname('John');
$customer->setLastname('Doe' . $i); $customer->setLastname('Doe' . $i);
$customer->setCreatedAt($this->clock->now()); $customer->setCreatedAt(\DateTime::createFromImmutable($this->clock->now()));
$customers[] = $customer; $customers[] = $customer;
@ -923,7 +923,7 @@ final readonly class OrderContext implements Context
$this->shipOrder($order); $this->shipOrder($order);
} }
$order->setCheckoutCompletedAt($this->clock->now()); $order->setCheckoutCompletedAt(\DateTime::createFromImmutable($this->clock->now()));
$this->objectManager->persist($order); $this->objectManager->persist($order);
$this->sharedStorage->set('order', $order); $this->sharedStorage->set('order', $order);
@ -949,7 +949,7 @@ final readonly class OrderContext implements Context
$this->payOrder($order); $this->payOrder($order);
$order->setCheckoutCompletedAt($this->clock->now()); $order->setCheckoutCompletedAt(\DateTime::createFromImmutable($this->clock->now()));
$this->objectManager->persist($order); $this->objectManager->persist($order);
$this->sharedStorage->set('order', $order); $this->sharedStorage->set('order', $order);
@ -985,7 +985,7 @@ final readonly class OrderContext implements Context
$this->shipOrder($order); $this->shipOrder($order);
} }
$order->setCheckoutCompletedAt($this->clock->now()); $order->setCheckoutCompletedAt(\DateTime::createFromImmutable($this->clock->now()));
$this->objectManager->persist($order); $this->objectManager->persist($order);
} }
@ -1014,7 +1014,7 @@ final readonly class OrderContext implements Context
); );
$order->setState($isFulfilled ? BaseOrderInterface::STATE_FULFILLED : BaseOrderInterface::STATE_NEW); $order->setState($isFulfilled ? BaseOrderInterface::STATE_FULFILLED : BaseOrderInterface::STATE_NEW);
$order->setCheckoutCompletedAt($this->clock->now()); $order->setCheckoutCompletedAt(\DateTime::createFromImmutable($this->clock->now()));
$this->objectManager->persist($order); $this->objectManager->persist($order);
} }

View file

@ -37,8 +37,8 @@
"phpstan/phpdoc-parser": ">= 2.0" "phpstan/phpdoc-parser": ">= 2.0"
}, },
"require-dev": { "require-dev": {
"doctrine/data-fixtures": "^1.7", "doctrine/data-fixtures": "^1.7 || ^2.2",
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -42,7 +42,7 @@ final class AllTaxons implements AllTaxonsInterface
$queryBuilder = $this->entityManager->getConnection()->createQueryBuilder(); $queryBuilder = $this->entityManager->getConnection()->createQueryBuilder();
$queryBuilder $queryBuilder
->select([ ->select(
'taxon.id as id', 'taxon.id as id',
'taxon.tree_root as tree_root', 'taxon.tree_root as tree_root',
'taxon.parent_id as parent_id', 'taxon.parent_id as parent_id',
@ -53,7 +53,7 @@ final class AllTaxons implements AllTaxonsInterface
'taxon.position as position', 'taxon.position as position',
'taxon.enabled as enabled', 'taxon.enabled as enabled',
'COALESCE(current_translation.name, fallback_translation.name) as name', 'COALESCE(current_translation.name, fallback_translation.name) as name',
]) )
->from('sylius_taxon', 'taxon') ->from('sylius_taxon', 'taxon')
->leftJoin( ->leftJoin(
'taxon', 'taxon',

View file

@ -24,7 +24,7 @@ final class ArchivingPromotionApplicator implements ArchivingPromotionApplicator
public function archive(PromotionInterface $data): PromotionInterface public function archive(PromotionInterface $data): PromotionInterface
{ {
$data->setArchivedAt($this->calendar->now()); $data->setArchivedAt(\DateTime::createFromImmutable($this->calendar->now()));
return $data; return $data;
} }

View file

@ -24,7 +24,7 @@ final readonly class ArchivingShippingMethodApplicator implements ArchivingShipp
public function archive(ShippingMethodInterface $data): ShippingMethodInterface public function archive(ShippingMethodInterface $data): ShippingMethodInterface
{ {
$data->setArchivedAt($this->clock->now()); $data->setArchivedAt(\DateTime::createFromImmutable($this->clock->now()));
return $data; return $data;
} }

View file

@ -41,7 +41,7 @@ final readonly class RequestResetPasswordTokenHandler
} }
$user->setPasswordResetToken($this->generator->generate()); $user->setPasswordResetToken($this->generator->generate());
$user->setPasswordRequestedAt($this->clock->now()); $user->setPasswordRequestedAt(\DateTime::createFromImmutable($this->clock->now()));
$this->commandBus->dispatch( $this->commandBus->dispatch(
new SendResetPasswordEmail( new SendResetPasswordEmail(

View file

@ -44,7 +44,7 @@ final readonly class VerifyShopUserHandler
); );
} }
$user->setVerifiedAt($this->clock->now()); $user->setVerifiedAt(\DateTime::createFromImmutable($this->clock->now()));
$user->setEmailVerificationToken(null); $user->setEmailVerificationToken(null);
$user->enable(); $user->enable();

View file

@ -24,7 +24,7 @@
], ],
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"doctrine/dbal": "^3.9", "doctrine/dbal": "^3.9 || ^4.0",
"api-platform/doctrine-orm": "^4.2.1", "api-platform/doctrine-orm": "^4.2.1",
"api-platform/symfony": "^4.2.1", "api-platform/symfony": "^4.2.1",
"lexik/jwt-authentication-bundle": "^3.1", "lexik/jwt-authentication-bundle": "^3.1",

View file

@ -41,7 +41,7 @@ final class ArchivingPromotionApplicatorTest extends TestCase
$this->clock->expects(self::once())->method('now')->willReturn($now); $this->clock->expects(self::once())->method('now')->willReturn($now);
$this->promotion->expects(self::once())->method('setArchivedAt')->with($now); $this->promotion->expects(self::once())->method('setArchivedAt')->with(\DateTime::createFromImmutable($now));
self::assertSame($this->promotion, $this->archivingPromotionApplicator->archive($this->promotion)); self::assertSame($this->promotion, $this->archivingPromotionApplicator->archive($this->promotion));
} }

View file

@ -64,12 +64,13 @@ final class RequestResetPasswordTokenHandlerTest extends TestCase
->method('findOneByEmail') ->method('findOneByEmail')
->with('test@email.com') ->with('test@email.com')
->willReturn($shopUser); ->willReturn($shopUser);
$this->clock->expects(self::once())->method('now')->willReturn(new \DateTimeImmutable()); $now = new \DateTimeImmutable();
$this->clock->expects(self::once())->method('now')->willReturn($now);
$this->generator->expects(self::once())->method('generate')->willReturn('TOKEN'); $this->generator->expects(self::once())->method('generate')->willReturn('TOKEN');
$shopUser->expects(self::once())->method('setPasswordResetToken')->with('TOKEN'); $shopUser->expects(self::once())->method('setPasswordResetToken')->with('TOKEN');
$shopUser->expects(self::once()) $shopUser->expects(self::once())
->method('setPasswordRequestedAt') ->method('setPasswordRequestedAt')
->with(self::isInstanceOf(\DateTimeImmutable::class)); ->with(\DateTime::createFromImmutable($now));
$sendResetPasswordEmail = new SendResetPasswordEmail('test@email.com', 'WEB', 'en_US'); $sendResetPasswordEmail = new SendResetPasswordEmail('test@email.com', 'WEB', 'en_US');
$this->messageBus->expects(self::once()) $this->messageBus->expects(self::once())
->method('dispatch') ->method('dispatch')

View file

@ -55,11 +55,12 @@ final class VerifyShopUserHandlerTest extends TestCase
->method('findOneBy') ->method('findOneBy')
->with(['emailVerificationToken' => 'ToKeN']) ->with(['emailVerificationToken' => 'ToKeN'])
->willReturn($user); ->willReturn($user);
$this->clock->expects(self::once())->method('now')->willReturn(new \DateTimeImmutable()); $now = new \DateTimeImmutable();
$this->clock->expects(self::once())->method('now')->willReturn($now);
$user->expects(self::once())->method('getEmail')->willReturn('shop@example.com'); $user->expects(self::once())->method('getEmail')->willReturn('shop@example.com');
$user->expects(self::once()) $user->expects(self::once())
->method('setVerifiedAt') ->method('setVerifiedAt')
->with($this->isInstanceOf(\DateTimeImmutable::class)); ->with(\DateTime::createFromImmutable($now));
$user->expects(self::once())->method('setEmailVerificationToken')->with(null); $user->expects(self::once())->method('setEmailVerificationToken')->with(null);
$user->expects(self::once())->method('enable'); $user->expects(self::once())->method('enable');
$this->commandBus->expects(self::once()) $this->commandBus->expects(self::once())

View file

@ -34,7 +34,7 @@
"symfony/framework-bundle": "^6.4.1 || ^7.4" "symfony/framework-bundle": "^6.4.1 || ^7.4"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -31,7 +31,7 @@
"symfony/framework-bundle": "^6.4.1 || ^7.4" "symfony/framework-bundle": "^6.4.1 || ^7.4"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -44,7 +44,7 @@ final class RequestResetPasswordEmailHandler
} }
$adminUser->setPasswordResetToken($this->generator->generate()); $adminUser->setPasswordResetToken($this->generator->generate());
$adminUser->setPasswordRequestedAt($this->clock->now()); $adminUser->setPasswordRequestedAt(\DateTime::createFromImmutable($this->clock->now()));
$this->commandBus->dispatch( $this->commandBus->dispatch(
new SendResetPasswordEmail($adminUser->getEmail(), $adminUser->getLocaleCode()), new SendResetPasswordEmail($adminUser->getEmail(), $adminUser->getLocaleCode()),

View file

@ -13,6 +13,8 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\DependencyInjection; namespace Sylius\Bundle\CoreBundle\DependencyInjection;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Mapping\ClassMetadata;
use Sylius\Bundle\CoreBundle\Attribute\AsCatalogPromotionApplicatorCriteria; use Sylius\Bundle\CoreBundle\Attribute\AsCatalogPromotionApplicatorCriteria;
use Sylius\Bundle\CoreBundle\Attribute\AsCatalogPromotionPriceCalculator; use Sylius\Bundle\CoreBundle\Attribute\AsCatalogPromotionPriceCalculator;
use Sylius\Bundle\CoreBundle\Attribute\AsEntityObserver; use Sylius\Bundle\CoreBundle\Attribute\AsEntityObserver;
@ -107,6 +109,7 @@ final class SyliusCoreExtension extends AbstractResourceExtension implements Pre
$this->prependDefaultDriver($container, $config['driver']); $this->prependDefaultDriver($container, $config['driver']);
$this->prependHwiOauth($container); $this->prependHwiOauth($container);
$this->prependDoctrineMigrations($container); $this->prependDoctrineMigrations($container);
$this->prependDoctrineIdentityGenerationPreferences($container);
} }
protected function getMigrationsNamespace(): string protected function getMigrationsNamespace(): string
@ -134,6 +137,21 @@ final class SyliusCoreExtension extends AbstractResourceExtension implements Pre
} }
} }
private function prependDoctrineIdentityGenerationPreferences(ContainerBuilder $container): void
{
if (!$container->hasExtension('doctrine')) {
return;
}
$container->prependExtensionConfig('doctrine', [
'orm' => [
'identity_generation_preferences' => [
PostgreSQLPlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
],
],
]);
}
private function prependHwiOauth(ContainerBuilder $container): void private function prependHwiOauth(ContainerBuilder $container): void
{ {
if (!$container->hasExtension('hwi_oauth')) { if (!$container->hasExtension('hwi_oauth')) {

View file

@ -13,12 +13,12 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Doctrine\DQL; namespace Sylius\Bundle\CoreBundle\Doctrine\DQL;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType; use Doctrine\ORM\Query\TokenType;
use Sylius\Bundle\CoreBundle\Doctrine\Platform\PlatformHelper;
final class Cast extends FunctionNode final class Cast extends FunctionNode
{ {
@ -48,7 +48,7 @@ final class Cast extends FunctionNode
$type = $this->type; $type = $this->type;
if (is_a($platform, MySQLPlatform::class, true) && 'text' === $type) { if (PlatformHelper::isMysql($platform) && 'text' === $type) {
$type = 'char'; $type = 'char';
} }

View file

@ -13,14 +13,12 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Doctrine\DQL; namespace Sylius\Bundle\CoreBundle\Doctrine\DQL;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType; use Doctrine\ORM\Query\TokenType;
use Sylius\Bundle\CoreBundle\Doctrine\Platform\PlatformHelper;
final class Day extends FunctionNode final class Day extends FunctionNode
{ {
@ -41,15 +39,15 @@ final class Day extends FunctionNode
{ {
$platform = $sqlWalker->getConnection()->getDatabasePlatform(); $platform = $sqlWalker->getConnection()->getDatabasePlatform();
if (is_a($platform, MySQLPlatform::class, true)) { if (PlatformHelper::isMysql($platform)) {
return sprintf('DAY(%s)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('DAY(%s)', $sqlWalker->walkArithmeticPrimary($this->date));
} }
if (is_a($platform, PostgreSQLPlatform::class, true)) { if (PlatformHelper::isPostgreSQL($platform)) {
return sprintf('EXTRACT(DAY FROM %s)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('EXTRACT(DAY FROM %s)', $sqlWalker->walkArithmeticPrimary($this->date));
} }
if (is_a($platform, SqlitePlatform::class, true)) { if (PlatformHelper::isSqlite($platform)) {
return sprintf('CAST(STRFTIME("%%d", %s) AS NUMBER)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('CAST(STRFTIME("%%d", %s) AS NUMBER)', $sqlWalker->walkArithmeticPrimary($this->date));
} }

View file

@ -13,14 +13,12 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Doctrine\DQL; namespace Sylius\Bundle\CoreBundle\Doctrine\DQL;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType; use Doctrine\ORM\Query\TokenType;
use Sylius\Bundle\CoreBundle\Doctrine\Platform\PlatformHelper;
final class Hour extends FunctionNode final class Hour extends FunctionNode
{ {
@ -41,15 +39,15 @@ final class Hour extends FunctionNode
{ {
$platform = $sqlWalker->getConnection()->getDatabasePlatform(); $platform = $sqlWalker->getConnection()->getDatabasePlatform();
if (is_a($platform, MySQLPlatform::class, true)) { if (PlatformHelper::isMysql($platform)) {
return sprintf('HOUR(%s)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('HOUR(%s)', $sqlWalker->walkArithmeticPrimary($this->date));
} }
if (is_a($platform, PostgreSQLPlatform::class, true)) { if (PlatformHelper::isPostgreSQL($platform)) {
return sprintf('EXTRACT(HOUR FROM %s)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('EXTRACT(HOUR FROM %s)', $sqlWalker->walkArithmeticPrimary($this->date));
} }
if (is_a($platform, SqlitePlatform::class, true)) { if (PlatformHelper::isSqlite($platform)) {
return sprintf('CAST(STRFTIME("%%H", %s) AS NUMBER)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('CAST(STRFTIME("%%H", %s) AS NUMBER)', $sqlWalker->walkArithmeticPrimary($this->date));
} }

View file

@ -13,14 +13,12 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Doctrine\DQL; namespace Sylius\Bundle\CoreBundle\Doctrine\DQL;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType; use Doctrine\ORM\Query\TokenType;
use Sylius\Bundle\CoreBundle\Doctrine\Platform\PlatformHelper;
final class Month extends FunctionNode final class Month extends FunctionNode
{ {
@ -41,15 +39,15 @@ final class Month extends FunctionNode
{ {
$platform = $sqlWalker->getConnection()->getDatabasePlatform(); $platform = $sqlWalker->getConnection()->getDatabasePlatform();
if (is_a($platform, MySQLPlatform::class, true)) { if (PlatformHelper::isMysql($platform)) {
return sprintf('MONTH(%s)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('MONTH(%s)', $sqlWalker->walkArithmeticPrimary($this->date));
} }
if (is_a($platform, PostgreSQLPlatform::class, true)) { if (PlatformHelper::isPostgreSQL($platform)) {
return sprintf('EXTRACT(MONTH FROM %s)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('EXTRACT(MONTH FROM %s)', $sqlWalker->walkArithmeticPrimary($this->date));
} }
if (is_a($platform, SqlitePlatform::class, true)) { if (PlatformHelper::isSqlite($platform)) {
return sprintf('CAST(STRFTIME("%%m", %s) AS NUMBER)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('CAST(STRFTIME("%%m", %s) AS NUMBER)', $sqlWalker->walkArithmeticPrimary($this->date));
} }

View file

@ -13,14 +13,12 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Doctrine\DQL; namespace Sylius\Bundle\CoreBundle\Doctrine\DQL;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType; use Doctrine\ORM\Query\TokenType;
use Sylius\Bundle\CoreBundle\Doctrine\Platform\PlatformHelper;
final class Week extends FunctionNode final class Week extends FunctionNode
{ {
@ -41,15 +39,15 @@ final class Week extends FunctionNode
{ {
$platform = $sqlWalker->getConnection()->getDatabasePlatform(); $platform = $sqlWalker->getConnection()->getDatabasePlatform();
if (is_a($platform, MySQLPlatform::class, true)) { if (PlatformHelper::isMysql($platform)) {
return sprintf('WEEK(%s)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('WEEK(%s)', $sqlWalker->walkArithmeticPrimary($this->date));
} }
if (is_a($platform, PostgreSQLPlatform::class, true)) { if (PlatformHelper::isPostgreSQL($platform)) {
return sprintf('EXTRACT(WEEK FROM %s)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('EXTRACT(WEEK FROM %s)', $sqlWalker->walkArithmeticPrimary($this->date));
} }
if (is_a($platform, SqlitePlatform::class, true)) { if (PlatformHelper::isSqlite($platform)) {
return sprintf('CAST(STRFTIME("%%W", %s) AS NUMBER)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('CAST(STRFTIME("%%W", %s) AS NUMBER)', $sqlWalker->walkArithmeticPrimary($this->date));
} }

View file

@ -13,14 +13,12 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Doctrine\DQL; namespace Sylius\Bundle\CoreBundle\Doctrine\DQL;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType; use Doctrine\ORM\Query\TokenType;
use Sylius\Bundle\CoreBundle\Doctrine\Platform\PlatformHelper;
final class Year extends FunctionNode final class Year extends FunctionNode
{ {
@ -41,15 +39,15 @@ final class Year extends FunctionNode
{ {
$platform = $sqlWalker->getConnection()->getDatabasePlatform(); $platform = $sqlWalker->getConnection()->getDatabasePlatform();
if (is_a($platform, MySQLPlatform::class, true)) { if (PlatformHelper::isMysql($platform)) {
return sprintf('YEAR(%s)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('YEAR(%s)', $sqlWalker->walkArithmeticPrimary($this->date));
} }
if (is_a($platform, PostgreSQLPlatform::class, true)) { if (PlatformHelper::isPostgreSQL($platform)) {
return sprintf('EXTRACT(YEAR FROM %s)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('EXTRACT(YEAR FROM %s)', $sqlWalker->walkArithmeticPrimary($this->date));
} }
if (is_a($platform, SqlitePlatform::class, true)) { if (PlatformHelper::isSqlite($platform)) {
return sprintf('CAST(STRFTIME("%%Y", %s) AS NUMBER)', $sqlWalker->walkArithmeticPrimary($this->date)); return sprintf('CAST(STRFTIME("%%Y", %s) AS NUMBER)', $sqlWalker->walkArithmeticPrimary($this->date));
} }

View file

@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Doctrine\Platform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
final class PlatformHelper
{
private function __construct()
{
}
public static function isMysql(object $platform): bool
{
return is_a($platform, MySQLPlatform::class, true);
}
public static function isPostgreSQL(object $platform): bool
{
return is_a($platform, PostgreSQLPlatform::class, true);
}
/** Compatibility layer for DBAL 3.x (SqlitePlatform) and 4.x (SQLitePlatform). */
public static function isSqlite(object $platform): bool
{
return str_contains(strtolower($platform::class), 'sqliteplatform');
}
}

View file

@ -18,6 +18,7 @@ use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Sylius\Bundle\CoreBundle\Doctrine\Platform\PlatformHelper;
use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@ -121,12 +122,6 @@ final class DatabaseSetupCommandsProvider implements DatabaseSetupCommandsProvid
private function isSQLite(): bool private function isSQLite(): bool
{ {
$platform = $this->entityManager->getConnection()->getDatabasePlatform(); return PlatformHelper::isSqlite($this->entityManager->getConnection()->getDatabasePlatform());
if (class_exists(\Doctrine\DBAL\Platforms\SqlitePlatform::class) && is_a($platform, \Doctrine\DBAL\Platforms\SqlitePlatform::class)) {
return true;
}
return false;
} }
} }

View file

@ -34,7 +34,7 @@ final class PriceChangeLogger implements PriceChangeLoggerInterface
$logEntry = $this->logEntryFactory->create( $logEntry = $this->logEntryFactory->create(
$channelPricing, $channelPricing,
$this->clock->now(), \DateTime::createFromImmutable($this->clock->now()),
$channelPricing->getPrice(), $channelPricing->getPrice(),
$channelPricing->getOriginalPrice(), $channelPricing->getOriginalPrice(),
); );

View file

@ -41,7 +41,6 @@ sylius_core:
doctrine: doctrine:
orm: orm:
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers: entity_managers:
default: default:
auto_mapping: true auto_mapping: true

View file

@ -55,8 +55,9 @@ final class DatabasePlatformDataProvider implements DataProviderInterface
{ {
try { try {
$platform = $connection->getDatabasePlatform(); $platform = $connection->getDatabasePlatform();
if ($platform instanceof AbstractPlatform) { $platformName = $this->getPlatformName($platform);
return $platform->getName(); if (null !== $platformName) {
return $platformName;
} }
} catch (\Throwable) { } catch (\Throwable) {
} }
@ -69,6 +70,27 @@ final class DatabasePlatformDataProvider implements DataProviderInterface
return null; return null;
} }
/** Compatibility layer for DBAL 3.x (getName()) and 4.x (class name based) */
private function getPlatformName(AbstractPlatform $platform): ?string
{
// DBAL 3.x has getName() method
if (method_exists($platform, 'getName')) {
return $platform->getName();
}
// DBAL 4.x removed getName(), use class name mapping
$className = strtolower($platform::class);
return match (true) {
str_contains($className, 'mysql') => 'mysql',
str_contains($className, 'postgresql') => 'postgresql',
str_contains($className, 'sqlite') => 'sqlite',
str_contains($className, 'sqlserver'), str_contains($className, 'sqlsrv') => 'mssql',
str_contains($className, 'oracle') => 'oracle',
default => null,
};
}
private function mapDriverToDatabase(string $driver): ?string private function mapDriverToDatabase(string $driver): ?string
{ {
return match (true) { return match (true) {

View file

@ -27,7 +27,7 @@
], ],
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"doctrine/dbal": "^3.9", "doctrine/dbal": "^3.9 || ^4.0",
"doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/doctrine-migrations-bundle": "^3.3",
"egulias/email-validator": "^4.0", "egulias/email-validator": "^4.0",
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",
@ -71,7 +71,7 @@
"phpstan/phpdoc-parser": ">= 2.0" "phpstan/phpdoc-parser": ">= 2.0"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"hwi/oauth-bundle": "^2.2", "hwi/oauth-bundle": "^2.2",
"matthiasnoback/symfony-config-test": "^6.0", "matthiasnoback/symfony-config-test": "^6.0",

View file

@ -13,7 +13,6 @@ declare(strict_types=1);
namespace Tests\Sylius\Bundle\CoreBundle\Calculator; namespace Tests\Sylius\Bundle\CoreBundle\Calculator;
use DateTime;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Sylius\Bundle\CoreBundle\Calculator\DelayStampCalculator; use Sylius\Bundle\CoreBundle\Calculator\DelayStampCalculator;
use Sylius\Bundle\CoreBundle\Calculator\DelayStampCalculatorInterface; use Sylius\Bundle\CoreBundle\Calculator\DelayStampCalculatorInterface;
@ -35,8 +34,8 @@ final class DelayStampCalculatorTest extends TestCase
public function testCalculatesDelayStampFromGivenDates(): void public function testCalculatesDelayStampFromGivenDates(): void
{ {
$currentTime = new DateTime('2021-11-11 20:20'); $currentTime = new \DateTime('2021-11-11 20:20');
$targetTime = new DateTime('2021-11-11 20:21'); $targetTime = new \DateTime('2021-11-11 20:21');
$expectedStamp = new DelayStamp(60000); $expectedStamp = new DelayStamp(60000);
@ -47,8 +46,8 @@ final class DelayStampCalculatorTest extends TestCase
public function testReturns0DelayIfDatesTargetTimeIsSmallerThanCurrentTime(): void public function testReturns0DelayIfDatesTargetTimeIsSmallerThanCurrentTime(): void
{ {
$currentTime = new DateTime('2021-11-11 20:21'); $currentTime = new \DateTime('2021-11-11 20:21');
$targetTime = new DateTime('2021-11-11 19:05'); $targetTime = new \DateTime('2021-11-11 19:05');
$expectedStamp = new DelayStamp(0); $expectedStamp = new DelayStamp(0);

View file

@ -78,7 +78,7 @@ final class RequestResetPasswordEmailHandlerTest extends TestCase
$adminUser->expects($this->once())->method('getEmail')->willReturn('admin@example.com'); $adminUser->expects($this->once())->method('getEmail')->willReturn('admin@example.com');
$adminUser->expects($this->once())->method('getLocaleCode')->willReturn('en_US'); $adminUser->expects($this->once())->method('getLocaleCode')->willReturn('en_US');
$adminUser->expects($this->once())->method('setPasswordResetToken')->with('sometoken'); $adminUser->expects($this->once())->method('setPasswordResetToken')->with('sometoken');
$adminUser->expects($this->once())->method('setPasswordRequestedAt')->with($now); $adminUser->expects($this->once())->method('setPasswordRequestedAt')->with(\DateTime::createFromImmutable($now));
$expectedMessage = new SendResetPasswordEmail('admin@example.com', 'en_US'); $expectedMessage = new SendResetPasswordEmail('admin@example.com', 'en_US');

View file

@ -66,7 +66,7 @@ final class AtomicOrderPromotionsUsageModifierTest extends TestCase
$this->entityManager $this->entityManager
->expects($this->exactly(2)) ->expects($this->exactly(2))
->method('lock') ->method('lock')
->willReturnCallback(function (object $entity, int $lockMode, mixed $version) use ($promotion, $coupon): void { ->willReturnCallback(function (object $entity, int|LockMode $lockMode, mixed $version) use ($promotion, $coupon): void {
$this->assertSame(LockMode::OPTIMISTIC, $lockMode); $this->assertSame(LockMode::OPTIMISTIC, $lockMode);
if ($entity === $promotion) { if ($entity === $promotion) {
@ -134,7 +134,7 @@ final class AtomicOrderPromotionsUsageModifierTest extends TestCase
$this->entityManager $this->entityManager
->expects($this->exactly(2)) ->expects($this->exactly(2))
->method('lock') ->method('lock')
->willReturnCallback(function (object $entity, int $lockMode, mixed $version) use ($promotion, $coupon): void { ->willReturnCallback(function (object $entity, int|LockMode $lockMode, mixed $version) use ($promotion, $coupon): void {
$this->assertSame(LockMode::OPTIMISTIC, $lockMode); $this->assertSame(LockMode::OPTIMISTIC, $lockMode);
if ($entity === $promotion) { if ($entity === $promotion) {
@ -202,7 +202,7 @@ final class AtomicOrderPromotionsUsageModifierTest extends TestCase
$this->entityManager $this->entityManager
->expects($this->exactly(2)) ->expects($this->exactly(2))
->method('lock') ->method('lock')
->willReturnCallback(function (object $entity, int $lockMode, mixed $version) use ($firstPromotion, $secondPromotion): void { ->willReturnCallback(function (object $entity, int|LockMode $lockMode, mixed $version) use ($firstPromotion, $secondPromotion): void {
$this->assertSame(LockMode::OPTIMISTIC, $lockMode); $this->assertSame(LockMode::OPTIMISTIC, $lockMode);
if ($entity === $firstPromotion) { if ($entity === $firstPromotion) {

View file

@ -76,7 +76,7 @@ final class PriceChangeLoggerTest extends TestCase
$this->logEntryFactory $this->logEntryFactory
->expects($this->once()) ->expects($this->once())
->method('create') ->method('create')
->with($channelPricing, $now, $price, $originalPrice) ->with($channelPricing, \DateTime::createFromImmutable($now), $price, $originalPrice)
->willReturn($logEntry) ->willReturn($logEntry)
; ;

View file

@ -32,7 +32,7 @@
"symfony/intl": "^6.4 || ^7.4" "symfony/intl": "^6.4 || ^7.4"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -44,7 +44,7 @@
"webmozart/assert": "^1.11" "webmozart/assert": "^1.11"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",
"symfony/browser-kit": "^6.4 || ^7.4", "symfony/browser-kit": "^6.4 || ^7.4",
"symfony/dependency-injection": "^6.4.1 || ^7.4", "symfony/dependency-injection": "^6.4.1 || ^7.4",

View file

@ -36,7 +36,7 @@
"phpstan/phpdoc-parser": ">= 2.0" "phpstan/phpdoc-parser": ">= 2.0"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",
"symfony/browser-kit": "^6.4 || ^7.4", "symfony/browser-kit": "^6.4 || ^7.4",

View file

@ -33,7 +33,7 @@
"symfony/framework-bundle": "^6.4.1 || ^7.4" "symfony/framework-bundle": "^6.4.1 || ^7.4"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -32,7 +32,7 @@
"webmozart/assert": "^1.11" "webmozart/assert": "^1.11"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",
"sylius/currency-bundle": "^2.0", "sylius/currency-bundle": "^2.0",

View file

@ -36,7 +36,7 @@
"symfony/service-contracts": "^3.5" "symfony/service-contracts": "^3.5"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\PaymentBundle\Doctrine\DBAL\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
/**
* Provides the "object" type for DBAL 4.x compatibility.
*
* The "object" type was removed in DBAL 4.0. This custom type restores it
* so that existing entity mappings using type="object" continue to work
* across both DBAL 3.x and 4.x.
*/
final class ObjectType extends Type
{
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
{
return $platform->getClobTypeDeclarationSQL($column);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform): string
{
return serialize($value);
}
public function convertToPHPValue($value, AbstractPlatform $platform): mixed
{
if ($value === null) {
return null;
}
if (is_resource($value)) {
$value = stream_get_contents($value);
}
set_error_handler(static function (int $code, string $message): never {
throw new \RuntimeException(sprintf('Could not unserialize object: %s', $message));
});
try {
return unserialize($value);
} finally {
restore_error_handler();
}
}
public function getName(): string
{
return 'object';
}
}

View file

@ -7,6 +7,11 @@ imports:
parameters: parameters:
env(SYLIUS_PAYMENT_ENCRYPTION_KEY_PATH): '%kernel.project_dir%/config/encryption/key' env(SYLIUS_PAYMENT_ENCRYPTION_KEY_PATH): '%kernel.project_dir%/config/encryption/key'
doctrine:
dbal:
types:
object: Sylius\Bundle\PaymentBundle\Doctrine\DBAL\Type\ObjectType
sylius_payment: sylius_payment:
payment_request: payment_request:
states_to_be_cancelled_when_payment_method_changed: states_to_be_cancelled_when_payment_method_changed:

View file

@ -35,7 +35,7 @@
"symfony/messenger": "^6.4 || ^7.4" "symfony/messenger": "^6.4 || ^7.4"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -13,7 +13,6 @@ declare(strict_types=1);
namespace Sylius\Bundle\ProductBundle\Doctrine\ORM\Query\AST\Function; namespace Sylius\Bundle\ProductBundle\Doctrine\ORM\Query\AST\Function;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\SqlWalker;
@ -22,13 +21,10 @@ use Doctrine\ORM\Query\SqlWalker;
*/ */
abstract class AbstractPostgresqlJsonFunctionNode extends AbstractJsonFunctionNode abstract class AbstractPostgresqlJsonFunctionNode extends AbstractJsonFunctionNode
{ {
/**
* @throws Exception
*/
protected function validatePlatform(SqlWalker $sqlWalker): void protected function validatePlatform(SqlWalker $sqlWalker): void
{ {
if (!$sqlWalker->getConnection()->getDatabasePlatform() instanceof PostgreSQLPlatform) { if (!$sqlWalker->getConnection()->getDatabasePlatform() instanceof PostgreSQLPlatform) {
throw Exception::notSupported(static::FUNCTION_NAME); throw new \LogicException(sprintf('Function "%s" is not supported on this platform.', static::FUNCTION_NAME));
} }
} }

View file

@ -35,7 +35,7 @@
"symfony/framework-bundle": "^6.4.1 || ^7.4" "symfony/framework-bundle": "^6.4.1 || ^7.4"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -40,7 +40,7 @@
"phpstan/phpdoc-parser": ">= 2.0" "phpstan/phpdoc-parser": ">= 2.0"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -47,7 +47,7 @@
"symfony/framework-bundle": "^6.4.1 || ^7.4" "symfony/framework-bundle": "^6.4.1 || ^7.4"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",
"symfony/browser-kit": "^6.4 || ^7.4", "symfony/browser-kit": "^6.4 || ^7.4",

View file

@ -24,6 +24,6 @@ final class ShippingDateAssigner implements ShippingDateAssignerInterface
public function assign(ShipmentInterface $shipment): void public function assign(ShipmentInterface $shipment): void
{ {
$shipment->setShippedAt($this->clock->now()); $shipment->setShippedAt(\DateTime::createFromImmutable($this->clock->now()));
} }
} }

View file

@ -37,7 +37,7 @@
"symfony/framework-bundle": "^6.4.1 || ^7.4" "symfony/framework-bundle": "^6.4.1 || ^7.4"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -44,7 +44,7 @@ final class ShippingDateAssignerTest extends TestCase
$shipment = $this->createMock(ShipmentInterface::class); $shipment = $this->createMock(ShipmentInterface::class);
$this->clock->expects($this->once())->method('now')->willReturn(new DateTimeImmutable('20-05-2019 20:20:20')); $this->clock->expects($this->once())->method('now')->willReturn(new DateTimeImmutable('20-05-2019 20:20:20'));
$shipment->expects($this->once())->method('setShippedAt')->with(new DateTimeImmutable('20-05-2019 20:20:20')); $shipment->expects($this->once())->method('setShippedAt')->with(new \DateTime('20-05-2019 20:20:20'));
$this->shippingDateAssigner->assign($shipment); $this->shippingDateAssigner->assign($shipment);
} }

View file

@ -36,7 +36,7 @@
"symfony/framework-bundle": "^6.4.1 || ^7.4" "symfony/framework-bundle": "^6.4.1 || ^7.4"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -35,7 +35,7 @@
"symfony/framework-bundle": "^6.4.1 || ^7.4" "symfony/framework-bundle": "^6.4.1 || ^7.4"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"doctrine/orm": "^2.18 || ^3.5", "doctrine/orm": "^2.18 || ^3.5",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",
"symfony/dependency-injection": "^6.4.1 || ^7.4" "symfony/dependency-injection": "^6.4.1 || ^7.4"

View file

@ -48,7 +48,7 @@
"webmozart/assert": "^1.11" "webmozart/assert": "^1.11"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13 || ^3.0",
"hwi/oauth-bundle": "^2.2", "hwi/oauth-bundle": "^2.2",
"matthiasnoback/symfony-dependency-injection-test": "^6.0", "matthiasnoback/symfony-dependency-injection-test": "^6.0",
"phpunit/phpunit": "^11.5", "phpunit/phpunit": "^11.5",

View file

@ -52,7 +52,8 @@ final class PromotionCouponGenerator implements PromotionCouponGeneratorInterfac
$coupon->setPromotion($promotion); $coupon->setPromotion($promotion);
$coupon->setCode($code); $coupon->setCode($code);
$coupon->setUsageLimit($instruction->getUsageLimit()); $coupon->setUsageLimit($instruction->getUsageLimit());
$coupon->setExpiresAt($instruction->getExpiresAt()); $expiresAt = $instruction->getExpiresAt();
$coupon->setExpiresAt($expiresAt instanceof \DateTimeImmutable ? \DateTime::createFromImmutable($expiresAt) : $expiresAt);
$generatedCoupons[$code] = $coupon; $generatedCoupons[$code] = $coupon;

View file

@ -194,12 +194,6 @@
"instaclick/php-webdriver": { "instaclick/php-webdriver": {
"version": "1.4.7" "version": "1.4.7"
}, },
"knplabs/gaufrette": {
"version": "v0.8.3"
},
"knplabs/knp-gaufrette-bundle": {
"version": "v0.7.1"
},
"knplabs/knp-menu": { "knplabs/knp-menu": {
"version": "v3.1.2" "version": "v3.1.2"
}, },

View file

@ -263,7 +263,9 @@ trait OrderPlacerTrait
): OrderInterface { ): OrderInterface {
$objectManager = $this->get('doctrine.orm.entity_manager'); $objectManager = $this->get('doctrine.orm.entity_manager');
$order->setCheckoutCompletedAt($checkoutCompletedAt); $order->setCheckoutCompletedAt(
$checkoutCompletedAt !== null ? \DateTime::createFromImmutable($checkoutCompletedAt) : null,
);
$objectManager->flush(); $objectManager->flush();

View file

@ -13,25 +13,14 @@ declare(strict_types=1);
namespace Sylius\Tests\Functional\Doctrine\Mock; namespace Sylius\Tests\Functional\Doctrine\Mock;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver;
class ConnectionMock extends Connection class ConnectionMock extends Connection
{ {
/** @var DatabasePlatformMock */ public function __construct(array $params = [], ?Driver $driver = null, ?Configuration $config = null)
private $_platformMock;
public function __construct(array $params = [], ?Driver $driver = null, ?Configuration $config = null, ?EventManager $eventManager = null)
{ {
$this->_platformMock = new DatabasePlatformMock(); parent::__construct($params, $driver ?? new DriverMock(), $config);
parent::__construct($params, $driver ?? new DriverMock(), $config, $eventManager);
}
public function getDatabasePlatform()
{
return $this->_platformMock;
} }
} }

View file

@ -23,32 +23,39 @@ class DatabasePlatformMock extends AbstractPlatform
return true; return true;
} }
public function getBooleanTypeDeclarationSQL(array $field) public function getBooleanTypeDeclarationSQL(array $field): string
{ {
return 'BOOLEAN';
} }
public function getIntegerTypeDeclarationSQL(array $field) public function getIntegerTypeDeclarationSQL(array $field): string
{ {
return 'INT';
} }
public function getBigIntTypeDeclarationSQL(array $field) public function getBigIntTypeDeclarationSQL(array $field): string
{ {
return 'BIGINT';
} }
public function getSmallIntTypeDeclarationSQL(array $field) public function getSmallIntTypeDeclarationSQL(array $field): string
{ {
return 'SMALLINT';
} }
protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef): string
{ {
return 'INT';
} }
public function getVarcharTypeDeclarationSQL(array $field) public function getVarcharTypeDeclarationSQL(array $field): string
{ {
return 'VARCHAR';
} }
public function getClobTypeDeclarationSQL(array $field) public function getClobTypeDeclarationSQL(array $field): string
{ {
return 'CLOB';
} }
public function getName(): string public function getName(): string

View file

@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Tests\Functional\Doctrine\Mock;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Statement;
class Dbal4DriverConnectionMock implements Connection
{
public function prepare(string $sql): Statement
{
return new Dbal4StatementMock();
}
public function query(string $sql): Result
{
return new DriverResultMock();
}
public function quote(string $value): string
{
return $value;
}
public function exec(string $sql): int|string
{
throw new \BadMethodCallException('Not implemented');
}
public function lastInsertId(): int|string
{
return 0;
}
public function beginTransaction(): void
{
}
public function commit(): void
{
}
public function rollBack(): void
{
}
public function getNativeConnection(): mixed
{
throw new \BadMethodCallException('Not implemented');
}
public function getServerVersion(): string
{
return '3.0.0';
}
}

View file

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Tests\Functional\Doctrine\Mock;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\ParameterType;
class Dbal4StatementMock implements Statement
{
public function bindValue(int|string $param, mixed $value, ParameterType $type = ParameterType::STRING): void
{
}
public function execute(): Result
{
return new DriverResultMock();
}
}

View file

@ -20,47 +20,43 @@ use Doctrine\DBAL\ParameterType;
class DriverConnectionMock implements Connection class DriverConnectionMock implements Connection
{ {
public function prepare($prepareString): Statement public function prepare(string $sql): Statement
{ {
return new StatementMock(); return new StatementMock();
} }
public function query(?string $sql = null): Result public function query(string $sql): Result
{ {
return new DriverResultMock(); return new DriverResultMock();
} }
public function quote($input, $type = ParameterType::STRING) public function quote($value, $type = ParameterType::STRING)
{ {
return (string) $input; return (string) $value;
} }
public function exec($statement): int public function exec(string $sql): int
{ {
throw new \BadMethodCallException('Not implemented'); throw new \BadMethodCallException('Not implemented');
} }
public function lastInsertId($name = null) public function lastInsertId($name = null): string|int|false
{ {
return false;
} }
public function beginTransaction() public function beginTransaction(): bool
{ {
return true;
} }
public function commit() public function commit(): bool
{ {
return true;
} }
public function rollBack() public function rollBack(): bool
{
}
public function errorCode()
{
}
public function errorInfo()
{ {
return true;
} }
} }

View file

@ -13,41 +13,18 @@ declare(strict_types=1);
namespace Sylius\Tests\Functional\Doctrine\Mock; namespace Sylius\Tests\Functional\Doctrine\Mock;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\AbstractSQLiteDriver;
use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\API\ExceptionConverter; use Doctrine\DBAL\ServerVersionProvider;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
class DriverMock implements Driver class DriverMock extends AbstractSQLiteDriver
{ {
public function connect(array $params, $username = null, $password = null, array $driverOptions = []) public function connect(array $params): DriverConnection
{ {
if (is_a(DriverConnection::class, ServerVersionProvider::class, true)) {
return new Dbal4DriverConnectionMock();
}
return new DriverConnectionMock(); return new DriverConnectionMock();
} }
public function getDatabasePlatform()
{
throw new \BadMethodCallException('Not implemented');
}
public function getSchemaManager(Connection $conn, ?AbstractPlatform $platform = null): AbstractSchemaManager
{
throw new \BadMethodCallException('Not implemented');
}
public function getExceptionConverter(): ExceptionConverter
{
throw new \BadMethodCallException('Not implemented');
}
public function getName()
{
throw new \BadMethodCallException('Not implemented');
}
public function getDatabase(Connection $conn)
{
throw new \BadMethodCallException('Not implemented');
}
} }

View file

@ -21,26 +21,27 @@ use Doctrine\DBAL\Driver\Result;
*/ */
class DriverResultMock implements Result class DriverResultMock implements Result
{ {
/** @var list<array<string, mixed>> */
private array $resultSet; private array $resultSet;
/** /**
* Creates a new mock statement that will serve the provided fake result set to clients. * Creates a new mock statement that will serve the provided fake result set to clients.
* *
* @param array $resultSet The faked SQL result set. * @param list<array<string, mixed>> $resultSet The faked SQL result set.
*/ */
public function __construct(array $resultSet = []) public function __construct(array $resultSet = [])
{ {
$this->resultSet = $resultSet; $this->resultSet = $resultSet;
} }
public function fetchNumeric() public function fetchNumeric(): array|false
{ {
$row = $this->fetchAssociative(); $row = $this->fetchAssociative();
return $row === false ? false : array_values($row); return $row === false ? false : array_values($row);
} }
public function fetchAssociative() public function fetchAssociative(): array|false
{ {
$current = current($this->resultSet); $current = current($this->resultSet);
next($this->resultSet); next($this->resultSet);
@ -48,7 +49,7 @@ class DriverResultMock implements Result
return $current; return $current;
} }
public function fetchOne() public function fetchOne(): mixed
{ {
$row = $this->fetchNumeric(); $row = $this->fetchNumeric();

View file

@ -21,12 +21,14 @@ use Traversable;
class StatementMock implements IteratorAggregate, Statement class StatementMock implements IteratorAggregate, Statement
{ {
public function bindValue($param, $value, $type = null) public function bindValue($param, $value, $type = null): bool
{ {
return true;
} }
public function bindParam($column, &$variable, $type = null, $length = null) public function bindParam($param, &$variable, $type = null, $length = null): bool
{ {
return true;
} }
public function execute($params = null): Result public function execute($params = null): Result