Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 3 additions & 15 deletions src/EventListener/CreateSchemaListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,7 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs)
$revisionsTable = $this->createRevisionsTable($schema);

$entityTable = $eventArgs->getClassTable();
if ($this->isDbal4_3()) {
$tableName = $this->config->getTablePrefix().$entityTable->getObjectName()->toString().$this->config->getTableSuffix();
} else {
$tableName = $this->config->getTablePrefix().$entityTable->getName().$this->config->getTableSuffix(); // @phpstan-ignore-line
}
$tableName = $this->config->getTablePrefix().$this->getUnquotedTableName($entityTable).$this->config->getTableSuffix();
$revisionTable = $schema->createTable($tableName);

foreach ($entityTable->getColumns() as $column) {
Expand Down Expand Up @@ -263,22 +259,14 @@ private function createRevisionsTable(Schema $schema): Table
private function createRevisionJoinTableForJoinTable(Schema $schema, string $joinTableName): void
{
$joinTable = $schema->getTable($joinTableName);
if ($this->isDbal4_3()) {
$revisionJoinTableName = $this->config->getTablePrefix().$joinTable->getObjectName()->toString().$this->config->getTableSuffix();
} else {
$revisionJoinTableName = $this->config->getTablePrefix().$joinTable->getName().$this->config->getTableSuffix(); // @phpstan-ignore-line
}
$revisionJoinTableName = $this->config->getTablePrefix().$this->getUnquotedTableName($joinTable).$this->config->getTableSuffix();

if ($schema->hasTable($revisionJoinTableName)) {
return;
}

$typeRegistry = Type::getTypeRegistry();
if ($this->isDbal4_3()) {
$tableName = $this->config->getTablePrefix().$joinTable->getObjectName()->toString().$this->config->getTableSuffix();
} else {
$tableName = $this->config->getTablePrefix().$joinTable->getName().$this->config->getTableSuffix(); // @phpstan-ignore-line
}
$tableName = $revisionJoinTableName;
$revisionJoinTable = $schema->createTable($tableName);
/** @var Column $column */
foreach ($joinTable->getColumns() as $column) {
Expand Down
23 changes: 23 additions & 0 deletions src/Utils/DbalCompatibilityTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\Table;

/**
* NEXT_MAJOR: remove this trait and all `if` blocks.
Expand All @@ -28,6 +29,28 @@ protected function isDbal4_3(): bool
return class_exists(UnqualifiedName::class); // @phpstan-ignore-line class added in doctrine/dbal 4.3
}

/**
* Returns the unquoted name of a table, usable as a base for deriving a related
* table name through concatenation.
*
* On DBAL >= 4.3, Table::getName() is deprecated and Table::getObjectName()->toString()
* renders a *quoted* identifier for reserved keywords (e.g. `"user"`). Concatenating
* that with a prefix/suffix yields an unparseable name (`"user"_audit`), which DBAL
* leaves uninitialized and then rejects from getObjectName(). Using the raw identifier
* value preserves the historical (pre-4.3) behaviour.
*/
protected function getUnquotedTableName(Table $table): string
{
if ($this->isDbal4_3()) {
$name = $table->getObjectName();
$qualifier = $name->getQualifier();

return (null !== $qualifier ? $qualifier->getValue().'.' : '').$name->getUnqualifiedName()->getValue();
}

return $table->getName(); // @phpstan-ignore-line
}

protected function isDbal4(): bool
{
return is_subclass_of(ParameterType::class, \UnitEnum::class); // @phpstan-ignore-line class changed to enum in doctrine/dbal 4
Expand Down
53 changes: 53 additions & 0 deletions tests/Fixtures/Issue/ReservedTableNameEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Sonata\EntityAuditBundle\Tests\Fixtures\Issue;

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

/**
* The table name is an SQL reserved keyword, so it is force-quoted by Doctrine.
*
* @psalm-suppress ClassMustBeFinal
*/
#[ORM\Entity]
#[ORM\Table(name: '`user`')]
class ReservedTableNameEntity
{
/**
* @var int|null
*/
#[ORM\Id]
#[ORM\Column(type: Types::INTEGER)]
#[ORM\GeneratedValue]
protected $id;

#[ORM\Column(type: Types::STRING, length: 255)]
private ?string $name = null;

public function getId(): ?int
{
return $this->id;
}

public function getName(): ?string
{
return $this->name;
}

public function setName(?string $name = null): void
{
$this->name = $name;
}
}
52 changes: 52 additions & 0 deletions tests/Issue/IssueReservedTableNameTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Sonata\EntityAuditBundle\Tests\Issue;

use Sonata\EntityAuditBundle\Tests\BaseTestCase;
use Sonata\EntityAuditBundle\Tests\Fixtures\Issue\ReservedTableNameEntity;

final class IssueReservedTableNameTest extends BaseTestCase
{
protected $schemaEntities = [
ReservedTableNameEntity::class,
];

protected $auditedEntities = [
ReservedTableNameEntity::class,
];

/**
* On DBAL >= 4.3 the audit table name was derived from the *quoted* identifier
* (e.g. `"user"` + `_audit` => `"user"_audit`), which is not a parseable name, so
* schema generation crashed with "Object name has not been initialized".
*/
public function testAuditSchemaForReservedKeywordTableName(): void
{
$entity = new ReservedTableNameEntity();
$entity->setName('foo');

$em = $this->getEntityManager();

$em->persist($entity);
$em->flush();

$reader = $this->getAuditManager()->createAuditReader($em);

$id = $entity->getId();
static::assertNotNull($id);

$audited = $reader->find(ReservedTableNameEntity::class, $id, 1);
static::assertSame('foo', $audited->getName());
}
}