Skip to content

Commit 89ee4b2

Browse files
committed
feat: Add Yii3, Laravel, and Spiral integrations
1 parent 16c70f6 commit 89ee4b2

7 files changed

Lines changed: 245 additions & 1 deletion

File tree

composer.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,23 @@
1515
"cycle/orm": "^2.18"
1616
},
1717
"require-dev": {
18+
"buggregator/trap": "^1.16",
1819
"cycle/skills": "1.x-dev",
20+
"illuminate/container": "^11 || ^12 || ^13",
21+
"illuminate/support": "^11 || ^12 || ^13",
1922
"infection/infection": ">=0.32.6",
2023
"llm/skills": "^1.7",
24+
"spiral/boot": "^3.12",
25+
"spiral/cycle-bridge": "^2.13",
2126
"testo/bridge-infection": "^0.1.6",
2227
"testo/testo": "^0.10.26",
23-
"vimeo/psalm": "^7"
28+
"vimeo/psalm": "^7",
29+
"yiisoft/di": "^1.3"
30+
},
31+
"suggest": {
32+
"spiral/cycle-bridge": "Provides integration with the Spiral Framework via TransactionBootloader",
33+
"wayofdev/laravel-cycle-orm-adapter": "Provides Cycle ORM integration for Laravel (TransactionServiceProvider)",
34+
"yiisoft/yii-cycle": "Provides Cycle ORM integration for the Yii Framework"
2435
},
2536
"autoload": {
2637
"psr-4": {
@@ -54,6 +65,21 @@
5465
"infection --ansi --configuration=infection.json --logger-github --threads=max --show-mutations=0 --min-msi=50"
5566
]
5667
},
68+
"extra": {
69+
"laravel": {
70+
"providers": [
71+
"Cycle\\Transaction\\Bridge\\Laravel\\Providers\\TransactionServiceProvider"
72+
]
73+
},
74+
"config-plugin": {
75+
"di": "src/Bridge/Yii3/config/di.php"
76+
},
77+
"spiral": {
78+
"bootloaders": [
79+
"Cycle\\Transaction\\Bridge\\Spiral\\Bootloader\\TransactionBootloader"
80+
]
81+
}
82+
},
5783
"minimum-stability": "dev",
5884
"prefer-stable": true,
5985
"config": {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Transaction\Bridge\Laravel\Providers;
6+
7+
use Cycle\Transaction\Internal\TransactionImpl;
8+
use Cycle\Transaction\Transaction;
9+
use Illuminate\Support\ServiceProvider;
10+
11+
final class TransactionServiceProvider extends ServiceProvider
12+
{
13+
#[\Override]
14+
public function register(): void
15+
{
16+
// TransactionImpl is autowired from the container's ORMInterface and DatabaseProviderInterface,
17+
// which the Cycle ORM Laravel adapter is expected to bind.
18+
$this->app->singleton(Transaction::class, TransactionImpl::class);
19+
}
20+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Transaction\Bridge\Spiral\Bootloader;
6+
7+
use Cycle\Transaction\Internal\TransactionImpl;
8+
use Cycle\Transaction\Transaction;
9+
use Spiral\Boot\Bootloader\Bootloader;
10+
11+
final class TransactionBootloader extends Bootloader
12+
{
13+
protected const SINGLETONS = [
14+
Transaction::class => TransactionImpl::class,
15+
];
16+
}

src/Bridge/Yii3/config/di.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Cycle\Transaction\Internal\TransactionImpl;
6+
use Cycle\Transaction\Transaction;
7+
8+
/**
9+
* Yii3 DI definitions. {@see TransactionImpl} is autowired from the container's ORMInterface and
10+
* DatabaseProviderInterface, which the Yii Cycle integration is expected to register.
11+
*/
12+
return [
13+
Transaction::class => TransactionImpl::class,
14+
];
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Transaction\Tests\Unit;
6+
7+
use Cycle\Database\DatabaseProviderInterface;
8+
use Cycle\ORM\ORMInterface;
9+
use Cycle\Transaction\Bridge\Spiral\Bootloader\TransactionBootloader;
10+
use Cycle\Transaction\Internal\TransactionImpl;
11+
use Cycle\Transaction\Tests\Fixtures\SqliteEnvironment;
12+
use Cycle\Transaction\Tests\Fixtures\TestEnvironment;
13+
use Cycle\Transaction\Transaction;
14+
use Spiral\Core\Container;
15+
use Testo\Assert;
16+
use Testo\Codecov\Covers;
17+
use Testo\Lifecycle\BeforeTest;
18+
use Testo\Test;
19+
20+
/**
21+
* Unit tests for the Spiral {@see TransactionBootloader}.
22+
*
23+
* Rather than booting a full Spiral application, this checks the bootloader's contract directly: it
24+
* binds {@see Transaction} as a singleton resolving to {@see TransactionImpl}, given an
25+
* {@see ORMInterface} and a {@see DatabaseProviderInterface} are available in the container.
26+
*/
27+
#[Test]
28+
#[Covers(TransactionBootloader::class)]
29+
final class TransactionBootloaderTest
30+
{
31+
private TestEnvironment $env;
32+
33+
#[BeforeTest]
34+
public function setUp(): void
35+
{
36+
$this->env = SqliteEnvironment::create();
37+
}
38+
39+
public function itResolvesTransactionAsASingleton(): void
40+
{
41+
$container = new Container();
42+
$container->bindSingleton(ORMInterface::class, $this->env->orm);
43+
$container->bindSingleton(DatabaseProviderInterface::class, $this->env->dbal);
44+
45+
// Apply the bootloader's bindings exactly as Spiral would.
46+
foreach ((new TransactionBootloader())->defineSingletons() as $alias => $resolver) {
47+
$container->bindSingleton($alias, $resolver);
48+
}
49+
50+
$transaction = $container->get(Transaction::class);
51+
52+
Assert::instanceOf($transaction, TransactionImpl::class);
53+
// A singleton must hand back the very same instance on every resolution.
54+
Assert::same($container->get(Transaction::class), $transaction);
55+
}
56+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Transaction\Tests\Unit;
6+
7+
use Cycle\Database\DatabaseProviderInterface;
8+
use Cycle\ORM\ORMInterface;
9+
use Cycle\Transaction\Bridge\Laravel\Providers\TransactionServiceProvider;
10+
use Cycle\Transaction\Internal\TransactionImpl;
11+
use Cycle\Transaction\Tests\Fixtures\SqliteEnvironment;
12+
use Cycle\Transaction\Tests\Fixtures\TestEnvironment;
13+
use Cycle\Transaction\Transaction;
14+
use Illuminate\Container\Container;
15+
use Testo\Assert;
16+
use Testo\Codecov\Covers;
17+
use Testo\Lifecycle\BeforeTest;
18+
use Testo\Test;
19+
20+
/**
21+
* Unit tests for the Laravel {@see TransactionServiceProvider}.
22+
*
23+
* Rather than booting a full Laravel application, this checks the provider's contract directly: it
24+
* binds {@see Transaction} as a singleton resolving to {@see TransactionImpl}, given an
25+
* {@see ORMInterface} and a {@see DatabaseProviderInterface} are available in the container.
26+
*/
27+
#[Test]
28+
#[Covers(TransactionServiceProvider::class)]
29+
final class TransactionServiceProviderTest
30+
{
31+
private TestEnvironment $env;
32+
33+
#[BeforeTest]
34+
public function setUp(): void
35+
{
36+
$this->env = SqliteEnvironment::create();
37+
}
38+
39+
public function itResolvesTransactionAsASingleton(): void
40+
{
41+
$container = new Container();
42+
$container->instance(ORMInterface::class, $this->env->orm);
43+
$container->instance(DatabaseProviderInterface::class, $this->env->dbal);
44+
45+
// ServiceProvider's docblock types $app as the Application contract, but it only uses the
46+
// container's bind/singleton methods, which Illuminate\Container\Container provides.
47+
/** @psalm-suppress InvalidArgument */
48+
(new TransactionServiceProvider($container))->register();
49+
50+
$transaction = $container->make(Transaction::class);
51+
52+
Assert::instanceOf($transaction, TransactionImpl::class);
53+
// A singleton must hand back the very same instance on every resolution.
54+
Assert::same($container->make(Transaction::class), $transaction);
55+
}
56+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Transaction\Tests\Unit;
6+
7+
use Cycle\Database\DatabaseProviderInterface;
8+
use Cycle\ORM\ORMInterface;
9+
use Cycle\Transaction\Internal\TransactionImpl;
10+
use Cycle\Transaction\Tests\Fixtures\SqliteEnvironment;
11+
use Cycle\Transaction\Tests\Fixtures\TestEnvironment;
12+
use Cycle\Transaction\Transaction;
13+
use Testo\Assert;
14+
use Testo\Lifecycle\BeforeTest;
15+
use Testo\Test;
16+
use Yiisoft\Di\Container;
17+
use Yiisoft\Di\ContainerConfig;
18+
19+
/**
20+
* Unit tests for the Yii3 DI configuration (`src/Bridge/Yii3/config/di.php`).
21+
*
22+
* The config is loaded into a real Yii container together with stub ORMInterface and
23+
* DatabaseProviderInterface services, and we assert that {@see Transaction} resolves to a shared
24+
* {@see TransactionImpl} instance.
25+
*/
26+
#[Test]
27+
final class TransactionYiiConfigTest
28+
{
29+
private TestEnvironment $env;
30+
31+
#[BeforeTest]
32+
public function setUp(): void
33+
{
34+
$this->env = SqliteEnvironment::create();
35+
}
36+
37+
public function itResolvesTransactionAsASharedService(): void
38+
{
39+
/** @var array<string, mixed> $definitions */
40+
$definitions = require \dirname(__DIR__, 2) . '/src/Bridge/Yii3/config/di.php';
41+
42+
$container = new Container(
43+
ContainerConfig::create()->withDefinitions([
44+
ORMInterface::class => $this->env->orm,
45+
DatabaseProviderInterface::class => $this->env->dbal,
46+
...$definitions,
47+
]),
48+
);
49+
50+
$transaction = $container->get(Transaction::class);
51+
52+
Assert::instanceOf($transaction, TransactionImpl::class);
53+
// The container must return the same shared instance on every resolution.
54+
Assert::same($container->get(Transaction::class), $transaction);
55+
}
56+
}

0 commit comments

Comments
 (0)