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
9 changes: 8 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,15 @@
"source": "https://github.com/cycle/active-record",
"security": "https://github.com/cycle/active-record/blob/master/.github/SECURITY.md"
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/cycle"
}
],
"require": {
"php": "^8.2",
"php": ">=8.2",
"cycle/database": "^2.11",
"cycle/orm": "^2.7",
"psr/container": "^2.0"
},
Expand Down
1,865 changes: 952 additions & 913 deletions composer.lock

Large diffs are not rendered by default.

87 changes: 0 additions & 87 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,91 +35,4 @@
<code><![CDATA[forUpdate]]></code>
</PossiblyUnusedMethod>
</file>
<file src="tests/src/Functional/ActiveRecordTest.php">
<AssignmentToVoid>
<code><![CDATA[$entityManager]]></code>
</AssignmentToVoid>
<NullReference>
<code><![CDATA[isSuccess]]></code>
</NullReference>
<PossiblyUnusedMethod>
<code><![CDATA[it_creates_entity_instance_using_make]]></code>
<code><![CDATA[it_deletes_entity]]></code>
<code><![CDATA[it_deletes_multiple_entities_in_single_transaction]]></code>
<code><![CDATA[it_finds_all_entities]]></code>
<code><![CDATA[it_finds_entity_by_primary_key]]></code>
<code><![CDATA[it_finds_one_entity]]></code>
<code><![CDATA[it_gets_default_repository_of_entity]]></code>
<code><![CDATA[it_persists_multiple_entities_in_single_transaction]]></code>
<code><![CDATA[it_runs_transaction_in_current_transaction_mode_without_opened_transaction]]></code>
<code><![CDATA[it_runs_transaction_in_transaction]]></code>
<code><![CDATA[it_runs_transaction_without_actions]]></code>
<code><![CDATA[it_saves_entity]]></code>
<code><![CDATA[it_triggers_exception_when_tries_to_save_entity_using_save_or_fail]]></code>
<code><![CDATA[it_uses_query_to_select_entity]]></code>
</PossiblyUnusedMethod>
<UndefinedVariable>
<code><![CDATA[$userOne]]></code>
<code><![CDATA[$userOne]]></code>
<code><![CDATA[$userOne]]></code>
<code><![CDATA[$userTwo]]></code>
<code><![CDATA[$userTwo]]></code>
</UndefinedVariable>
<UnusedVariable>
<code><![CDATA[$entityManager]]></code>
</UnusedVariable>
</file>
<file src="tests/src/Functional/Bridge/Spiral/Bootloader/ActiveRecordBootloaderTest.php">
<PossiblyUnusedMethod>
<code><![CDATA[it_gets_container_from_static_origin_class]]></code>
</PossiblyUnusedMethod>
</file>
<file src="tests/src/Functional/DatabaseTestCase.php">
<PossiblyUnusedProperty>
<code><![CDATA[$orm]]></code>
</PossiblyUnusedProperty>
</file>
<file src="tests/src/Functional/FacadeTest.php">
<PossiblyUnusedMethod>
<code><![CDATA[it_fails_to_get_orm_from_facade_when_container_is_not_set]]></code>
<code><![CDATA[it_gets_entity_manager_from_facade]]></code>
<code><![CDATA[it_gets_orm_from_facade_when_container_has_orm]]></code>
<code><![CDATA[it_throws_exception_when_container_does_not_have_orm]]></code>
<code><![CDATA[it_throws_exception_when_container_does_not_have_orm_set]]></code>
</PossiblyUnusedMethod>
</file>
<file src="tests/src/Functional/Loggable.php">
<PossiblyUnusedMethod>
<code><![CDATA[disableProfiling]]></code>
</PossiblyUnusedMethod>
<UndefinedInterfaceMethod>
<code><![CDATA[setLogger]]></code>
</UndefinedInterfaceMethod>
</file>
<file src="tests/src/Functional/Query/ActiveQueryTest.php">
<PossiblyUnusedMethod>
<code><![CDATA[it_gets_role_from_query]]></code>
</PossiblyUnusedMethod>
</file>
<file src="tests/src/Functional/Repository/ActiveRepositoryTest.php">
<MissingTemplateParam>
<code><![CDATA[class() extends ActiveRepository {]]></code>
</MissingTemplateParam>
<PossiblyUnusedMethod>
<code><![CDATA[it_extends_repository_constructor]]></code>
<code><![CDATA[it_fetches_one_entity]]></code>
<code><![CDATA[it_fetches_one_entity_by_pk]]></code>
<code><![CDATA[it_uses_custom_repository_with_active_query]]></code>
</PossiblyUnusedMethod>
</file>
<file src="tests/src/Functional/TestLogger.php">
<ImplicitToStringCast>
<code><![CDATA[$message]]></code>
<code><![CDATA[$message]]></code>
</ImplicitToStringCast>
<PossiblyUnusedMethod>
<code><![CDATA[countReadQueries]]></code>
<code><![CDATA[countWriteQueries]]></code>
</PossiblyUnusedMethod>
</file>
</files>
6 changes: 0 additions & 6 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@

<projectFiles>
<directory name="src/"/>
<directory name="tests/src/"/>
<ignoreFiles>
<directory name="tests/src/Arch/"/>
</ignoreFiles>
<file name=".php-cs-fixer.dist.php"/>
<file name="rector.php"/>
<ignoreFiles>
<directory name="vendor/"/>
</ignoreFiles>
Expand Down
32 changes: 29 additions & 3 deletions src/ActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
use Cycle\ActiveRecord\Exception\Transaction\TransactionException;
use Cycle\ActiveRecord\Internal\TransactionFacade;
use Cycle\ActiveRecord\Query\ActiveQuery;
use Cycle\Database\DatabaseInterface;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\Exception\RunnerException;
use Cycle\ORM\ORMInterface;
use Cycle\ORM\RepositoryInterface;

Expand Down Expand Up @@ -35,7 +38,7 @@
* @param array<non-empty-string, mixed> $data An associative array where keys are property names
* and values are property values.
*/
public static function make(array $data): static

Check failure on line 41 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

PossiblyUnusedMethod

src/ActiveRecord.php:41:28: PossiblyUnusedMethod: Cannot find any calls to method Cycle\ActiveRecord\ActiveRecord::make (see https://psalm.dev/087)
{
return self::getOrm()->make(static::class, $data);
}
Expand All @@ -43,7 +46,7 @@
/**
* Find a single record based on the given primary key.
*/
final public static function findByPK(mixed $primaryKey): ?static

Check failure on line 49 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

PossiblyUnusedMethod

src/ActiveRecord.php:49:34: PossiblyUnusedMethod: Cannot find any calls to method Cycle\ActiveRecord\ActiveRecord::findByPK (see https://psalm.dev/087)
{
return static::query()->wherePK($primaryKey)->fetchOne();
}
Expand All @@ -53,7 +56,7 @@
*
* @note Limit of 1 will be added to the query.
*/
final public static function findOne(array $scope = []): ?static

Check failure on line 59 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

PossiblyUnusedMethod

src/ActiveRecord.php:59:34: PossiblyUnusedMethod: Cannot find any calls to method Cycle\ActiveRecord\ActiveRecord::findOne (see https://psalm.dev/087)
{
return static::query()->fetchOne($scope);
}
Expand All @@ -63,7 +66,7 @@
*
* @return iterable<static>
*/
final public static function findAll(array $scope = []): iterable

Check failure on line 69 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

PossiblyUnusedMethod

src/ActiveRecord.php:69:34: PossiblyUnusedMethod: Cannot find any calls to method Cycle\ActiveRecord\ActiveRecord::findAll (see https://psalm.dev/087)
{
return static::query()->where($scope)->fetchAll();
}
Expand All @@ -74,18 +77,41 @@
* All the ActiveRecord write operations within the callback will be registered
* using the Entity Manager without being executed until the end of the callback.
*
* @note DBAL operations will not be executed within the transaction. Use {@see self::transact()} for that.
*
* @template TResult
* @param callable(): TResult $callback
* @param callable(EntityManagerInterface): TResult $callback
* @return TResult
*
* @throws TransactionException
* @throws RunnerException
* @throws \Throwable
*/
public static function transact(
public static function groupActions(

Check failure on line 90 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

PossiblyUnusedMethod

src/ActiveRecord.php:90:28: PossiblyUnusedMethod: Cannot find any calls to method Cycle\ActiveRecord\ActiveRecord::groupActions (see https://psalm.dev/087)
callable $callback,
TransactionMode $mode = TransactionMode::OpenNew,
): mixed {
return TransactionFacade::transact($callback, $mode);
return TransactionFacade::groupOrmActions($callback, $mode);

Check failure on line 94 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Parameter #1 $callback of static method Cycle\ActiveRecord\Internal\TransactionFacade::groupOrmActions() expects callable(): TResult, callable(Cycle\ORM\EntityManagerInterface): TResult given.

Check failure on line 94 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

InvalidArgument

src/ActiveRecord.php:94:51: InvalidArgument: Argument 1 of Cycle\ActiveRecord\Internal\TransactionFacade::groupOrmActions expects callable():mixed, but callable(Cycle\ORM\EntityManagerInterface):TResult:fn-cycle\activerecord\activerecord::groupactions as mixed provided (see https://psalm.dev/004)
}

/**
* Open a new DB transaction and execute the callback within it.
*
* All the DBAL operations within the callback will be executed within a single transaction.
* If an exception is thrown within the callback, the transaction will be rolled back.
* If the callback returns a value, the transaction will be committed.
*
* @template TResult
* @param callable(DatabaseInterface): TResult $callback
* @return TResult
*
* @throws TransactionException
* @throws \Throwable
*/
public static function transact(

Check failure on line 111 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

PossiblyUnusedMethod

src/ActiveRecord.php:111:28: PossiblyUnusedMethod: Cannot find any calls to method Cycle\ActiveRecord\ActiveRecord::transact (see https://psalm.dev/087)
callable $callback,
): mixed {
return TransactionFacade::transact($callback, static::class === self::class ? null : static::class);

Check failure on line 114 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Parameter #1 $callback of static method Cycle\ActiveRecord\Internal\TransactionFacade::transact() expects callable(): TResult, callable(Cycle\Database\DatabaseInterface): TResult given.

Check failure on line 114 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

InvalidArgument

src/ActiveRecord.php:114:44: InvalidArgument: Argument 1 of Cycle\ActiveRecord\Internal\TransactionFacade::transact expects callable():mixed, but callable(Cycle\Database\DatabaseInterface):TResult:fn-cycle\activerecord\activerecord::transact as mixed provided (see https://psalm.dev/004)
}

/**
Expand All @@ -98,7 +124,7 @@
return new ActiveQuery(static::class);
}

public static function getRepository(): RepositoryInterface

Check failure on line 127 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

PossiblyUnusedMethod

src/ActiveRecord.php:127:28: PossiblyUnusedMethod: Cannot find any calls to method Cycle\ActiveRecord\ActiveRecord::getRepository (see https://psalm.dev/087)
{
return self::getOrm()->getRepository(static::class);
}
Expand All @@ -106,7 +132,7 @@
/**
* Persist the entity.
*/
final public function save(bool $cascade = true): bool

Check failure on line 135 in src/ActiveRecord.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

PossiblyUnusedMethod

src/ActiveRecord.php:135:27: PossiblyUnusedMethod: Cannot find any calls to method Cycle\ActiveRecord\ActiveRecord::save (see https://psalm.dev/087)
{
$transacting = TransactionFacade::getEntityManager();
if ($transacting === null) {
Expand Down
15 changes: 11 additions & 4 deletions src/Facade.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Cycle\ActiveRecord;

use Cycle\ActiveRecord\Exception\ConfigurationException;
use Cycle\Database\DatabaseManager;
use Cycle\ORM\EntityManager;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\ORMInterface;
Expand All @@ -22,32 +23,38 @@

private static ?ContainerInterface $container = null;

private static ?DatabaseManager $dbal = null;

public static function setContainer(ContainerInterface $container): void
{
self::$container = $container;
}

public static function setOrm(ORMInterface $orm): void
/**
* @throws ConfigurationException
*/
public static function getOrm(): ORMInterface
{
self::$orm = $orm;
return self::$orm ??= self::getFromContainer(ORMInterface::class);

Check warning on line 38 in src/Facade.php

View workflow job for this annotation

GitHub Actions / mutation-testing (ubuntu-latest, 8.2, locked)

Escaped Mutant for Mutator "AssignCoalesce": --- Original +++ New @@ @@ */ public static function getOrm() : ORMInterface { - return self::$orm ??= self::getFromContainer(ORMInterface::class); + return self::$orm = self::getFromContainer(ORMInterface::class); } /** * @throws ConfigurationException
}

/**
* @throws ConfigurationException
*/
public static function getOrm(): ORMInterface
public static function getDatabaseManager(): DatabaseManager
{
return self::$orm ??= self::getFromContainer(ORMInterface::class);
return self::$dbal ??= self::getFromContainer(DatabaseManager::class);

Check warning on line 46 in src/Facade.php

View workflow job for this annotation

GitHub Actions / mutation-testing (ubuntu-latest, 8.2, locked)

Escaped Mutant for Mutator "AssignCoalesce": --- Original +++ New @@ @@ */ public static function getDatabaseManager() : DatabaseManager { - return self::$dbal ??= self::getFromContainer(DatabaseManager::class); + return self::$dbal = self::getFromContainer(DatabaseManager::class); } public static function getEntityManager() : EntityManagerInterface {
}

public static function getEntityManager(): EntityManagerInterface
{
return self::$entityManager ??= new EntityManager(self::getOrm());

Check warning on line 51 in src/Facade.php

View workflow job for this annotation

GitHub Actions / mutation-testing (ubuntu-latest, 8.2, locked)

Escaped Mutant for Mutator "AssignCoalesce": --- Original +++ New @@ @@ } public static function getEntityManager() : EntityManagerInterface { - return self::$entityManager ??= new EntityManager(self::getOrm()); + return self::$entityManager = new EntityManager(self::getOrm()); } public static function reset() : void {
}

public static function reset(): void
{
self::$orm = null;
self::$dbal = null;
self::$entityManager = null;
self::$container = null;
}
Expand All @@ -67,7 +74,7 @@
self::$container === null and throw new ConfigurationException(
\sprintf(
'Container has not been set. Please set the container first using %s method.',
self::class . '::setContainer()',

Check warning on line 77 in src/Facade.php

View workflow job for this annotation

GitHub Actions / mutation-testing (ubuntu-latest, 8.2, locked)

Escaped Mutant for Mutator "Concat": --- Original +++ New @@ @@ private static function getFromContainer(string $class) : object { // Check if container is set - self::$container === null and throw new ConfigurationException(\sprintf('Container has not been set. Please set the container first using %s method.', self::class . '::setContainer()')); + self::$container === null and throw new ConfigurationException(\sprintf('Container has not been set. Please set the container first using %s method.', '::setContainer()' . self::class)); // Pull service from container try { return self::$container->get($class);

Check warning on line 77 in src/Facade.php

View workflow job for this annotation

GitHub Actions / mutation-testing (ubuntu-latest, 8.2, locked)

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ private static function getFromContainer(string $class) : object { // Check if container is set - self::$container === null and throw new ConfigurationException(\sprintf('Container has not been set. Please set the container first using %s method.', self::class . '::setContainer()')); + self::$container === null and throw new ConfigurationException(\sprintf('Container has not been set. Please set the container first using %s method.', '::setContainer()')); // Pull service from container try { return self::$container->get($class);

Check warning on line 77 in src/Facade.php

View workflow job for this annotation

GitHub Actions / mutation-testing (ubuntu-latest, 8.2, locked)

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ private static function getFromContainer(string $class) : object { // Check if container is set - self::$container === null and throw new ConfigurationException(\sprintf('Container has not been set. Please set the container first using %s method.', self::class . '::setContainer()')); + self::$container === null and throw new ConfigurationException(\sprintf('Container has not been set. Please set the container first using %s method.', self::class)); // Pull service from container try { return self::$container->get($class);
),
);

Expand Down
31 changes: 29 additions & 2 deletions src/Internal/TransactionFacade.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
use Cycle\ActiveRecord\Facade;
use Cycle\ActiveRecord\TransactionMode;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\Service\SourceProviderInterface;
use Cycle\ORM\Transaction\Runner;

/**
* @internal
*/
final class TransactionFacade
{
private static ?EntityManagerInterface $em = null;
Expand All @@ -27,7 +31,7 @@
* @throws TransactionException
* @throws \Throwable
*/
public static function transact(
public static function groupOrmActions(
callable $callback,
TransactionMode $mode = TransactionMode::OpenNew,
): mixed {
Expand All @@ -43,11 +47,34 @@
self::$em = Facade::getEntityManager();

try {
$result = $callback();
$result = $callback(self::$em);

Check failure on line 50 in src/Internal/TransactionFacade.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Callable callable(): TResult invoked with 1 parameter, 0 required.
self::$em->run(true, $runner);
return $result;
} finally {
self::$em = null;
}
}

/**
* @template TResult
* @param callable(): TResult $callback
* @param class-string|null $entity If null, the default database will be used.
* @return TResult
*
* @throws TransactionException
* @throws \Throwable
*/
public static function transact(
callable $callback,
?string $entity,
): mixed {
$dbal = $entity === null
? Facade::getDatabaseManager()->database()
: Facade::getOrm()
->getService(SourceProviderInterface::class)
->getSource($entity)
->getDatabase();

return $dbal->transaction($callback);
}
}
9 changes: 6 additions & 3 deletions src/TransactionMode.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@

namespace Cycle\ActiveRecord;

use Cycle\ORM\Exception\RunnerException;

enum TransactionMode
{
/**
* Do nothing with transactions.
*
* @see \Cycle\ORM\Transaction\Runner::innerTransaction() with non-strict mode.
* @see \Cycle\ORM\Transaction\Runner::outerTransaction() with non-strict mode.
*/
case Ignore;

/**
* The currently opened transaction will be used. If no transaction is opened, a new one will be created.
* The currently opened transaction will be used. If no transaction is opened, a {@see RunnerException}
* exception will be thrown.
*
* @see \Cycle\ORM\Transaction\Runner::innerTransaction() with strict mode.
* @see \Cycle\ORM\Transaction\Runner::outerTransaction() with strict mode.
*/
case Current;

Expand Down
Loading
Loading