Skip to content

Commit 3cbc0af

Browse files
committed
Add convenience association handling
1 parent 6caa48b commit 3cbc0af

9 files changed

+211
-25
lines changed

src/Record.php

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,14 @@ class Record implements \ArrayAccess
2929

3030
private $model;
3131

32-
public function __construct(Schema $schema)
32+
public function __construct(Schema $schema, Model $model = null)
3333
{
3434
$this->schema = $schema;
3535
$this->values = array_fill_keys($schema->getFields(), null);
3636
$this->state = self::STATE_INSERT;
3737
$this->changed = [];
3838
$this->referencedRecords = [];
39+
$this->model = $model;
3940
}
4041

4142
public function getPrimaryKey(): array
@@ -119,6 +120,53 @@ public function getReferencedRecords(string $name): array
119120
return $this->referencedRecords[$name];
120121
}
121122

123+
public function associate(string $name, Model $model): void
124+
{
125+
$relationship = $this->getSchema()->getRelationship($name);
126+
127+
if (!$relationship->isUniqueRelationship()) {
128+
throw new \InvalidArgumentException('A single model can only be associated to an unique relationships');
129+
}
130+
131+
$keys = $relationship->getFields();
132+
$fields = $relationship->getReferencedFields();
133+
$record = $model->getDatabaseRecord();
134+
135+
if ($record->getSchema() !== $relationship->getReferencedSchema()) {
136+
throw new \InvalidArgumentException('The associated model has a record in unexpected schema');
137+
}
138+
139+
while ($keys) {
140+
$value = $record[array_pop($fields)];
141+
142+
if ($value === null) {
143+
throw new \RuntimeException('Cannot associate to models with nulls in referenced fields');
144+
}
145+
146+
$this[array_pop($keys)] = $value;
147+
}
148+
149+
$this->referencedRecords[$relationship->getName()] = [$record];
150+
$reverse = $relationship->getReverseRelationship();
151+
152+
if ($reverse->isUniqueRelationship()) {
153+
$record->referencedRecords[$reverse->getName()] = [$this];
154+
} elseif ($record->hasReferencedRecords($reverse->getName())) {
155+
$record->referencedRecords[$reverse->getName()][] = $this;
156+
}
157+
}
158+
159+
public function addAssociation(string $name, Model $model): void
160+
{
161+
$relationship = $this->getSchema()->getRelationship($name);
162+
163+
if ($relationship->isUniqueRelationship()) {
164+
throw new \InvalidArgumentException('Cannot add a new model to an unique relationship');
165+
}
166+
167+
$model->getDatabaseRecord()->associate($relationship->getReverseRelationship()->getName(), $this->getModel());
168+
}
169+
122170
public function getRelatedModel(string $name): ?Model
123171
{
124172
$relationship = $this->getSchema()->getRelationship($name);

tests/helpers/IntegrationTestCase.php

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ abstract class IntegrationTestCase extends TestCase
2323
/** @var TestParentSchema */
2424
protected $parentSchema;
2525

26+
/** @var TestHouseSchema */
27+
protected $houseSchema;
28+
2629
abstract protected function createConnection(): Connection;
2730
abstract protected function setUpDatabase(Connection $connection): void;
2831

@@ -32,17 +35,19 @@ protected function setUp()
3235

3336
$this->personSchema = new TestPersonSchema($container);
3437
$this->parentSchema = new TestParentSchema($container);
38+
$this->houseSchema = new TestHouseSchema($container);
3539

3640
$container[TestPersonSchema::class] = $this->personSchema;
3741
$container[TestParentSchema::class] = $this->parentSchema;
42+
$container[TestHouseSchema::class] = $this->houseSchema;
3843

3944
$this->connection = $this->createConnection();
4045
$this->setUpDatabase($this->connection);
4146
}
4247

43-
private function getTestPersonRepository(): TestPersonRepository
48+
private function getTestPersonRepository(): TestRepository
4449
{
45-
return new TestPersonRepository($this->connection, $this->personSchema, $this->parentSchema);
50+
return new TestRepository($this->connection, $this->personSchema, $this->parentSchema, $this->houseSchema);
4651
}
4752

4853
public function testCrudOperations(): void
@@ -161,20 +166,29 @@ public function testBooleanFields(): void
161166
$this->assertCount(0, $repository->findByHasLicense(false));
162167
}
163168

