diff --git a/composer.json b/composer.json index fad8c7e..9d94d68 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,8 @@ "php": ">=8.1", "cycle/database": "^2.11", "cycle/orm": "^2.7", - "psr/container": "^2.0" + "psr/container": "^2.0", + "yiisoft/injector": "^1.2" }, "require-dev": { "buggregator/trap": "^1.5", diff --git a/composer.lock b/composer.lock index e2e8662..bf490e6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "39a64bf69a855ddea1139bf99c075406", + "content-hash": "68d723ec5ccf2cd6e9ca2ec0b17ffca4", "packages": [ { "name": "brick/math", @@ -4143,6 +4143,76 @@ } ], "time": "2025-04-30T23:37:27+00:00" + }, + { + "name": "yiisoft/injector", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/injector.git", + "reference": "0dc0127a7542341bdaabda7b85204e992938b83e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/injector/zipball/0dc0127a7542341bdaabda7b85204e992938b83e", + "reference": "0dc0127a7542341bdaabda7b85204e992938b83e", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "require-dev": { + "maglnet/composer-require-checker": "^3.8|^4.2", + "phpbench/phpbench": "^1.1", + "phpunit/phpunit": "^9.5", + "psr/container": "^1.0|^2.0", + "rector/rector": "^0.18.12", + "roave/infection-static-analysis-plugin": "^1.16", + "spatie/phpunit-watcher": "^1.23", + "vimeo/psalm": "^4.30|^5.7", + "yiisoft/test-support": "^1.2" + }, + "suggest": { + "psr/container": "For automatic resolving of dependencies" + }, + "type": "library", + "autoload": { + "psr-4": { + "Yiisoft\\Injector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR-11 compatible injector. Executes a callable and makes an instances by injecting dependencies from a given DI container.", + "homepage": "https://www.yiiframework.com/", + "keywords": [ + "PSR-11", + "dependency injection", + "di", + "injector", + "reflection" + ], + "support": { + "chat": "https://t.me/yii3en", + "forum": "https://www.yiiframework.com/forum/", + "irc": "irc://irc.freenode.net/yii", + "issues": "https://github.com/yiisoft/injector/issues?state=open", + "source": "https://github.com/yiisoft/injector", + "wiki": "https://www.yiiframework.com/wiki/" + }, + "funding": [ + { + "url": "https://github.com/yiisoft", + "type": "github" + }, + { + "url": "https://opencollective.com/yiisoft", + "type": "open_collective" + } + ], + "time": "2023-12-20T09:39:03+00:00" } ], "packages-dev": [ @@ -11202,76 +11272,6 @@ } ], "time": "2021-10-26T21:43:25+00:00" - }, - { - "name": "yiisoft/injector", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/injector.git", - "reference": "0dc0127a7542341bdaabda7b85204e992938b83e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/injector/zipball/0dc0127a7542341bdaabda7b85204e992938b83e", - "reference": "0dc0127a7542341bdaabda7b85204e992938b83e", - "shasum": "" - }, - "require": { - "php": "^7.4|^8.0" - }, - "require-dev": { - "maglnet/composer-require-checker": "^3.8|^4.2", - "phpbench/phpbench": "^1.1", - "phpunit/phpunit": "^9.5", - "psr/container": "^1.0|^2.0", - "rector/rector": "^0.18.12", - "roave/infection-static-analysis-plugin": "^1.16", - "spatie/phpunit-watcher": "^1.23", - "vimeo/psalm": "^4.30|^5.7", - "yiisoft/test-support": "^1.2" - }, - "suggest": { - "psr/container": "For automatic resolving of dependencies" - }, - "type": "library", - "autoload": { - "psr-4": { - "Yiisoft\\Injector\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR-11 compatible injector. Executes a callable and makes an instances by injecting dependencies from a given DI container.", - "homepage": "https://www.yiiframework.com/", - "keywords": [ - "PSR-11", - "dependency injection", - "di", - "injector", - "reflection" - ], - "support": { - "chat": "https://t.me/yii3en", - "forum": "https://www.yiiframework.com/forum/", - "irc": "irc://irc.freenode.net/yii", - "issues": "https://github.com/yiisoft/injector/issues?state=open", - "source": "https://github.com/yiisoft/injector", - "wiki": "https://www.yiiframework.com/wiki/" - }, - "funding": [ - { - "url": "https://github.com/yiisoft", - "type": "github" - }, - { - "url": "https://opencollective.com/yiisoft", - "type": "open_collective" - } - ], - "time": "2023-12-20T09:39:03+00:00" } ], "aliases": [], diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 325ad1b..eb633d9 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -4,9 +4,9 @@ - - - + + getSchema()->define(static::class, SchemaInterface::TABLE)]]> + @@ -19,8 +19,8 @@ - + - + diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index e65a2bc..e08fed7 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -12,6 +12,7 @@ use Cycle\ORM\Exception\RunnerException; use Cycle\ORM\ORMInterface; use Cycle\ORM\RepositoryInterface; +use Cycle\ORM\SchemaInterface; /** * A base class for entities that are managed by the ORM. @@ -19,6 +20,16 @@ */ abstract class ActiveRecord { + /** + * Get the table name associated with the entity. + * + * @return non-empty-string + */ + final public static function tableName(): string + { + return static::getOrm()->getSchema()->define(static::class, SchemaInterface::TABLE); + } + /** * Create a new entity instance with the given data. * It is preferable to use this method instead of the constructor because @@ -142,8 +153,14 @@ final public static function groupActions( * will be used. * * @template TResult - * @param callable(DatabaseInterface, EntityManagerInterface): TResult $callback Note that the provided - * Entity Manager doesn't collect operations and executes them right away in the opened transaction. + * @param callable(): TResult $callback A function that may accept parameters of the following types in any order: + * - {@see DatabaseInterface} + * - {@see EntityManagerInterface} + * - {@see ORMInterface} + * - {@see SchemaInterface} + * @psalm-param callable(...): TResult $callback + * @note that the provided Entity Manager doesn't collect operations and executes them right away in the opened transaction. + * * @return TResult * * @throws TransactionException diff --git a/src/Internal/TransactionFacade.php b/src/Internal/TransactionFacade.php index 4f61afa..854cbb8 100644 --- a/src/Internal/TransactionFacade.php +++ b/src/Internal/TransactionFacade.php @@ -12,6 +12,7 @@ use Cycle\ORM\Service\SourceProviderInterface; use Cycle\ORM\Transaction\Runner; use Cycle\ORM\Transaction\UnitOfWork; +use Yiisoft\Injector\Injector; /** * @internal @@ -65,8 +66,9 @@ public static function groupOrmActions( /** * @template TResult - * @param callable(DatabaseInterface, EntityManagerInterface): TResult $callback + * @param callable(): TResult $callback * @param class-string|null $entity If null, the default database will be used. + * @psalm-param callable(...): TResult $callback * @return TResult * * @throws TransactionException @@ -86,12 +88,13 @@ public static function transact( return $dbal->transaction(static function (DatabaseInterface $db) use ($callback): mixed { $previous = self::$em; try { + $orm = Facade::getOrm(); self::$em = $em = new EntityManager( - static fn(): UnitOfWork => new UnitOfWork(Facade::getOrm(), Runner::outerTransaction(strict: true)), + static fn(): UnitOfWork => new UnitOfWork($orm, Runner::outerTransaction(strict: true)), autoExecute: true, ); - return $callback($db, $em); + return (new Injector())->invoke($callback, [$db, $em, $orm, $orm->getHeap(), $orm->getSchema()]); } finally { self::$em = $previous; } diff --git a/src/Repository/ActiveRepository.php b/src/Repository/ActiveRepository.php index a170ace..782680a 100644 --- a/src/Repository/ActiveRepository.php +++ b/src/Repository/ActiveRepository.php @@ -51,6 +51,8 @@ public function __construct(string $role) * * @note Limit of 1 will be added to the query. * + * @param string|int|non-empty-list|non-empty-array|object $id + * * @return TEntity|null */ public function findByPK(mixed $id): ?object diff --git a/tests/src/Functional/ActiveRecordTest.php b/tests/src/Functional/ActiveRecordTest.php index 0314909..d7450b0 100644 --- a/tests/src/Functional/ActiveRecordTest.php +++ b/tests/src/Functional/ActiveRecordTest.php @@ -12,6 +12,9 @@ use Cycle\Database\DatabaseInterface; use Cycle\ORM\EntityManagerInterface; use Cycle\ORM\Exception\RunnerException; +use Cycle\ORM\Heap\HeapInterface; +use Cycle\ORM\ORMInterface; +use Cycle\ORM\SchemaInterface; use Cycle\ORM\Select\Repository; use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use PHPUnit\Framework\Attributes\Test; @@ -283,9 +286,30 @@ public function it_runs_transaction_with_orm_actions(): void self::assertSame($savedUserFour->name, $user4->name); } + #[Test] + public function transact_method_resolves_parameters(): void + { + $ars = User::transact(static fn( + SchemaInterface $schema, + EntityManagerInterface $em, + ORMInterface $orm, + HeapInterface $heap, + DatabaseInterface $dbal, + ): array => \func_get_args()); + + self::assertIsArray($ars); + } + #[Test] public function query_method_returns_ActiveQuery(): void { self::assertInstanceOf(ActiveQuery::class, Identity::query()); } + + #[Test] + public function get_table_name(): void + { + self::assertSame('user', User::tableName()); + self::assertSame('user_identity', Identity::tableName()); + } }