Skip to content

Commit

Permalink
Improve, add tests and fix issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov committed Jun 22, 2024
1 parent 0d51f69 commit 552216b
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 13 deletions.
29 changes: 18 additions & 11 deletions src/AbstractActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
* ActiveRecord is the base class for classes representing relational data in terms of objects.
*
* See {@see ActiveRecord} for a concrete implementation.
*
* @psalm-import-type ARClass from ActiveQueryInterface
*/
abstract class AbstractActiveRecord implements ActiveRecordInterface
{
Expand Down Expand Up @@ -260,16 +262,17 @@ public function hasAttribute(string $name): bool
*
* Call methods declared in {@see ActiveQuery} to further customize the relation.
*
* @param string $class The class name of the related record
* @param string|ActiveRecordInterface|Closure $class The class name of the related record, or an instance of the
* related record, or a Closure to create an {@see ActiveRecordInterface} object.
* @param array $link The primary-foreign key constraint. The keys of the array refer to the attributes of the
* record associated with the `$class` model, while the values of the array refer to the corresponding attributes in
* **this** AR class.
*
* @return ActiveQueryInterface The relational query object.
*
* @psalm-param class-string<ActiveRecordInterface> $class
* @psalm-param ARClass $class
*/
public function hasMany(string $class, array $link): ActiveQueryInterface
public function hasMany(string|ActiveRecordInterface|Closure $class, array $link): ActiveQueryInterface
{
return $this->createRelationQuery($class, $link, true);
}
Expand Down Expand Up @@ -298,16 +301,17 @@ public function hasMany(string $class, array $link): ActiveQueryInterface
*
* Call methods declared in {@see ActiveQuery} to further customize the relation.
*
* @param string $class The class name of the related record.
* @param string|ActiveRecordInterface|Closure $class The class name of the related record, or an instance of the
* related record, or a Closure to create an {@see ActiveRecordInterface} object.
* @param array $link The primary-foreign key constraint. The keys of the array refer to the attributes of the
* record associated with the `$class` model, while the values of the array refer to the corresponding attributes in
* **this** AR class.
*
* @return ActiveQueryInterface The relational query object.
*
* @psalm-param class-string<ActiveRecordInterface> $class
* @psalm-param ARClass $class
*/
public function hasOne(string $class, array $link): ActiveQueryInterface
public function hasOne(string|ActiveRecordInterface|Closure $class, array $link): ActiveQueryInterface
{
return $this->createRelationQuery($class, $link, false);
}
Expand All @@ -318,9 +322,12 @@ public function insert(array $attributes = null): bool
}

/**
* @psalm-param class-string<ActiveRecordInterface> $arClass
* @param string|ActiveRecordInterface|Closure $arClass The class name of the related record, or an instance of the
* related record, or a Closure to create an {@see ActiveRecordInterface} object.
*
* @psalm-param ARClass $arClass
*/
public function instantiateQuery(string $arClass): ActiveQueryInterface
public function instantiateQuery(string|ActiveRecordInterface|Closure $arClass): ActiveQueryInterface
{
return new ActiveQuery($arClass, $this->db);
}
Expand Down Expand Up @@ -1070,18 +1077,18 @@ protected function setRelationDependencies(
/**
* Creates a query instance for `has-one` or `has-many` relation.
*
* @param string $arClass The class name of the related record.
* @param string|ActiveRecordInterface|Closure $arClass The class name of the related record.
* @param array $link The primary-foreign key constraint.
* @param bool $multiple Whether this query represents a relation to more than one record.
*
* @return ActiveQueryInterface The relational query object.
*
* @psalm-param class-string<ActiveRecordInterface> $arClass
* @psalm-param ARClass $arClass
* {@see hasOne()}
* {@see hasMany()}
*/
protected function createRelationQuery(string $arClass, array $link, bool $multiple): ActiveQueryInterface
protected function createRelationQuery(string|ActiveRecordInterface|Closure $arClass, array $link, bool $multiple): ActiveQueryInterface
{
return $this->instantiateQuery($arClass)->primaryModel($this)->link($link)->multiple($multiple);
}
Expand Down
6 changes: 4 additions & 2 deletions src/ActiveQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@
* These methods may only be called in a relational context. Same is true for {@see inverseOf()}, which marks a relation
* as inverse of another relation and {@see onCondition()} which adds a condition that's to be added to relational
* query join condition.
*
* @psalm-import-type ARClass from ActiveQueryInterface
*/
class ActiveQuery extends Query implements ActiveQueryInterface
{
Expand All @@ -113,7 +115,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
private array $joinWith = [];

/**
* @psalm-param class-string<ActiveRecordInterface>|ActiveRecordInterface|Closure $arClass
* @psalm-param ARClass $arClass
*/
final public function __construct(
protected string|ActiveRecordInterface|Closure $arClass,
Expand Down Expand Up @@ -990,7 +992,7 @@ public function getARClassName(): string
public function getARInstance(): ActiveRecordInterface
{
if ($this->arClass instanceof ActiveRecordInterface) {
return $this->arClass;
return clone $this->arClass;
}

if ($this->arClass instanceof Closure) {
Expand Down
7 changes: 7 additions & 0 deletions src/ActiveQueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
* represents a relation between two active record classes and will return related records only.
*
* A class implementing this interface should also use {@see ActiveQueryTrait} and {@see ActiveRelationTrait}.
*
* @psalm-type ARClass = class-string<ActiveRecordInterface>|ActiveRecordInterface|Closure():ActiveRecordInterface
*/
interface ActiveQueryInterface extends QueryInterface
{
Expand Down Expand Up @@ -298,6 +300,11 @@ public function getTablesUsedInFrom(): array;
*/
public function getSql(): string|null;

/**
* @return string|ActiveRecordInterface|Closure The AR class associated with this query.
*
* @psalm-return ARClass
*/
public function getARClass(): string|ActiveRecordInterface|Closure;

/**
Expand Down
15 changes: 15 additions & 0 deletions tests/ActiveRecordTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -948,4 +948,19 @@ public function testGetDirtyAttributesAfterFind(): void
$customer->getDirtyAttributes(['id', 'email', 'address', 'status', 'unknown']),
);
}

public function testRelationWithInstance(): void
{
$this->checkFixture($this->db, 'customer');

$customerQuery = new ActiveQuery(Customer::class, $this->db);
$customer = $customerQuery->findOne(2);

$orders = $customer->getOrdersUsingInstance();

$this->assertTrue($customer->isRelationPopulated('ordersUsingInstance'));
$this->assertCount(2, $orders);
$this->assertSame(2, $orders[0]->getId());
$this->assertSame(3, $orders[1]->getId());
}
}
6 changes: 6 additions & 0 deletions tests/Stubs/ActiveRecord/Customer.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public function relationQuery(string $name): ActiveQueryInterface
'orderItems' => $this->getOrderItemsQuery(),
'orderItems2' => $this->getOrderItems2Query(),
'items2' => $this->getItems2Query(),
'ordersUsingInstance' => $this->hasMany(new Order($this->db()), ['customer_id' => 'id']),
default => parent::relationQuery($name),
};
}
Expand Down Expand Up @@ -261,4 +262,9 @@ public function getItems2Query(): ActiveQuery
return $this->hasMany(Item::class, ['id' => 'item_id'])
->via('orderItems2');
}

public function getOrdersUsingInstance(): array
{
return $this->relation('ordersUsingInstance');
}
}

0 comments on commit 552216b

Please sign in to comment.