164-
public function testLoadingRelationships(): void
169+
public function testRelationships(): void
165170
{
166171
$repository = $this->getTestPersonRepository();
167172

173+
$home = $repository->createHouse('Anonymous Street');
174+
$repository->saveHouse($home);
175+
168176
$jane = $repository->createPerson('Jane', 'Doe', 20);
169177
$john = $repository->createPerson('John', 'Doe', 20);
170178
$mama = $repository->createPerson('Mama', 'Doe', 40);
171179
$papa = $repository->createPerson('Papa', 'Doe', 40);
172180

181+
$home->movePeople([$jane, $john, $mama, $papa]);
182+
173183
$repository->savePerson($jane);
174184
$repository->savePerson($john);
175185
$repository->savePerson($mama);
176186
$repository->savePerson($papa);
177187

188+
$mama->marry($papa);
189+
$repository->savePerson($mama);
190+
$repository->savePerson($papa);
191+
178192
$repository->makeParent($jane, $mama);
179193
$repository->makeParent($jane, $papa);
180194
$repository->makeParent($john, $mama);
@@ -184,37 +198,33 @@ public function testLoadingRelationships(): void
184198

185199
$repository->loadFamily([$person]);
186200

201+
$this->assertSame('Anonymous Street', $person->getHome()->getStreet());
202+
$this->assertCount(4, $person->getHome()->getResidents());
203+
187204
$firstName = function (TestPersonModel $model): string {
188205
return $model->getFirstName();
189206
};
190207

191-
$childRecords = function (TestPersonModel $model): array {
192-
$childRecords = [];
193-
194-
foreach ($model->getChildren() as $child) {
195-
$childRecords[] = $child->getDatabaseRecord();
196-
}
197-
198-
return $childRecords;
199-
};
200-
201208
$parents = $person->getParents();
202209

203210
$this->assertCount(2, $parents);
204211

212+
$residentNames = array_map($firstName, $person->getHome()->getResidents());
205213
$names = array_map($firstName, $parents);
206214
sort($names);
215+
sort($residentNames);
207216

208217
$this->assertSame(['Mama', 'Papa'], $names);
218+
$this->assertSame(['Jane', 'John', 'Mama', 'Papa'], $residentNames);
209219

210220
foreach ($parents as $parent) {
211-
$this->assertTrue(\in_array($person->getDatabaseRecord(), $childRecords($parent), true));
221+
$this->assertContains($person, $parent->getChildren());
212222
}
213223

214224
$repository->loadFamily($parents);
215225

216226
foreach ($parents as $parent) {
217-
$this->assertTrue(\in_array($person->getDatabaseRecord(), $childRecords($parent), true));
227+
$this->assertContains($person, $parent->getChildren());
218228
}
219229
}
220230

tests/helpers/TestHouseModel.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Simply\Database\Test;
4+
5+
use Simply\Database\Model;
6+
use Simply\Database\Record;
7+
8+
/**
9+
* TestHouseModel.
10+
* @author Riikka Kalliomäki <[email protected]>
11+
* @copyright Copyright (c) 2018 Riikka Kalliomäki
12+
* @license http://opensource.org/licenses/mit-license.php MIT License
13+
*/
14+
class TestHouseModel extends Model
15+
{
16+
public function __construct(TestHouseSchema $schema, string $street)
17+
{
18+
$record = new Record($schema, $this);
19+
$record['street'] = $street;
20+
21+
parent::__construct($record);
22+
}
23+
24+
public function getStreet(): string
25+
{
26+
return $this->record['street'];
27+
}
28+
29+
public function movePeople(array $people): void
30+
{
31+
foreach ($people as $person) {
32+
$this->record->addAssociation('residents', $person);
33+
}
34+
}
35+
36+
/**
37+
* @return TestHouseModel[]
38+
*/
39+
public function getResidents(): array
40+
{
41+
return $this->record->getRelatedModels('residents');
42+
}
43+
}

tests/helpers/TestHouseSchema.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Simply\Database\Test;
4+
5+
use Simply\Database\Schema;
6+
7+
/**
8+
* TestHouseSchema.
9+
* @author Riikka Kalliomäki <[email protected]>
10+
* @copyright Copyright (c) 2018 Riikka Kalliomäki
11+
* @license http://opensource.org/licenses/mit-license.php MIT License
12+
*/
13+
class TestHouseSchema extends Schema
14+
{
15+
protected $model = TestHouseModel::class;
16+
17+
protected $table = 'phpunit_tests_house';
18+
19+
protected $primaryKey = 'id';
20+
21+
protected $fields = ['id', 'street'];
22+
23+
protected $relationships = [
24+
'residents' => [
25+
'key' => 'id',
26+
'schema' => TestPersonSchema::class,
27+
'field' => 'home_id',
28+
],
29+
];
30+
}

