Skip to content

Commit

Permalink
Merge pull request #17 from cycle/active-query
Browse files Browse the repository at this point in the history
feat: ActiveQuery PoC
  • Loading branch information
lotyp authored May 5, 2024
2 parents d8ef258 + ba41883 commit 6d08c64
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 30 deletions.
4 changes: 2 additions & 2 deletions infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"report": "master"
}
},
"minCoveredMsi": 70,
"minMsi": 55,
"minCoveredMsi": 50,
"minMsi": 50,
"phpUnit": {
"configDir": "./"
},
Expand Down
14 changes: 14 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
<code><![CDATA[setOrm]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/Repository/ActiveRepository.php">
<UnusedClass>
<code><![CDATA[ActiveRepository]]></code>
</UnusedClass>
</file>
<file src="tests/app/Bootloader/SyncTablesBootloader.php">
<UnusedClass>
<code><![CDATA[SyncTablesBootloader]]></code>
Expand All @@ -36,10 +41,19 @@
</PossiblyUnusedProperty>
</file>
<file src="tests/app/Entity/User.php">
<PossiblyUnusedMethod>
<code><![CDATA[getIdentity]]></code>
</PossiblyUnusedMethod>
<PossiblyUnusedProperty>
<code><![CDATA[$identity]]></code>
</PossiblyUnusedProperty>
</file>
<file src="tests/app/Query/UserQuery.php">
<PossiblyUnusedMethod>
<code><![CDATA[active]]></code>
<code><![CDATA[orderByCreatedAt]]></code>
</PossiblyUnusedMethod>
</file>
<file src="tests/src/Pest.php">
<InternalMethod>
<code><![CDATA[in]]></code>
Expand Down
32 changes: 11 additions & 21 deletions src/ActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,53 @@

namespace Cycle\ActiveRecord;

use Cycle\ActiveRecord\Query\ActiveQuery;
use Cycle\ORM\EntityManager;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\ORMInterface;
use Cycle\ORM\RepositoryInterface;
use Cycle\ORM\Select;
use Cycle\ORM\Transaction\StateInterface;
use Throwable;

/**
* @template TEntity of ActiveRecord
*/
abstract class ActiveRecord
{
/**
* Finds a single record based on the given primary key.
*/
final public static function find(mixed $primaryKey): ?static
{
/** @var static|null $entity */
$entity = self::getRepository()->findByPK($primaryKey);

return $entity;
return static::query()->wherePK($primaryKey)->fetchOne();
}

/**
* Finds a single record based on the given scope.
*/
final public static function findOne(array $scope = []): ?static
{
/** @var static|null $entity */
$entity = self::getRepository()->findOne($scope);

return $entity;
return static::query()->where($scope)->fetchOne();
}

/**
* Finds all records based on the given scope.
*
* @return iterable<static>
*/
final public static function findAll(array $scope = []): iterable
{
return self::getRepository()->findAll($scope);
return static::query()->where($scope)->fetchAll();
}

/**
* Returns a Select query builder for the extending entity class.
* Returns a ActiveQuery query builder for the extending entity class.
*
* @return Select<TEntity>
* @return ActiveQuery<static>
*/
final public static function query(): Select
public static function query(): ActiveQuery
{
/** @var Select<TEntity> $select */
$select = new Select(self::getOrm(), static::class);

return $select;
return new ActiveQuery(static::class);
}

private static function getRepository(): RepositoryInterface
public static function getRepository(): RepositoryInterface
{
return self::getOrm()->getRepository(static::class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Cycle\ActiveRecord\Exceptions;
namespace Cycle\ActiveRecord\Contract;

use Throwable;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

declare(strict_types=1);

namespace Cycle\ActiveRecord\Exceptions;
namespace Cycle\ActiveRecord\Exception;

use Cycle\ActiveRecord\Contract\ActiveRecordException;
use RuntimeException;

class ConfigurationException extends RuntimeException implements ActiveRecordException
Expand Down
2 changes: 1 addition & 1 deletion src/Facade.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Cycle\ActiveRecord;

use Cycle\ActiveRecord\Exceptions\ConfigurationException;
use Cycle\ActiveRecord\Exception\ConfigurationException;
use Cycle\ORM\EntityManager;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\ORMInterface;
Expand Down
24 changes: 24 additions & 0 deletions src/Query/ActiveQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Cycle\ActiveRecord\Query;

use Cycle\ActiveRecord\Facade;
use Cycle\ORM\Select;

/**
* @template-covariant TEntity of object
*
* @extends Select<TEntity>
*/
class ActiveQuery extends Select

Check failure on line 15 in src/Query/ActiveQuery.php

View workflow job for this annotation

GitHub Actions / static-analysis (ubuntu-latest, 8.2, locked)

Template type TEntity is declared as covariant, but occurs in invariant position in extended type Cycle\ORM\Select<TEntity of object> of class Cycle\ActiveRecord\Query\ActiveQuery.
{
/**
* @param class-string<TEntity> $class
*/
final public function __construct(string $class)
{
parent::__construct(Facade::getOrm(), $class);
}
}
61 changes: 61 additions & 0 deletions src/Repository/ActiveRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Cycle\ActiveRecord\Repository;

use Cycle\ActiveRecord\Facade;
use Cycle\ORM\Select;

/**
* @internal
*
* @template-covariant TEntity of object
*/
class ActiveRepository
{
/** @var Select<TEntity> */
private Select $select;

/**
* @param class-string<TEntity> $class
*/
final public function __construct(string $class)
{
$orm = Facade::getOrm();
$this->select = new Select($orm, $class);
}

/**
* ActiveQuery is always immutable by default.
*/
public function __clone()
{
$this->select = clone $this->select;
}

/**
* Get selector associated with the repository.
*
* @return Select<TEntity>
*/
public function select(): Select

Check failure on line 42 in src/Repository/ActiveRepository.php

View workflow job for this annotation

GitHub Actions / static-analysis (ubuntu-latest, 8.2, locked)

Template type TEntity is declared as covariant, but occurs in invariant position in return type of method Cycle\ActiveRecord\Repository\ActiveRepository::select().
{
return clone $this->select;
}

public function find(mixed $id): ?object
{
return $this->select()->wherePK($id)->fetchOne();
}

public function findOne(array $scope = []): ?object
{
return $this->select()->fetchOne($scope);
}

public function findAll(array $scope = [], array $orderBy = []): iterable
{
return $this->select()->where($scope)->orderBy($orderBy)->fetchAll();
}
}
17 changes: 14 additions & 3 deletions tests/app/Entity/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Relation\BelongsTo;
use Cycle\App\Query\UserQuery;

/**
* @extends ActiveRecord<User>
*/
#[Entity(table: 'user')]
class User extends ActiveRecord
{
Expand All @@ -24,9 +22,22 @@ class User extends ActiveRecord
#[BelongsTo(target: Identity::class, innerKey: 'id', outerKey: 'id', cascade: true, load: 'eager')]
public Identity $identity;

/**
* @return UserQuery<static>
*/
public static function query(): UserQuery
{
return new UserQuery(static::class);
}

public function __construct(string $name, ?Identity $identity = null)
{
$this->name = $name;
$this->identity = $identity ?? new Identity();
}

public function getIdentity()
{
return self::query()->where('id', $this->id)->fetchOne();
}
}
25 changes: 25 additions & 0 deletions tests/app/Query/UserQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Cycle\App\Query;

use Cycle\ActiveRecord\Query\ActiveQuery;

/**
* @template-covariant TEntity of object
*
* @extends ActiveQuery<TEntity>
*/
class UserQuery extends ActiveQuery
{
public function active(bool $state = true): UserQuery
{
return $this->where(['active' => $state]);
}

public function orderByCreatedAt(string $direction = 'ASC'): UserQuery
{
return $this->orderBy(['created_at' => $direction]);
}
}
14 changes: 14 additions & 0 deletions tests/src/ActiveRecordTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Cycle\ActiveRecord\Facade;
use Cycle\App\Entity\User;
use Cycle\Database\DatabaseManager;
use Cycle\ORM\Select\Repository;
use PHPUnit\Framework\Attributes\Test;
use Throwable;

Expand Down Expand Up @@ -163,7 +164,9 @@ public function it_deletes_entity(): void
#[Test]
public function it_deletes_multiple_entities_using_remove_method(): void
{
/** @var User $userOne */
$userOne = User::find(1);
/** @var User $userTwo */
$userTwo = User::find(2);

$userOne->remove();
Expand All @@ -176,4 +179,15 @@ public function it_deletes_multiple_entities_using_remove_method(): void

$this::assertCount(0, User::findAll());
}

/**
* @test
*/
#[Test]
public function it_gets_default_repository_of_entity(): void
{
$repository = User::getRepository();

$this::assertInstanceOf(Repository::class, $repository);
}
}
2 changes: 1 addition & 1 deletion tests/src/FacadeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Cycle\Tests;

use Cycle\ActiveRecord\Exceptions\ConfigurationException;
use Cycle\ActiveRecord\Exception\ConfigurationException;
use Cycle\ActiveRecord\Facade;
use Cycle\ORM\EntityManager;
use Cycle\ORM\ORMInterface;
Expand Down

0 comments on commit 6d08c64

Please sign in to comment.