diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 1ce1a90c1b0..ac2788b39a4 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -8,7 +8,7 @@ on: - .github/workflows/coding-standards.yml - bin/** - composer.* - - lib/** + - src/** - phpcs.xml.dist - tests/** push: @@ -18,7 +18,7 @@ on: - .github/workflows/coding-standards.yml - bin/** - composer.* - - lib/** + - src/** - phpcs.xml.dist - tests/** diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 6af2ac37939..07d12226501 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -8,7 +8,7 @@ on: - .github/workflows/continuous-integration.yml - ci/** - composer.* - - lib/** + - src/** - phpunit.xml.dist - tests/** push: @@ -18,7 +18,7 @@ on: - .github/workflows/continuous-integration.yml - ci/** - composer.* - - lib/** + - src/** - phpunit.xml.dist - tests/** diff --git a/.github/workflows/phpbench.yml b/.github/workflows/phpbench.yml index a0a1efe6c0c..d98e7fa2158 100644 --- a/.github/workflows/phpbench.yml +++ b/.github/workflows/phpbench.yml @@ -8,7 +8,7 @@ on: paths: - .github/workflows/phpbench.yml - composer.* - - lib/** + - src/** - phpbench.json - tests/** push: @@ -17,7 +17,7 @@ on: paths: - .github/workflows/phpbench.yml - composer.* - - lib/** + - src/** - phpbench.json - tests/** diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 921ccf8115c..025f29ea02d 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -7,7 +7,7 @@ on: paths: - .github/workflows/static-analysis.yml - composer.* - - lib/** + - src/** - phpstan* - psalm* - tests/Doctrine/StaticAnalysis/** @@ -17,7 +17,7 @@ on: paths: - .github/workflows/static-analysis.yml - composer.* - - lib/** + - src/** - phpstan* - psalm* - tests/Doctrine/StaticAnalysis/** diff --git a/.gitignore b/.gitignore index 4b84f4214b1..0b0720faa94 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,6 @@ logs/ reports/ dist/ download/ -lib/api/ -lib/Doctrine/Common -lib/Doctrine/DBAL /.settings/ .buildpath .project diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 8727b0dadbc..00000000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "docs/en/_theme"] - path = docs/en/_theme - url = git://github.com/doctrine/doctrine-sphinx-theme.git -[submodule "lib/vendor/doctrine-build-common"] - path = lib/vendor/doctrine-build-common - url = git://github.com/doctrine/doctrine-build-common.git diff --git a/README.md b/README.md index b72a261a80f..22aab0538c4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -| [3.0.x][3.0] | [2.16.x][2.16] | [2.15.x][2.15] | +| [3.0.x][3.0] | [2.18.x][2.18] | [2.17.x][2.17] | |:----------------:|:----------------:|:----------:| -| [![Build status][3.0 image]][3.0] | [![Build status][2.16 image]][2.16] | [![Build status][2.15 image]][2.15] | -| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.16 coverage image]][2.16 coverage] | [![Coverage Status][2.15 coverage image]][2.15 coverage] | +| [![Build status][3.0 image]][3.0] | [![Build status][2.18 image]][2.18] | [![Build status][2.17 image]][2.17] | +| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.18 coverage image]][2.18 coverage] | [![Coverage Status][2.17 coverage image]][2.17 coverage] | [

πŸ‡ΊπŸ‡¦ UKRAINE NEEDS YOUR HELP NOW!

](https://www.doctrine-project.org/stop-war.html) @@ -22,11 +22,11 @@ without requiring unnecessary code duplication. [3.0]: https://github.com/doctrine/orm/tree/3.0.x [3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg [3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.x - [2.16 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.16.x - [2.16]: https://github.com/doctrine/orm/tree/2.16.x - [2.16 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.16.x/graph/badge.svg - [2.16 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.16.x - [2.15 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.15.x - [2.15]: https://github.com/doctrine/orm/tree/2.15.x - [2.15 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.15.x/graph/badge.svg - [2.15 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.15.x + [2.18 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.18.x + [2.18]: https://github.com/doctrine/orm/tree/2.18.x + [2.18 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.18.x/graph/badge.svg + [2.18 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.18.x + [2.17 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.17.x + [2.17]: https://github.com/doctrine/orm/tree/2.17.x + [2.17 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.17.x/graph/badge.svg + [2.17 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.17.x diff --git a/SECURITY.md b/SECURITY.md index d7013cb4069..b0e72932b29 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,6 +13,5 @@ understand the assumptions we make. - [DBAL Security Page](https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/security.html) - [ORM Security Page](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/reference/security.html) -If you find a Security bug in Doctrine, please report it on Jira and change the -Security Level to "Security Issues". It will be visible to Doctrine Core -developers and you only. +If you find a Security bug in Doctrine, please follow our +[Security reporting guidelines](https://www.doctrine-project.org/policies/security.html#reporting). diff --git a/docs/en/cookbook/aggregate-fields.rst b/docs/en/cookbook/aggregate-fields.rst index c9635fa52d6..001d70d34b4 100644 --- a/docs/en/cookbook/aggregate-fields.rst +++ b/docs/en/cookbook/aggregate-fields.rst @@ -36,71 +36,50 @@ Our entities look like: namespace Bank\Entities; use Doctrine\ORM\Mapping as ORM; - - /** - * @ORM\Entity - */ + use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\Common\Collections\Collection; + + #[ORM\Entity] class Account { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] private ?int $id; - - /** - * @ORM\Column(type="string", unique=true) - */ - private string $no; - - /** - * @ORM\OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"}) - */ - private array $entries; - - /** - * @ORM\Column(type="integer") - */ - private int $maxCredit = 0; - - public function __construct(string $no, int $maxCredit = 0) - { - $this->no = $no; - $this->maxCredit = $maxCredit; - $this->entries = new \Doctrine\Common\Collections\ArrayCollection(); + + #[ORM\OneToMany(targetEntity: Entry::class, mappedBy: 'account', cascade: ['persist'])] + private Collection $entries; + + + public function __construct( + #[ORM\Column(type: 'string', unique: true)] + private string $no, + + #[ORM\Column(type: 'integer')] + private int $maxCredit = 0, + ) { + $this->entries = new ArrayCollection(); } } - - /** - * @ORM\Entity - */ + + #[ORM\Entity] class Entry { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] private ?int $id; - - /** - * @ORM\ManyToOne(targetEntity="Account", inversedBy="entries") - */ - private Account $account; - - /** - * @ORM\Column(type="integer") - */ - private int $amount; - - public function __construct(Account $account, int $amount) - { - $this->account = $account; - $this->amount = $amount; + + public function __construct( + #[ORM\ManyToOne(targetEntity: Account::class, inversedBy: 'entries')] + private Account $account, + + #[ORM\Column(type: 'integer')] + private int $amount, + ) { // more stuff here, from/to whom, stated reason, execution date and such } - + public function getAmount(): Amount { return $this->amount; @@ -193,9 +172,8 @@ relation with this method: public function addEntry(int $amount): void { $this->assertAcceptEntryAllowed($amount); - - $e = new Entry($this, $amount); - $this->entries[] = $e; + + $this->entries[] = new Entry($this, $amount); } } @@ -213,18 +191,18 @@ Now look at the following test-code for our entities: { $account = new Account("123456", maxCredit: 200); $this->assertEquals(0, $account->getBalance()); - + $account->addEntry(500); $this->assertEquals(500, $account->getBalance()); - + $account->addEntry(-700); $this->assertEquals(-200, $account->getBalance()); } - + public function testExceedMaxLimit() { $account = new Account("123456", maxCredit: 200); - + $this->expectException(Exception::class); $account->addEntry(-1000); } @@ -285,22 +263,19 @@ entries collection) we want to add an aggregate field called balance; } - + public function addEntry(int $amount): void { $this->assertAcceptEntryAllowed($amount); - - $e = new Entry($this, $amount); - $this->entries[] = $e; + + $this->entries[] = new Entry($this, $amount); $this->balance += $amount; } } @@ -331,13 +306,13 @@ potentially lead to inconsistent state. See this example: // The Account $accId has a balance of 0 and a max credit limit of 200: // request 1 account $account1 = $em->find(Account::class, $accId); - + // request 2 account $account2 = $em->find(Account::class, $accId); - + $account1->addEntry(-200); $account2->addEntry(-200); - + // now request 1 and 2 both flush the changes. The aggregate field ``Account::$balance`` is now -200, however the @@ -357,10 +332,8 @@ Optimistic locking is as easy as adding a version column: class Account { - /** - * @ORM\Column(type="integer") - * @ORM\Version - */ + #[ORM\Column(type: 'integer')] + #[ORM\Version] private int $version; } diff --git a/docs/en/cookbook/resolve-target-entity-listener.rst b/docs/en/cookbook/resolve-target-entity-listener.rst index 04a50e56043..294c5779af4 100644 --- a/docs/en/cookbook/resolve-target-entity-listener.rst +++ b/docs/en/cookbook/resolve-target-entity-listener.rst @@ -47,10 +47,8 @@ A Customer entity use Acme\CustomerModule\Entity\Customer as BaseCustomer; use Acme\InvoiceModule\Model\InvoiceSubjectInterface; - /** - * @ORM\Entity - * @ORM\Table(name="customer") - */ + #[ORM\Entity] + #[ORM\Table(name: 'customer')] class Customer extends BaseCustomer implements InvoiceSubjectInterface { // In our example, any methods defined in the InvoiceSubjectInterface @@ -69,19 +67,12 @@ An Invoice entity use Doctrine\ORM\Mapping AS ORM; use Acme\InvoiceModule\Model\InvoiceSubjectInterface; - /** - * Represents an Invoice. - * - * @ORM\Entity - * @ORM\Table(name="invoice") - */ + #[ORM\Entity] + #[ORM\Table(name: 'invoice')] class Invoice { - /** - * @ORM\ManyToOne(targetEntity="Acme\InvoiceModule\Model\InvoiceSubjectInterface") - * @var InvoiceSubjectInterface - */ - protected $subject; + #[ORM\ManyToOne(targetEntity: InvoiceSubjectInterface::class)] + protected InvoiceSubjectInterface $subject; } An InvoiceSubjectInterface diff --git a/docs/en/reference/security.rst b/docs/en/reference/security.rst index 51e6a3903a6..53d2a87ab60 100644 --- a/docs/en/reference/security.rst +++ b/docs/en/reference/security.rst @@ -12,9 +12,8 @@ page only handles Security issues in the ORM. - `DBAL Security Page ` -If you find a Security bug in Doctrine, please report it on Jira and change the -Security Level to "Security Issues". It will be visible to Doctrine Core -developers and you only. +If you find a Security bug in Doctrine, please follow our +`Security reporting guidelines `_. User input and Doctrine ORM --------------------------- diff --git a/phpstan-dbal3.neon b/phpstan-dbal3.neon index cb6fb45261c..a223cd4b792 100644 --- a/phpstan-dbal3.neon +++ b/phpstan-dbal3.neon @@ -31,6 +31,3 @@ parameters: message: '#Negated boolean expression is always false\.#' paths: - src/Mapping/Driver/AttributeDriver.php - - src/Mapping/Driver/SimplifiedXmlDriver.php - - src/Mapping/Driver/XmlDriver.php - - src/ORMSetup.php diff --git a/phpstan.neon b/phpstan.neon index 90fda7be9f1..93e3875d269 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -49,6 +49,3 @@ parameters: message: '#Negated boolean expression is always false\.#' paths: - src/Mapping/Driver/AttributeDriver.php - - src/Mapping/Driver/SimplifiedXmlDriver.php - - src/Mapping/Driver/XmlDriver.php - - src/ORMSetup.php diff --git a/src/Mapping/DefaultTypedFieldMapper.php b/src/Mapping/DefaultTypedFieldMapper.php index 7a9aadc9254..e83cc9ea47c 100644 --- a/src/Mapping/DefaultTypedFieldMapper.php +++ b/src/Mapping/DefaultTypedFieldMapper.php @@ -55,7 +55,15 @@ public function validateAndComplete(array $mapping, ReflectionProperty $field): $mapping['enumType'] = $type->getName(); $reflection = new ReflectionEnum($type->getName()); - $type = $reflection->getBackingType(); + if (! $reflection->isBacked()) { + throw MappingException::backedEnumTypeRequired( + $field->class, + $mapping['fieldName'], + $mapping['enumType'], + ); + } + + $type = $reflection->getBackingType(); assert($type instanceof ReflectionNamedType); } diff --git a/src/Mapping/MappingException.php b/src/Mapping/MappingException.php index bd50bb25111..9b732427642 100644 --- a/src/Mapping/MappingException.php +++ b/src/Mapping/MappingException.php @@ -622,6 +622,16 @@ public static function invalidOverrideType(string $expectdType, mixed $givenValu )); } + public static function backedEnumTypeRequired(string $className, string $fieldName, string $enumType): self + { + return new self(sprintf( + 'Attempting to map a non-backed enum type %s in entity %s::$%s. Please use backed enums only', + $enumType, + $className, + $fieldName, + )); + } + public static function nonEnumTypeMapped(string $className, string $fieldName, string $enumType): self { return new self(sprintf( diff --git a/src/Tools/Pagination/LimitSubqueryOutputWalker.php b/src/Tools/Pagination/LimitSubqueryOutputWalker.php index 6ae0e2f6375..8bbc44c21a1 100644 --- a/src/Tools/Pagination/LimitSubqueryOutputWalker.php +++ b/src/Tools/Pagination/LimitSubqueryOutputWalker.php @@ -232,7 +232,7 @@ public function walkSelectStatementWithoutRowNumber(SelectStatement $AST, bool $ $innerSql, ); - // http://www.doctrine-project.org/jira/browse/DDC-1958 + // https://github.com/doctrine/orm/issues/2630 $sql = $this->preserveSqlOrdering($sqlIdentifier, $innerSql, $sql, $orderByClause); // Apply the limit and offset. diff --git a/tests/Tests/Models/Enums/FaultySwitch.php b/tests/Tests/Models/Enums/FaultySwitch.php new file mode 100644 index 00000000000..7f01b239966 --- /dev/null +++ b/tests/Tests/Models/Enums/FaultySwitch.php @@ -0,0 +1,22 @@ +name = $name; + } +} diff --git a/tests/Tests/ORM/Internal/TopologicalSortTest.php b/tests/Tests/ORM/Internal/TopologicalSortTest.php index 12855a426c0..05a1be05534 100644 --- a/tests/Tests/ORM/Internal/TopologicalSortTest.php +++ b/tests/Tests/ORM/Internal/TopologicalSortTest.php @@ -285,14 +285,3 @@ private function computeResult(): array }, array_values($this->topologicalSort->sort())); } } - -class Node -{ - /** @var string */ - public $name; - - public function __construct(string $name) - { - $this->name = $name; - } -} diff --git a/tests/Tests/ORM/Mapping/TypedEnumFieldMapperTest.php b/tests/Tests/ORM/Mapping/TypedEnumFieldMapperTest.php new file mode 100644 index 00000000000..3954cff86a3 --- /dev/null +++ b/tests/Tests/ORM/Mapping/TypedEnumFieldMapperTest.php @@ -0,0 +1,31 @@ +expectException(MappingException::class); + $this->expectExceptionMessage( + 'Attempting to map a non-backed enum type Doctrine\Tests\Models\Enums\SwitchStatus in entity Doctrine\Tests\Models\Enums\FaultySwitch::$status. Please use backed enums only', + ); + + self::defaultTypedFieldMapper()->validateAndComplete(['fieldName' => 'status'], $reflectionClass->getProperty('status')); + } +}