Skip to content

Commit c82f748

Browse files
authored
[BUGFIX] #673: generate ext_tables.sql CREATE TABLE for models without own properties (#812)
* [TASK] Fix #673: generate ext_tables.sql CREATE TABLE for models without own properties When a domain object has no own properties but is the target of a ZeroToMany inline relation, the FK column for that relation must still appear in ext_tables.sql. The template condition previously skipped the entire CREATE TABLE block, silently dropping the FK column and leaving the database table uncreated. - Extend the template condition in extTables.sqlt to also check for incoming FK relations via listForeignKeyRelations - Add validation warning in ExtensionValidator when a domain object has no properties (warns user that no CREATE TABLE will be generated) - Add functional test verifying the FK column is included in ext_tables.sql for a child model with no own properties - Add unit test for the new validation warning - Update phpstan-baseline.neon for 2 new LF usages - Update ChangeLog * [BUGFIX] Suppress no-properties warning when domain object is inline FK target The validation warning added in #812 fired for all domain objects with no own properties, including those that are the target of a ZeroToMany inline relation. For these objects the template correctly generates a CREATE TABLE (via the FK column condition added in the same PR), so the warning message was factually incorrect. Fix: only emit the warning when the domain object has neither own properties nor any incoming inline FK relation. Also adds a unit test covering the suppressed-warning case.
1 parent 941ab71 commit c82f748

6 files changed

Lines changed: 130 additions & 2 deletions

File tree

Classes/Domain/Validator/ExtensionValidator.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ class ExtensionValidator extends AbstractValidator
7777
* @var int
7878
*/
7979
public const ERROR_DOMAINOBJECT_DUPLICATE = 103;
80+
/**
81+
* @var int
82+
*/
83+
public const ERROR_DOMAINOBJECT_NO_PROPERTIES = 104;
8084
/**
8185
* @var int
8286
*/
@@ -546,6 +550,15 @@ private function validateDomainObjects(Extension $extension): void
546550
);
547551
}
548552

553+
if (!count($domainObject->getProperties()) && !$this->hasIncomingFkRelation($domainObject, $extension)) {
554+
$this->validationResult['warnings'][] = new ExtensionException(
555+
'Domain object "' . $domainObject->getName() . '" has no properties.' . LF
556+
. 'Without properties, no CREATE TABLE statement will be generated in ext_tables.sql.' . LF
557+
. 'Add at least one property to ensure the database table is created correctly.',
558+
self::ERROR_DOMAINOBJECT_NO_PROPERTIES
559+
);
560+
}
561+
549562
$this->validateProperties($domainObject);
550563
$this->validateDomainObjectActions($domainObject);
551564
$this->validateMapping($domainObject);
@@ -566,6 +579,26 @@ private function validateDomainObjects(Extension $extension): void
566579
}
567580
}
568581

582+
/**
583+
* Returns true if any other domain object in the extension has an inline ZeroToMany relation
584+
* pointing to $domainObject, meaning a FK column will be generated in ext_tables.sql even
585+
* when $domainObject has no own properties.
586+
*/
587+
private function hasIncomingFkRelation(DomainObject $domainObject, Extension $extension): bool
588+
{
589+
foreach ($extension->getDomainObjects() as $otherObject) {
590+
foreach ($otherObject->getProperties() as $property) {
591+
if ($property instanceof ZeroToManyRelation
592+
&& $property->getRenderType() === 'inline'
593+
&& $property->getForeignClassName() === $domainObject->getFullQualifiedClassName()
594+
) {
595+
return true;
596+
}
597+
}
598+
}
599+
return false;
600+
}
601+
569602
/**
570603
* cover all cases:
571604
* 1. extend TYPO3 class like fe_users (no mapping table needed)

Documentation/ChangeLog/Index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Change log
99
Version 13.1.0
1010
--------------
1111

12+
* [BUGFIX] ``ext_tables.sql`` now includes a ``CREATE TABLE`` statement for models that have no own properties but are the target of a ``ZeroToMany inline`` relation — previously the FK column was silently dropped, leaving the database table uncreated.
13+
* [BUGFIX] A validation warning is now shown when a domain object has no properties, informing the user that no ``CREATE TABLE`` statement will be generated in ``ext_tables.sql``.
1214
* [FEATURE] XLF files are no longer rewritten when only the ``date=`` attribute changed — avoids VCS noise on every regeneration. The ``staticDateInXliffFiles`` setting is removed as it is no longer needed.
1315

1416
Version 12.0.0

Resources/Private/CodeTemplates/Extbase/extTables.sqlt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{namespace k=EBT\ExtensionBuilder\ViewHelpers}{escaping off}<k:format.trim>
22
<f:for each="{extension.domainObjects}" as="domainObject">
3-
<f:if condition="{domainObject.properties -> f:count()} > 0 || {k:domainObjectChecks(renderCondition: 'needsTypeField', domainObject: domainObject)}">
3+
<f:if condition="{domainObject.properties -> f:count()} > 0 || {k:domainObjectChecks(renderCondition: 'needsTypeField', domainObject: domainObject)} || {k:listForeignKeyRelations(domainObject:domainObject) -> f:count()} > 0">
44
CREATE TABLE {domainObject.databaseTableName} (<f:for each="{k:listForeignKeyRelations(domainObject:domainObject)}" as="relation">
55
{relation.foreignKeyName} int unsigned DEFAULT '0' NOT NULL,</f:for><f:for each="{domainObject.properties}" as="property"><f:if condition="{property.isPersistable}">
66
{property.sqlDefinition}</f:if></f:for><k:mapping renderCondition="needsTypeField" domainObject="{domainObject}">

Tests/Functional/Service/FileGeneratorTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,52 @@ public function writeAggregateRootClassesFromDomainObject(): void
481481
self::assertFileExists($extensionDir . 'Classes/Domain/Repository/' . $domainObject->getName() . 'Repository.php');
482482
}
483483

484+
/**
485+
* Verify that ext_tables.sql contains a CREATE TABLE for a child model that has no own properties
486+
* but is the target of a ZeroToMany inline relation (requires FK column in its table).
487+
*
488+
* @test
489+
*/
490+
public function extTablesSqlContainsForeignKeyForChildModelWithNoProperties(): void
491+
{
492+
$ownerModelName = 'OwnerModel';
493+
$childModelName = 'ChildModel';
494+
$relationName = 'children';
495+
496+
$ownerDomainObject = $this->buildDomainObject($ownerModelName, true, true);
497+
$childDomainObject = $this->buildDomainObject($childModelName, true);
498+
499+
$property = new BooleanProperty('active');
500+
$ownerDomainObject->addProperty($property);
501+
502+
$relation = new Relation\ZeroToManyRelation($relationName);
503+
$relation->setForeignModel($childDomainObject);
504+
$relation->setRenderType('inline');
505+
$ownerDomainObject->addProperty($relation);
506+
507+
$this->extension->addDomainObject($ownerDomainObject);
508+
$this->extension->addDomainObject($childDomainObject);
509+
510+
$this->fileGenerator->build($this->extension);
511+
512+
$sqlFile = $this->extension->getExtensionDir() . 'ext_tables.sql';
513+
self::assertFileExists($sqlFile, 'ext_tables.sql was not generated');
514+
515+
$sqlContent = file_get_contents($sqlFile);
516+
self::assertStringContainsString(
517+
'CREATE TABLE ' . $childDomainObject->getDatabaseTableName(),
518+
$sqlContent,
519+
'ext_tables.sql must contain CREATE TABLE for child model with no own properties'
520+
);
521+
// FK column name is derived from the owner model name (lowercase)
522+
$expectedFkColumn = strtolower($ownerModelName);
523+
self::assertStringContainsString(
524+
$expectedFkColumn,
525+
$sqlContent,
526+
'ext_tables.sql must contain the FK column for the child model'
527+
);
528+
}
529+
484530
/**
485531
* @test
486532
*/

