Skip to content

Commit fb59228

Browse files
authored
Merge pull request #12490 from greg0ire/3.7.x
Merge 3.6.x up into 3.7.x
2 parents 9eccd1f + 7086859 commit fb59228

10 files changed

Lines changed: 196 additions & 24 deletions

File tree

SECURITY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ understand the assumptions we make.
1212

1313
- [DBAL Security Page](https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/security.html)
1414
- [ORM Security Page](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/reference/security.html)
15+
- [security.rst page in this repository](docs/en/reference/security.rst)
1516

1617
If you find a Security bug in Doctrine, please follow our
1718
[Security reporting guidelines](https://www.doctrine-project.org/policies/security.html#reporting).
19+
20+
Security vulnerabilities should be reported to security@doctrine-project.org
21+
and not posted on the GitHub issue tracker of the project.

docs/en/reference/events.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,15 @@ instance and class metadata.
999999
}
10001000
}
10011001
1002+
.. note::
1003+
1004+
Starting in ORM 3.6, the ``GenerateSchemaTableEventArgs`` class
1005+
provides ``setSchema()`` and ``setClassTable()`` methods to replace
1006+
the schema or table objects. These methods require ``doctrine/dbal``
1007+
^4.5 or higher, which provides the Schema Editor API. If called with
1008+
an earlier DBAL version, a ``BadMethodCallException`` will be
1009+
thrown.
1010+
10021011
postGenerateSchema
10031012
~~~~~~~~~~~~~~~~~~
10041013

@@ -1025,6 +1034,14 @@ and the EntityManager.
10251034
}
10261035
}
10271036
1037+
.. note::
1038+
1039+
Starting in ORM 3.6, the ``GenerateSchemaEventArgs`` class provides
1040+
a ``setSchema()`` method to replace the schema object. This method
1041+
requires ``doctrine/dbal`` ^4.5 or higher, which provides the Schema
1042+
Editor API. If called with an earlier DBAL version, a
1043+
``BadMethodCallException`` will be thrown.
1044+
10281045
.. _PrePersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PrePersistEventArgs.php
10291046
.. _PreRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PreRemoveEventArgs.php
10301047
.. _PreUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PreUpdateEventArgs.php

phpstan-baseline.neon

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2862,12 +2862,6 @@ parameters:
28622862
count: 1
28632863
path: src/Tools/Console/Command/MappingDescribeCommand.php
28642864

2865-
-
2866-
message: '#^Parameter \#1 \$entityListeners of method Doctrine\\ORM\\Tools\\Console\\Command\\MappingDescribeCommand\:\:formatEntityListeners\(\) expects list\<object\>, array\<string, list\<array\{class\: class\-string, method\: string\}\>\> given\.$#'
2867-
identifier: argument.type
2868-
count: 1
2869-
path: src/Tools/Console/Command/MappingDescribeCommand.php
2870-
28712865
-
28722866
message: '#^Parameter \#2 \$callback of function array_filter expects \(callable\(class\-string\)\: bool\)\|null, Closure\(mixed\)\: \(0\|1\|false\) given\.$#'
28732867
identifier: argument.type
@@ -3201,18 +3195,6 @@ parameters:
32013195
count: 1
32023196
path: src/Tools/SchemaTool.php
32033197

3204-
-
3205-
message: '#^Parameter \#1 \$columnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addIndex\(\) expects non\-empty\-list\<string\>, list\<string\> given\.$#'
3206-
identifier: argument.type
3207-
count: 1
3208-
path: src/Tools/SchemaTool.php
3209-
3210-
-
3211-
message: '#^Parameter \#1 \$columnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addUniqueIndex\(\) expects non\-empty\-list\<string\>, array\<string\> given\.$#'
3212-
identifier: argument.type
3213-
count: 1
3214-
path: src/Tools/SchemaTool.php
3215-
32163198
-
32173199
message: '#^Parameter \#2 \$columns of class Doctrine\\DBAL\\Schema\\Index constructor expects non\-empty\-list\<string\>, list\<string\> given\.$#'
32183200
identifier: argument.type

phpstan-dbal3.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ parameters:
4848
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:getObjectName\(\)\.$~'
4949
path: src/Mapping/Driver/DatabaseDriver.php
5050

51+
-
52+
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:getObjectName\(\)\.$~'
53+
path: src/Tools/SchemaTool.php
54+
5155
-
5256
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint::get.*\.$~'
5357
identifier: method.notFound

src/Tools/Console/Command/MappingDescribeCommand.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,13 +376,21 @@ private function formatMappingsAsJson(array $propertyMappings): array
376376
/**
377377
* Format the entity listeners
378378
*
379-
* @phpstan-param list<object> $entityListeners
379+
* @phpstan-param array<string, list<array{class: class-string, method: string}>> $entityListeners
380380
*
381381
* @return string[]
382382
* @phpstan-return array{0: string, 1: string}
383383
*/
384384
private function formatEntityListeners(array $entityListeners): array
385385
{
386-
return $this->formatField('Entity listeners', array_map('get_class', $entityListeners));
386+
$listeners = [];
387+
388+
foreach ($entityListeners as $eventName => $eventListeners) {
389+
foreach ($eventListeners as $listener) {
390+
$listeners[] = sprintf('%s: %s::%s', $eventName, $listener['class'], $listener['method']);
391+
}
392+
}
393+
394+
return $this->formatField('Entity listeners', $listeners);
387395
}
388396
}

