Skip to content

LLM found issues #814

Description

@hrach

Critical / high

  1. AbstractEntity::__clone doesn't clone wrappers holding null — src/Entity/AbstractEntity.php:233-267 (reproduced by the audit agent) The clone loop guards on $this->hasValue($name), which is false for an initialized wrapper whose value is null (nullable m:1/1:1, nullable DateTimeWrapper, embeddable). The wrapper object is then shared between the original and the clone, and its parent still points at the original. Repro: clone a book whose translator is null, set $clone->translator = $author → the original book's translator changes and is marked modified. Same for a nullable datetime property.
  2. HasOne::getEntitiesForPersistence() drops the current entity — src/Relationships/HasOne.php:384 return $this->tracked + [$entity]; tracked is list-keyed ($this->tracked[] = $oldEntity; at line 167), so both arrays have key 0 and + keeps the left side — whenever the relationship changed at least once, the current entity is silently excluded from the cascade. For the non-main side of a 1:1, replacing a persisted entity with a new one means the new entity is never persisted (lost insert). Should be array_merge($this->tracked, [$entity]).
  3. MetadataParser getter/setter cache contaminates sibling entities — src/Entity/Reflection/MetadataParser.php:148-190, 335-345 (reproduced) classPropertiesCache keys parsed properties by defining class/trait, but bakes in the $methods of the first concrete entity parsed. Two entities sharing an abstract base: if EntityA has getterName() and EntityB doesn't, parse order determines whether B crashes calling a missing getter or A's getter is silently ignored.

Medium

  • setRawValue() on a wrapped property never marks it modified — src/Entity/AbstractEntity.php:116-137. The wrapper path returns without $this->modified[$name] = true, and DbalMapper persists modifiedOnly raw values, so the change is silently never written to the DB (the plain-value path does mark modified).
  • null inside an IN-list behaves differently in SQL vs PHP evaluation — src/Collection/Functions/CompareEqualsFunction.php:52-83, CompareNotEqualsFunction.php:43-74. findBy(['name' => ['a', null]]) emits IN ('a', NULL) (never matches NULL rows; NOT IN returns zero rows), while ArrayCollection uses strict in_array
    and does match — same filter, different results per backend.
  • HasManyCollection applies limit/offset twice — src/Collection/HasManyCollection.php:115-121 + ArrayCollection.php:285-287. With pending relationship changes and a non-zero offset, the SQL-offset rows get array_sliced again in memory, returning wrong/fewer entities.
  • CompareGreaterThan/SmallerThanFunction use === 1 / === -1 — src/Collection/Functions/CompareGreaterThanFunction.php:19, CompareSmallerThanFunction.php:19. The PropertyComparator contract is "negative/zero/positive" (the >=/<= siblings honor it); a strcmp-style custom comparator makes >/< filters silently classPropertiesCache keys parsed properties by defining class/trait, but bakes in the $methods of the first concrete entity parsed. Two entities sharing an abstract base: if EntityA has getterName() and EntityB doesn't, parse order determines whether crashes calling missing getter or A's getter is silently ignored.

Low (briefly)

  • sort($values) in RelationshipMapperOneHasMany.php:176 sorts a variable that's never used afterward — the deterministic-SQL intent (cf. commit 80f3d88) needed sort($ids).
  • PhpDocRepositoryFinder.php:78-82 validates the repository interface via assert() (no-op with zend.assertions=-1) and the sprintf args in its message are swapped; line 98's str_replace('Repository', 'Mapper', …) also rewrites namespace segments.
  • Join-alias generation in DbalQueryBuilderHelper.php:38-47 can collide for paths like a->b_c vs a->b->c, and mergeJoins() merges by alias only — wrong joins, no error.
  • PropertyMetadata::isValid() accepts any string as int/float ('abc' → 0) — silent coercion instead of a validation error (PropertyMetadata.php:117-139).
  • HasMany::__clone clears tracked but not toAdd/toRemove (HasMany.php:372-377); HasOne::doPersist() never resets isModified (HasOne.php:400-403); {enum BackedEnum::*} wildcard throws a raw ReflectionException (ModifierParser.php:194-196); Conventions caches mappings keyed by table name only (Conventions.php:82-93).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions