2020use TYPO3 \CMS \Extbase \Error \Result ;
2121use TYPO3 \CMS \Extbase \Persistence \Generic \Mapper \DataMapFactory ;
2222use TYPO3 \CMS \Extbase \Validation \Validator \AbstractValidator ;
23- use TYPO3 \CMS \Extbase \Validation \Validator \ObjectValidatorInterface ;
2423
2524/**
26- * Validates that all string properties of a domain model ( and its nested relations) do not exceed
27- * the maximum field length defined in the database schema.
25+ * Validates that all string properties of a domain model and its nested relations
26+ * do not exceed the maximum field length defined in the database schema.
2827 *
29- * Recursively traverses 1:1 relations (AbstractDomainObject properties) and 1:n / m:n relations
30- * (Traversable properties, e.g. ObjectStorage) and validates each related model as well.
31- * Circular references are detected via a shared SplObjectStorage and skipped to prevent infinite loops.
32- *
33- * Implements ObjectValidatorInterface so that TYPO3's validation chain can inject the shared
34- * validatedInstancesContainer when this validator is used with other object validators.
28+ * Recursively traverses related domain objects and validates each entity only once.
29+ * Processed entities are tracked using a stable identifier based on class name and uid
30+ * to prevent endless recursion caused by cyclic relations and lazy-loading proxies.
3531 */
36- final class TcaSchemaFieldLengthValidator extends AbstractValidator implements ObjectValidatorInterface
32+ final class TcaSchemaFieldLengthValidator extends AbstractValidator
3733{
3834 private const SUPPORTED_TYPES = [
3935 TableColumnType::INPUT ->value ,
@@ -45,7 +41,7 @@ final class TcaSchemaFieldLengthValidator extends AbstractValidator implements O
4541 TableColumnType::TEXT ->value ,
4642 ];
4743
48- private ? \ SplObjectStorage $ validatedInstancesContainer = null ;
44+ private array $ processedEntityIdentifiers = [] ;
4945 private array $ columnCache = [];
5046
5147 public function __construct (
@@ -55,32 +51,26 @@ public function __construct(
5551 ) {
5652 }
5753
58- public function setValidatedInstancesContainer (\SplObjectStorage $ validatedInstancesContainer ): void
59- {
60- $ this ->validatedInstancesContainer = $ validatedInstancesContainer ;
61- }
62-
6354 public function isValid (mixed $ value ): void
6455 {
56+ $ this ->processedEntityIdentifiers = [];
57+
6558 if (!($ value instanceof AbstractDomainObject)) {
6659 return ;
6760 }
6861
69- if ($ this ->validatedInstancesContainer === null ) {
70- $ this ->validatedInstancesContainer = new \SplObjectStorage ();
71- }
72-
7362 $ this ->result ->merge ($ this ->validateDomainObject ($ value ));
7463 }
7564
7665 private function validateDomainObject (AbstractDomainObject $ value ): Result
7766 {
7867 $ result = new Result ();
7968
80- if ($ this ->validatedInstancesContainer ->contains ($ value )) {
69+ $ identifier = $ this ->getValidationIdentifier ($ value );
70+ if (isset ($ this ->processedEntityIdentifiers [$ identifier ])) {
8171 return $ result ;
8272 }
83- $ this ->validatedInstancesContainer -> attach ( $ value ) ;
73+ $ this ->processedEntityIdentifiers [ $ identifier ] = true ;
8474
8575 $ dataMap = $ this ->dataMapFactory ->buildDataMap (get_class ($ value ));
8676 $ tableName = $ dataMap ->getTableName ();
@@ -139,4 +129,16 @@ private function validateDomainObject(AbstractDomainObject $value): Result
139129
140130 return $ result ;
141131 }
132+
133+ private function getValidationIdentifier (AbstractDomainObject $ object ): string
134+ {
135+ $ className = get_parent_class ($ object );
136+ $ uid = $ object ->getUid ();
137+
138+ if ($ uid > 0 ) {
139+ return sprintf ('%s:%s ' , $ className , $ uid );
140+ }
141+
142+ return sprintf ('%s:%s ' , $ className , spl_object_id ($ object ));
143+ }
142144}
0 commit comments