Tests/Unit/Domain/Validator/ValidationServiceTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
namespace EBT\ExtensionBuilder\Tests\Unit\Domain\Validator;
1919

2020
use EBT\ExtensionBuilder\Domain\Exception\ExtensionException;
21+
use EBT\ExtensionBuilder\Domain\Model\DomainObject\Relation\ZeroToManyRelation;
2122
use EBT\ExtensionBuilder\Domain\Validator\ExtensionValidator;
2223
use EBT\ExtensionBuilder\Tests\BaseUnitTest;
2324

@@ -31,6 +32,52 @@ public function testForReservedWord(): void
3132
self::assertTrue(ExtensionValidator::isReservedWord('DATABASE'));
3233
}
3334

35+
/**
36+
* @test
37+
*/
38+
public function validateExtensionWarnsForDomainObjectWithNoProperties(): void
39+
{
40+
$domainObject = $this->buildDomainObject('EmptyModel');
41+
// no properties added
42+
$this->extension->addDomainObject($domainObject);
43+
44+
$extensionValidator = new ExtensionValidator();
45+
$result = $extensionValidator->validateExtension($this->extension);
46+
47+
$warningCodes = array_map(fn($w) => $w->getCode(), $result['warnings']);
48+
self::assertContains(
49+
ExtensionValidator::ERROR_DOMAINOBJECT_NO_PROPERTIES,
50+
$warningCodes,
51+
'Expected warning for domain object without properties'
52+
);
53+
}
54+
55+
/**
56+
* @test
57+
*/
58+
public function noWarningForDomainObjectWithNoPropertiesWhenItIsInlineFkTarget(): void
59+
{
60+
$ownerObject = $this->buildDomainObject('OwnerModel', true, true);
61+
$childObject = $this->buildDomainObject('ChildModel', true);
62+
// child has no own properties, but is target of an inline ZeroToMany relation
63+
$relation = new ZeroToManyRelation('children');
64+
$relation->setForeignModel($childObject);
65+
$relation->setRenderType('inline');
66+
$ownerObject->addProperty($relation);
67+
$this->extension->addDomainObject($ownerObject);
68+
$this->extension->addDomainObject($childObject);
69+
70+
$extensionValidator = new ExtensionValidator();
71+
$result = $extensionValidator->validateExtension($this->extension);
72+
73+
$warningCodes = array_map(fn($w) => $w->getCode(), $result['warnings']);
74+
self::assertNotContains(
75+
ExtensionValidator::ERROR_DOMAINOBJECT_NO_PROPERTIES,
76+
$warningCodes,
77+
'No warning expected for child model that is target of an inline FK relation'
78+
);
79+
}
80+
3481
/**
3582
* @test
3683
*/

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ parameters:
141141
-
142142
message: '#^Constant LF not found\.$#'
143143
identifier: constant.notFound
144-
count: 26
144+
count: 28
145145
path: Classes/Domain/Validator/ExtensionValidator.php
146146

147147
-

0 commit comments

Comments
 (0)