Skip to content
Merged
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
20 changes: 20 additions & 0 deletions tests/Model/UsersTyped.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Tests\Model;

/**
* Model whose typed properties are non-nullable and have no default value, so a freshly
* constructed instance keeps them "uninitialized". The repository hydration path serializes an
* empty instance (via anydataset-db's PreFetchTrait) to enumerate the fields before copying the
* row into it, which used to raise "Typed property ... must not be accessed before initialization".
*
* The property names match the `users` table columns, so no field mapping is required.
*/
class UsersTyped
{
public int $id;

public string $name;

public string $createdate;
}
42 changes: 42 additions & 0 deletions tests/RepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
use Tests\Model\TestUpdateProcessor;
use Tests\Model\Users;
use Tests\Model\UsersMap;
use Tests\Model\UsersTyped;
use Tests\Model\UsersWithAttribute;
use Throwable;

Expand Down Expand Up @@ -135,6 +136,47 @@ public function testGet()
$this->assertEquals('2015-05-02 00:00:00', $users->getCreatedate());
}

/**
* Regression: a model with non-nullable typed properties (no default) is "uninitialized" on
* `new`. The hydration path serializes an empty instance to enumerate the fields, which used
* to raise "must not be accessed before initialization".
*/
public function testGetIntoUninitializedTypedModel()
{
$mapper = new Mapper(UsersTyped::class, 'users', 'id');
$repository = new Repository($this->repository->getExecutor(), $mapper);

$user = $repository->get(1);
$this->assertInstanceOf(UsersTyped::class, $user);
$this->assertEquals(1, $user->id);
$this->assertEquals('John Doe', $user->name);
$this->assertEquals('2015-05-02 00:00:00', $user->createdate);
}

/**
* Regression: saving an entity whose non-nullable typed PK is left uninitialized (it is an
* auto-increment column) must not raise "must not be accessed before initialization" when the
* entity is serialized to build the INSERT. The uninitialized property is simply omitted.
*/
public function testSaveObjectWithUninitializedTypedProperty()
{
$mapper = new Mapper(UsersTyped::class, 'users', 'id');
$repository = new Repository($this->repository->getExecutor(), $mapper);

$user = new UsersTyped();
$user->name = 'Newbie';
// $user->id and $user->createdate are intentionally left uninitialized: both must be
// omitted from the INSERT instead of raising an error while serializing the entity.

$repository->save($user);

// save() writes the auto-increment id back onto the entity, proving the INSERT ran and the
// uninitialized properties were serialized (omitted) without raising an error. createdate
// stays uninitialized (save only writes back the PK), so it is not read here.
$this->assertEquals(4, $user->id);
$this->assertEquals('Newbie', $user->name);
}

public function testGetByFilter()
{
$users = $this->repository->getByFilter('id = :id', ['id' => 1]);
Expand Down
Loading