Skip to content

Commit

Permalink
fix OneHasOne loading from non-main side [closes #714]
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Jan 4, 2025
1 parent c7ee9ab commit c1e8df0
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 38 deletions.
47 changes: 30 additions & 17 deletions src/Relationships/HasOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,23 @@ abstract class HasOne implements IRelationshipContainer
/** @var ICollection<E>|null */
protected ?ICollection $collection = null;

/** Is value validated against storage? */
/**
* Denotes if the value is validated, i.e. if the value is pk of valid entity or if the value is not null when it is
* disallowed.
*
* By default, relationship is validated because no initial value has been set yet.
* The first setRawValue will change that to false (with exception on null, which won't be validated later).
*/
protected bool $isValueValidated = true;

/** Is raw value loaded from storage and not converted yet? */
protected bool $isValueFromStorage = false;
/**
* Denotes if the value is present. Value is not present when this relationship side
* is not the main one and the reverse side was not yet asked to get the initial value.
* After setting this value in runtime, the value is always present.
*
* If value is not present and is worked with, it is fetched via {@see fetchValue()}.
*/
protected bool $isValuePresent = true;

/** @var E|string|int|null */
protected mixed $value = null;
Expand Down Expand Up @@ -88,10 +100,9 @@ public function convertToRawValue($value)

public function setRawValue($value): void
{
$isChanged = $this->getPrimaryValue() !== $value;
$this->value = $value;
$this->isValueValidated = !$isChanged && $value === null;
$this->isValueFromStorage = true;
$this->isValueValidated = $value === null;
$this->isValuePresent = true;
}


Expand Down Expand Up @@ -122,7 +133,7 @@ public function hasInjectedValue(): bool

public function isLoaded(): bool
{
return !$this->isValueFromStorage || $this->isValueValidated;
return $this->value instanceof IEntity;
}


Expand All @@ -141,12 +152,10 @@ public function set($value, bool $allowNull = false): bool
return false;
}

if (($this->parent !== null && $this->parent->isAttached()) || $value === null) {
$entity = $this->createEntity($value, $allowNull);
$isValueValidated = true;
if ($this->parent?->isAttached() === true || $value === null) {
$entity = $this->createEntity($value, allowNull: $allowNull);
} else {
$entity = $value;
$isValueValidated = false;
}

if ($entity instanceof IEntity || $entity === null) {
Expand All @@ -167,8 +176,8 @@ public function set($value, bool $allowNull = false): bool
}

$this->value = $entity;
$this->isValueValidated = $isValueValidated;
$this->isValueFromStorage = false;
$this->isValueValidated = $entity === null || $entity instanceof IEntity;
$this->isValuePresent = true;
return $isChanged;
}

