Skip to content

Commit

Permalink
Rework RelationCollection
Browse files Browse the repository at this point in the history
  • Loading branch information
olvlvl committed Nov 2, 2024
1 parent 2ddb284 commit a5f173e
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 135 deletions.
159 changes: 42 additions & 117 deletions lib/ActiveRecord/RelationCollection.php
Original file line number Diff line number Diff line change
@@ -1,156 +1,81 @@
<?php

/*
* This file is part of the ICanBoogie package.
*
* (c) Olivier Laviale <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace ICanBoogie\ActiveRecord;

use ArrayAccess;
use Closure;
use ICanBoogie\ActiveRecord;
use ICanBoogie\OffsetNotWritable;
use ICanBoogie\ActiveRecord\Config\Association;

use function is_string;

/**
* Relation collection of a model.
*
* @implements ArrayAccess<string, Relation>
* Where _key_ is a getter name e.g. 'comments'
*/
class RelationCollection implements ArrayAccess
class RelationCollection
{
/**
* Relations.
*
* @var array<string, Relation>
* Where _key_ is a getter name e.g. 'comments'
* Where _key_ is a getter name; for example, 'comments'
*/
private array $relations;
private readonly array $relations;

public function __construct(
public readonly Model $model,
?ActiveRecord\Config\Association $association,
?Association $association
) {
$this->apply_association($association);
}
$relations = [];

if ($association) {
foreach ($association->belongs_to as $r) {
$as = $r->as;
$relations[$as] = new BelongsToRelation(
owner: $this->model,
related: $r->associate,
local_key: $r->local_key,
foreign_key: $r->foreign_key,
as: $as,
);
}

private function apply_association(?ActiveRecord\Config\Association $association): void
{
if (!$association) {
return;
}
if ($association->has_many) {
assert(is_string($this->model->primary));
}

foreach ($association->belongs_to as $r) {
$this->belongs_to(
related: $r->associate,
local_key: $r->local_key,
foreign_key: $r->foreign_key,
as: $r->as,
);
foreach ($association->has_many as $r) {
$as = $r->as;
$relations[$as] = new HasManyRelation(
owner: $this->model,
related: $r->associate,
foreign_key: $r->foreign_key,
as: $as,
through: $r->through,
);
}
}

foreach ($association->has_many as $r) {
$this->has_many(
related: $r->associate,
foreign_key: $r->foreign_key,
as: $r->as,
through: $r->through,
);
}
$this->relations = $relations;
}

/**
* Checks if a relation exists.
* Whether a relation exists.
*
* @param string $offset Relation name.
* @param non-empty-string $relation_name
*/
public function offsetExists(mixed $offset): bool
public function has(string $relation_name): bool
{
return isset($this->relations[$offset]);
return isset($this->relations[$relation_name]);
}

/**
* Returns a relation.
* Returns an existing relation.
*
* @param string $offset Relation name.
* @param non-empty-string $relation_name
*
* @throws RelationNotDefined if the relation is not defined.
*/
public function offsetGet(mixed $offset): Relation
{
return $this->relations[$offset]
?? throw new RelationNotDefined($offset, $this);
}

/**
* @throws OffsetNotWritable because relations cannot be set.
* @throws RelationNotDefined if the relation doesn't exist.
*/
public function offsetSet(mixed $offset, mixed $value): void
public function get(string $relation_name): ?Relation
{
throw new OffsetNotWritable($offset, $this);
}

/**
* @throws OffsetNotWritable because relations cannot be unset.
*/
public function offsetUnset(mixed $offset): void
{
throw new OffsetNotWritable($offset, $this);
}

/**
* Adds a {@link BelongsToRelation} relation.
*
* @param class-string<ActiveRecord> $related
* @param non-empty-string $local_key
* @param non-empty-string $foreign_key
* @param non-empty-string $as
*/
public function belongs_to(
string $related,
string $local_key,
string $foreign_key,
string $as,
): void {
$this->relations[$as] = new BelongsToRelation(
owner: $this->model,
related: $related,
local_key: $local_key,
foreign_key: $foreign_key,
as: $as,
);
}

/**
* Adds a {@link HasManyRelation} relation.
*
* @param class-string<ActiveRecord> $related
* @param non-empty-string $foreign_key
* @param non-empty-string $as
* @param class-string<ActiveRecord>|null $through
*/
public function has_many(
string $related,
string $foreign_key,
string $as,
?string $through = null,
): void {
assert(is_string($this->model->primary));

$this->relations[$as] = new HasManyRelation(
owner: $this->model,
related: $related,
foreign_key: $foreign_key,
as: $as,
through: $through,
);
return $this->relations[$relation_name]
?? throw new RelationNotDefined($relation_name, $this);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions tests/ActiveRecord/HasManyRelationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function test_relations(): void
$relations = $this->articles->relations;
$this->assertInstanceOf(RelationCollection::class, $relations);

$relation = $relations['comments'];
$relation = $relations->get('comments');
$this->assertInstanceOf(HasManyRelation::class, $relation);
$this->assertSame('comments', $relation->as);
$this->assertSame($this->articles, $relation->owner);
Expand All @@ -71,7 +71,7 @@ public function test_relations(): void
public function test_undefined_relation(): void
{
$this->expectException(RelationNotDefined::class);
$this->articles->relations['undefined_relation'];
$this->articles->relations->get('undefined_relation');
}

public function test_getter(): void
Expand Down
8 changes: 4 additions & 4 deletions tests/ActiveRecord/HasManyRelationThroughTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,18 @@ public function test_through_is_set(): void
{
$r = $this->physicians->relations;

$this->assertArrayHasKey('appointments', $r);
$this->assertArrayHasKey('patients', $r);
$this->assertTrue($r->has('appointments'));
$this->assertTrue($r->has('patients'));

$ra = $r['appointments'];
$ra = $r->get('appointments');
assert($ra instanceof HasManyRelation);
$this->assertInstanceOf(HasManyRelation::class, $ra);
$this->assertEquals('appointments', $ra->as);
$this->assertEquals('ph_id', $ra->local_key);
$this->assertEquals('physician_id', $ra->foreign_key);
$this->assertNull($ra->through);

$rp = $r['patients'];
$rp = $r->get('patients');
assert($rp instanceof HasManyRelation);
$this->assertInstanceOf(HasManyRelation::class, $rp);
$this->assertEquals('patients', $rp->as);
Expand Down
8 changes: 4 additions & 4 deletions tests/ActiveRecord/ModelBelongsToTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ public function getter_is_created_from_the_column_name_without_id_suffix(): void

$people = $models->model_for_record(Person::class);

$this->assertArrayHasKey('dance_session', $people->relations);
$this->assertArrayHasKey('hire_skill', $people->relations);
$this->assertArrayHasKey('summon_skill', $people->relations);
$this->assertArrayHasKey('teach_skill', $people->relations);
$this->assertTrue($people->relations->has('dance_session'));
$this->assertTrue($people->relations->has('hire_skill'));
$this->assertTrue($people->relations->has('summon_skill'));
$this->assertTrue($people->relations->has('teach_skill'));
}
}
14 changes: 6 additions & 8 deletions tests/ActiveRecord/RelationNotDefinedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@ final class RelationNotDefinedTest extends TestCase
public function test_exception(): void
{
$relation_name = uniqid();
$collection = $this
->getMockBuilder(RelationCollection::class)
->disableOriginalConstructor()
->getMock();
$relations = new class() extends RelationCollection {
public function __construct() {
}
};

/* @var $collection RelationCollection */

$exception = new RelationNotDefined($relation_name, $collection);
$exception = new RelationNotDefined($relation_name, $relations);

$this->assertSame($relation_name, $exception->relation_name);
$this->assertSame($collection, $exception->collection);
$this->assertSame($relations, $exception->collection);
}
}

0 comments on commit a5f173e

Please sign in to comment.