tests/helpers/TestParentModel.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ class TestParentModel extends Model
1515
{
1616
public function __construct(TestParentSchema $schema, TestPersonModel $child, TestPersonModel $parent)
1717
{
18-
$record = new Record($schema);
19-
$record['child_id'] = $child->getId();
20-
$record['parent_id'] = $parent->getId();
18+
$record = new Record($schema, $this);
19+
20+
$record->associate('child', $child);
21+
$record->associate('parent', $parent);
2122

2223
parent::__construct($record);
2324
}
24-
}
25+
}

tests/helpers/TestPersonModel.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class TestPersonModel extends Model
1515
{
1616
public function __construct(TestPersonSchema $schema, string $firstName, string $lastName, int $age)
1717
{
18-
$record = new Record($schema);
18+
$record = new Record($schema, $this);
1919
$record['first_name'] = $firstName;
2020
$record['last_name'] = $lastName;
2121
$record['age'] = $age;
@@ -79,4 +79,15 @@ public function getChildren(): array
7979
{
8080
return $this->record->getRelatedModelsByProxy('children', 'child');
8181
}
82+
83+
public function marry(TestPersonModel $person): void
84+
{
85+
$this->record->associate('spouse', $person);
86+
$person->record->associate('spouse', $this);
87+
}
88+
89+
public function getHome(): TestHouseModel
90+
{
91+
return $this->record->getRelatedModel('home');
92+
}
8293
}

tests/helpers/TestPersonSchema.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class TestPersonSchema extends Schema
1818

1919
protected $primaryKey = 'id';
2020

21-
protected $fields = ['id', 'first_name', 'last_name', 'age', 'weight', 'license'];
21+
protected $fields = ['id', 'first_name', 'last_name', 'age', 'weight', 'license', 'spouse_id', 'home_id'];
2222

2323
protected $relationships = [
2424
'parents' => [
@@ -31,5 +31,20 @@ class TestPersonSchema extends Schema
3131
'schema' => TestParentSchema::class,
3232
'field' => 'parent_id',
3333
],
34+
'spouse' => [
35+
'key' => 'spouse_id',
36+
'schema' => TestPersonSchema::class,
37+
'field' => 'id',
38+
],
39+
'spouse_alt' => [
40+
'key' => 'id',
41+
'schema' => TestPersonSchema::class,
42+
'field' => 'spouse_id',
43+
],
44+
'home' => [
45+
'key' => 'home_id',
46+
'schema' => TestHouseSchema::class,
47+
'field' => 'id',
48+
],
3449
];
3550
}

tests/helpers/TestPersonRepository.php renamed to tests/helpers/TestRepository.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,35 @@
1111
* @copyright Copyright (c) 2018 Riikka Kalliomäki
1212
* @license http://opensource.org/licenses/mit-license.php MIT License
1313
*/
14-
class TestPersonRepository extends Repository
14+
class TestRepository extends Repository
1515
{
1616
private $personSchema;
1717
private $parentSchema;
18+
private $houseSchema;
1819

1920
public function __construct(
2021
Connection $connection,
2122
TestPersonSchema $personSchema,
22-
TestParentSchema $parentSchema
23+
TestParentSchema $parentSchema,
24+
TestHouseSchema $houseSchema
2325
) {
2426
parent::__construct($connection);
2527

2628
$this->personSchema = $personSchema;
2729
$this->parentSchema = $parentSchema;
30+
$this->houseSchema = $houseSchema;
2831
}
2932

3033
public function createPerson(string $firstName, string $lastName, int $age): TestPersonModel
3134
{
3235
return new TestPersonModel($this->personSchema, $firstName, $lastName, $age);
3336
}
3437

38+
public function createHouse(string $street): TestHouseModel
39+
{
40+
return new TestHouseModel($this->houseSchema, $street);
41+
}
42+
3543
public function findById(int $id): ?TestPersonModel
3644
{
3745
return $this->findByPrimaryKey($this->personSchema, $id);
@@ -86,6 +94,11 @@ public function savePerson(TestPersonModel $model): void
8694
$this->save($model);
8795
}
8896

97+
public function saveHouse(TestHouseModel $model): void
98+
{
99+
$this->save($model);
100+
}
101+
89102
public function makeParent(TestPersonModel $child, TestPersonModel $parent)
90103
{
91104
$relationship = new TestParentModel($this->parentSchema, $child, $parent);
@@ -97,6 +110,8 @@ public function loadFamily(array $people)
97110
$this->fillRelationships($people, [
98111
'parents.parent.children.child',
99112
'children.child.parents.parent',
113+
'spouse.spouse',
114+
'home.residents',
100115
]);
101116
}
102117

0 commit comments

Comments
 (0)