Expand Down Expand Up @@ -214,7 +223,7 @@ protected function getPrimaryValue(): mixed
*/
protected function getValue(bool $allowPreloadContainer = true): ?IEntity
{
if (!$this->isValueValidated && ($this->value !== null || $this->metadata->isNullable)) {
if ((!$this->isValueValidated && ($this->value !== null || $this->metadata->isNullable)) || !$this->isValuePresent) {
$this->initValue($allowPreloadContainer);
}

Expand All @@ -229,9 +238,9 @@ protected function initValue(bool $allowPreloadContainer = true): void
throw new InvalidStateException('Relationship is not attached to a parent entity.');
}

if ($this->isValueFromStorage && $allowPreloadContainer) {
// load the value using relationship mapper to utilize preload container and not to validate if
// relationship's entity is really present in the database;
if (!$this->isValuePresent || $allowPreloadContainer) {
// load the value using relationship mapper to utilize preload container to avoid validation if the
// relationship's entity is actually present in the database;
$this->set($this->fetchValue());

} else {
Expand Down Expand Up @@ -339,6 +348,10 @@ protected function isChanged(?IEntity $newValue): bool
// newValue is null
return true;

} else if (!$this->isValuePresent) {
// before initial load, we cannot detect changes
return false;

} elseif ($newValue instanceof IEntity && $newValue->isPersisted()) {
// value is persisted entity or null
// newValue is persisted entity
Expand Down
12 changes: 4 additions & 8 deletions src/Relationships/OneHasOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class OneHasOne extends HasOne
public function __construct(PropertyMetadata $metadata)
{
parent::__construct($metadata);
$this->isValueFromStorage = !$this->metadataRelationship->isMain;
$this->isValuePresent = $this->metadataRelationship->isMain;
}


Expand All @@ -34,25 +34,21 @@ public function setRawValue($value): void
{
parent::setRawValue($value);
if (!$this->metadataRelationship->isMain) {
$this->isValueValidated = false;
$this->isValuePresent = $value !== null;
}
}


public function getRawValue()
{
if ($this->isValueFromStorage && !$this->metadataRelationship->isMain) {
$this->initValue();
}
if (!$this->isValuePresent) $this->initValue();
return parent::getRawValue();
}


public function hasInjectedValue(): bool
{
if ($this->isValueFromStorage && !$this->metadataRelationship->isMain) {
$this->initValue();
}
if (!$this->isValuePresent) $this->initValue();
return parent::hasInjectedValue();
}

Expand Down
22 changes: 22 additions & 0 deletions tests/cases/integration/Relationships/relationships.oneHasOne.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,27 @@ class RelationshipOneHasOneTest extends DataTestCase
}


public function testAccessFromNonMainSide(): void
{
$book = new Book();
$book->author = $this->orm->authors->getByIdChecked(1);
$book->title = 'GoT';
$book->publisher = 1;

$ean = new Ean();
$ean->code = 'GoTEAN';
$ean->book = $book;

$this->orm->books->persistAndFlush($book);
$eanId = $ean->id;

$this->orm->clear();

$ean = $this->orm->eans->getByIdChecked($eanId);
Assert::same('GoT', $ean->book->title);
}


public function testReadOnNullable(): void
{
$album = new PhotoAlbum();
Expand Down Expand Up @@ -265,6 +286,7 @@ class RelationshipOneHasOneTest extends DataTestCase
$ean = new Ean();
$ean->code = '1234';
$ean->book = $this->orm->books->getByIdChecked(1);
Assert::true($ean->isAttached());
$this->orm->eans->persistAndFlush($ean);
$this->orm->clear();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" = 1;
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" = 2;
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" = 1;
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" IN (1);
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" IN (2);
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" IN (1);
START TRANSACTION;
INSERT INTO "books" ("title", "author_id", "translator_id", "next_part", "ean_id", "publisher_id", "genre", "published_at", "printed_at", "thread_id", "price", "price_currency", "orig_price_cents", "orig_price_currency") VALUES ('Books 5', 1, 2, NULL, NULL, 1, 'fantasy', '2021-12-31 23:59:59.000000'::timestamp, NULL, NULL, NULL, NULL, NULL, NULL);
SELECT CURRVAL('public.books_id_seq');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ INSERT INTO "eans" ("code", "type") VALUES ('123', 2);
SELECT CURRVAL('public.eans_id_seq');
UPDATE "books" SET "ean_id" = 1 WHERE "id" = 3;
COMMIT;
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" = 1;
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" = 1;
SELECT "books".* FROM "books" AS "books" WHERE "books"."id" = 4;
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" IN (1);
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" IN (1);
SELECT "books".* FROM "books" AS "books" WHERE "books"."id" IN (4);
START TRANSACTION;
INSERT INTO "eans" ("code", "type") VALUES ('456', 2);
SELECT CURRVAL('public.eans_id_seq');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" = 1;
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" IN (1);
START TRANSACTION;
INSERT INTO "public"."authors" ("name", "born", "web", "favorite_author_id") VALUES ('Jon Snow', '2021-03-21 08:23:00.000000'::timestamp, 'http://www.example.com', NULL);
SELECT CURRVAL('public.authors_id_seq');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SELECT "tags".* FROM "tags" AS "tags" WHERE "tags"."id" = 2;
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" = 1;
SELECT "tags".* FROM "tags" AS "tags" WHERE "tags"."id" IN (2);
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" IN (1);
START TRANSACTION;
INSERT INTO "tag_followers" ("created_at", "author_id", "tag_id") VALUES ('2021-12-02 19:21:00.000000'::timestamptz, 1, 2);
COMMIT;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" = 1;
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" IN (1);
START TRANSACTION;
INSERT INTO "public"."authors" ("name", "born", "web", "favorite_author_id") VALUES ('A', '2021-03-21 08:23:00.000000'::timestamp, 'http://www.example.com', NULL);
SELECT CURRVAL('public.authors_id_seq');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" = 1;
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" IN (1);
START TRANSACTION;
INSERT INTO "public"."authors" ("name", "born", "web", "favorite_author_id") VALUES ('A', '2021-03-21 08:23:00.000000'::timestamp, 'http://www.example.com', NULL);
SELECT CURRVAL('public.authors_id_seq');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" = 1;
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" = 1;
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" IN (1);
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" IN (1);
START TRANSACTION;
INSERT INTO "books" ("title", "author_id", "translator_id", "next_part", "ean_id", "publisher_id", "genre", "published_at", "printed_at", "thread_id", "price", "price_currency", "orig_price_cents", "orig_price_currency") VALUES ('Book 5', 1, NULL, NULL, NULL, 1, 'fantasy', '2021-12-31 23:59:59.000000'::timestamp, NULL, NULL, NULL, NULL, NULL, NULL);
SELECT CURRVAL('public.books_id_seq');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
SELECT "authors".* FROM "public"."authors" AS "authors" WHERE "authors"."id" = 1;
SELECT "publishers".* FROM "publishers" AS "publishers" WHERE "publishers"."publisher_id" = 1;
START TRANSACTION;
INSERT INTO "eans" ("code", "type") VALUES ('GoTEAN', 2);
SELECT CURRVAL('public.eans_id_seq');
INSERT INTO "books" ("title", "author_id", "translator_id", "next_part", "ean_id", "publisher_id", "genre", "published_at", "printed_at", "thread_id", "price", "price_currency", "orig_price_cents", "orig_price_currency") VALUES ('GoT', 1, NULL, NULL, 1, 1, 'fantasy', '2021-12-31 23:59:59.000000'::timestamp, NULL, NULL, NULL, NULL, NULL, NULL);
SELECT CURRVAL('public.books_id_seq');
COMMIT;
SELECT "eans".* FROM "eans" AS "eans" WHERE "eans"."id" = 1;
SELECT "books".* FROM "books" AS "books" WHERE "books"."ean_id" IN (1);

0 comments on commit c1e8df0

Please sign in to comment.