src/Tools/Event/GenerateSchemaEventArgs.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
namespace Doctrine\ORM\Tools\Event;
66

7+
use BadMethodCallException;
78
use Doctrine\Common\EventArgs;
89
use Doctrine\DBAL\Schema\Schema;
910
use Doctrine\ORM\EntityManagerInterface;
1011

12+
use function method_exists;
13+
1114
/**
1215
* Event Args used for the Events::postGenerateSchema event.
1316
*
@@ -17,7 +20,7 @@ class GenerateSchemaEventArgs extends EventArgs
1720
{
1821
public function __construct(
1922
private readonly EntityManagerInterface $em,
20-
private readonly Schema $schema,
23+
private Schema $schema,
2124
) {
2225
}
2326

@@ -30,4 +33,17 @@ public function getSchema(): Schema
3033
{
3134
return $this->schema;
3235
}
36+
37+
public function setSchema(Schema $schema): void
38+
{
39+
// @phpstan-ignore function.impossibleType (Checking for unreleased Schema::edit() API)
40+
if (! method_exists(Schema::class, 'edit')) {
41+
throw new BadMethodCallException(
42+
'The setSchema() method requires the DBAL Schema::edit() API which is not available in the current DBAL version. '
43+
. 'This feature requires doctrine/dbal ^4.5 or higher.',
44+
);
45+
}
46+
47+
$this->schema = $schema;
48+
}
3349
}

src/Tools/Event/GenerateSchemaTableEventArgs.php

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,27 @@
44

55
namespace Doctrine\ORM\Tools\Event;
66

7+
use BadMethodCallException;
78
use Doctrine\Common\EventArgs;
89
use Doctrine\DBAL\Schema\Schema;
910
use Doctrine\DBAL\Schema\Table;
1011
use Doctrine\ORM\Mapping\ClassMetadata;
1112

13+
use function method_exists;
14+
1215
/**
1316
* Event Args used for the Events::postGenerateSchemaTable event.
1417
*
1518
* @link www.doctrine-project.com
1619
*/
1720
class GenerateSchemaTableEventArgs extends EventArgs
1821
{
22+
private bool $classTableWasMutated = false;
23+
1924
public function __construct(
2025
private readonly ClassMetadata $classMetadata,
21-
private readonly Schema $schema,
22-
private readonly Table $classTable,
26+
private Schema $schema,
27+
private Table $classTable,
2328
) {
2429
}
2530

@@ -37,4 +42,36 @@ public function getClassTable(): Table
3742
{
3843
return $this->classTable;
3944
}
45+
46+
public function setSchema(Schema $schema): void
47+
{
48+
// @phpstan-ignore function.impossibleType (Checking for unreleased Schema::edit() API)
49+
if (! method_exists(Schema::class, 'edit')) {
50+
throw new BadMethodCallException(
51+
'The setSchema() method requires the DBAL Schema::edit() API which is not available in the current DBAL version. '
52+
. 'This feature requires doctrine/dbal ^4.5 or higher.',
53+
);
54+
}
55+
56+
$this->schema = $schema;
57+
}
58+
59+
public function setClassTable(Table $classTable): void
60+
{
61+
// @phpstan-ignore function.impossibleType (Checking for unreleased Schema::edit() API)
62+
if (! method_exists(Schema::class, 'edit')) {
63+
throw new BadMethodCallException(
64+
'The setClassTable() method requires the DBAL Schema::edit() API which is not available in the current DBAL version. '
65+
. 'This feature requires doctrine/dbal ^4.5 or higher.',
66+
);
67+
}
68+
69+
$this->classTable = $classTable;
70+
$this->classTableWasMutated = true;
71+
}
72+
73+
public function classTableWasMutated(): bool
74+
{
75+
return $this->classTableWasMutated;
76+
}
4077
}

src/Tools/SchemaTool.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,12 +389,39 @@ public function getSchemaFromMetadata(array $classes): Schema
389389
}
390390
}
391391

392+
$tableEventArgs = new GenerateSchemaTableEventArgs($class, $schema, $table);
392393
$eventManager->dispatchEvent(
393394
ToolEvents::postGenerateSchemaTable,
394-
new GenerateSchemaTableEventArgs($class, $schema, $table),
395+
$tableEventArgs,
395396
);
397+
398+
// Always retrieve the schema (listener may have mutated it)
399+
$schema = $tableEventArgs->getSchema();
400+
401+
// If the listener mutated the table, we need to replace it in the schema
402+
if ($tableEventArgs->classTableWasMutated()) {
403+
$originalTableName = $table->getObjectName();
404+
$newTable = $tableEventArgs->getClassTable();
405+
406+
// @phpstan-ignore method.notFound (Using unreleased Schema::edit() API)
407+
$schemaEditor = $schema->edit();
408+
$schemaEditor->dropTable($originalTableName);
409+
$schemaEditor->addTable($newTable);
410+
411+
$schema = $schemaEditor->create();
412+
$table = $newTable;
413+
}
396414
}
397415

416+
$schemaEventArgs = new GenerateSchemaEventArgs($this->em, $schema);
417+
$eventManager->dispatchEvent(
418+
ToolEvents::postGenerateSchema,
419+
$schemaEventArgs,
420+
);
421+
422+
// Always retrieve the schema (listener may have mutated it)
423+
$schema = $schemaEventArgs->getSchema();
424+
398425
$eventManager->dispatchEvent(
399426
ToolEvents::postGenerateSchema,
400427
new GenerateSchemaEventArgs($this->em, $schema),

tests/Tests/ORM/Tools/Console/Command/MappingDescribeCommandTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Doctrine\ORM\Tools\Console\Command\MappingDescribeCommand;
99
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
1010
use Doctrine\Tests\Models\Cache\AttractionInfo;
11+
use Doctrine\Tests\Models\CMS\CmsAddress;
1112
use Doctrine\Tests\OrmFunctionalTestCase;
1213
use InvalidArgumentException;
1314
use PHPUnit\Framework\Attributes\CoversClass;
@@ -138,4 +139,22 @@ public static function provideCompletionSuggestions(): iterable
138139
['text', 'json'],
139140
];
140141
}
142+
143+
public function testShowEntityWithEntityListeners(): void
144+
{
145+
$this->tester->execute(
146+
[
147+
'command' => $this->command->getName(),
148+
'entityName' => CmsAddress::class,
149+
],
150+
);
151+
152+
$display = $this->tester->getDisplay();
153+
154+
self::assertStringContainsString(CmsAddress::class, $display);
155+
self::assertStringContainsString('Entity listeners', $display);
156+
self::assertStringContainsString('CmsAddressListener', $display);
157+
self::assertStringContainsString('postPersist', $display);
158+
self::assertStringContainsString('prePersist', $display);
159+
}
141160
}

tests/Tests/ORM/Tools/SchemaToolTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
1313
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
1414
use Doctrine\DBAL\Schema\PrimaryKeyConstraintEditor;
15+
use Doctrine\DBAL\Schema\Schema;
1516
use Doctrine\DBAL\Schema\Table as DbalTable;
1617
use Doctrine\DBAL\Types\EnumType;
1718
use Doctrine\DBAL\Types\Types;
@@ -53,6 +54,7 @@
5354
use Doctrine\Tests\Models\NullDefault\NullDefaultColumn;
5455
use Doctrine\Tests\OrmTestCase;
5556
use PHPUnit\Framework\Attributes\Group;
57+
use PHPUnit\Framework\Attributes\RequiresMethod;
5658

5759
use function array_map;
5860
use function class_exists;
@@ -527,6 +529,62 @@ public function testCompositeIdPositionRespectedInSchema(): void
527529
self::assertSame(['first', 'second', 'third'], $pkColumns);
528530
}
529531

532+
#[RequiresMethod(Schema::class, 'edit')]
533+
public function testOverwritingSchemaInListener(): void
534+
{
535+
$em = $this->getTestEntityManager();
536+
537+
$em->getEventManager()->addEventListener(
538+
[ToolEvents::postGenerateSchemaTable, ToolEvents::postGenerateSchema],
539+
new class () {
540+
public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs): void
541+
{
542+
$eventArgs->setSchema(new Schema());
543+
}
544+
545+
public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs): void
546+
{
547+
$eventArgs->setSchema(new Schema());
548+
}
549+
},
550+
);
551+
$schemaTool = new SchemaTool($em);
552+
553+
$schema = $schemaTool->getSchemaFromMetadata([$em->getClassMetadata(CmsAddress::class)]);
554+
555+
self::assertFalse(
556+
$schema->hasTable('cms_address'),
557+
'The original schema should have been overwritten by the listener, so cms_address table should not exist.',
558+
);
559+
}
560+
561+
#[RequiresMethod(Schema::class, 'edit')]
562+
public function testOverwritingTableInListener(): void
563+
{
564+
$em = $this->getTestEntityManager();
565+
566+
$em->getEventManager()->addEventListener(
567+
[ToolEvents::postGenerateSchemaTable],
568+
new class () {
569+
public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs): void
570+
{
571+
$eventArgs->setClassTable(new DbalTable('just_address'));
572+
}
573+
},
574+
);
575+
$schemaTool = new SchemaTool($em);
576+
577+
$schema = $schemaTool->getSchemaFromMetadata([$em->getClassMetadata(CmsAddress::class)]);
578+
self::assertTrue(
579+
$schema->hasTable('just_address'),
580+
'The original table should have been overwritten by the listener, so just_address table should exist.',
581+
);
582+
self::assertFalse(
583+
$schema->hasTable('cms_address'),
584+
'The original table should have been overwritten by the listener, so cms_address table should not exist.',
585+
);
586+
}
587+
530588
/** @return string[] */
531589
private static function getIndexedColumns(DbalIndex $index): array
532590
{

0 commit comments

Comments
 (0)