spiral/cycle-bridge
Bridge package integrating Cycle ORM v2 with Spiral Framework 3+. Provides ORM configuration and runtime wiring for Spiral apps using PDO database drivers on PHP 8.1+.
Installation Add the package via Composer:
composer require spiral/cycle-bridge
Register the bridge in your config/app.php under providers:
Spiral\CycleBridge\CycleBridgeProvider::class,
Basic Setup
Configure Cycle ORM in config/cycle.php (if not already present):
return [
'dsn' => env('DATABASE_DSN', 'mysql://user:pass@localhost/db'),
'orm' => [
'default' => [
'entityManager' => \Cycle\ORM\EntityManager::class,
'connection' => \Cycle\Database\Connection\Connection::class,
],
],
];
First Use Case: Querying Entities with Relations Define entities with relations and leverage the new Relations Bulk Loader binding:
use Spiral\CycleBridge\CycleBridge;
use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Relation;
#[Entity]
class User
{
#[Column(type: 'primary')]
public int $id;
#[Column(type: 'string')]
public string $name;
#[Relation(target: Post::class, type: 'one-to-many')]
public array $posts;
}
#[Entity]
class Post
{
#[Column(type: 'primary')]
public int $id;
#[Column(type: 'string')]
public string $title;
}
// In a Spiral controller:
public function index(CycleBridge $cycle)
{
// Fetch users with eagerly loaded posts using Relations Bulk Loader
$users = $cycle->getEntityManager()
->getRepository(User::class)
->findAll()
->with('posts') // Bulk loads relations
->fetchAll();
return $users;
}
Dependency Injection
Inject CycleBridge into Spiral handlers (controllers, commands) to access the ORM:
public function __construct(private CycleBridge $cycle) {}
Repository Pattern with Relations Use Cycle’s repositories for CRUD operations, now optimized for bulk relation loading:
// Fetch a user with all related posts in a single query
$user = $this->cycle->getRepository(User::class)
->find(1)
->with('posts')
->fetch();
Transactions Wrap operations in transactions for atomicity:
$this->cycle->getEntityManager()->transactional(function () {
$user = new User();
$user->name = 'John';
$this->cycle->getRepository(User::class)->persist($user);
});
Query Building with Relations Leverage Cycle’s query builder for complex queries, including relation filtering:
$query = $this->cycle->getEntityManager()
->getRepository(User::class)
->createQueryBuilder()
->where('name', '=', 'John')
->with('posts') // Bulk loads posts for all matching users
->fetchAll();
Event Handling
Use Cycle’s lifecycle events (e.g., prePersist, postLoad) via Spiral’s event system:
$this->cycle->getEntityManager()->addListener(
new YourEventListener()
);
vendor:publish --provider="Cycle\Migrations\MigrationsProvider"
CycleBridge in unit tests, including relation loading:
$this->bean->provide(CycleBridge::class, function () {
$mock = $this->mock(CycleBridge::class);
$mock->getEntityManager()
->getRepository(User::class)
->find(1)
->with('posts')
->willReturn($this->mock(User::class));
return $mock;
});
config/cycle.php for performance, especially with bulk-loaded relations.Connection Configuration
config/cycle.php matches your database DSN. Test with:
php artisan cycle:migrate
cycle:debug command to inspect connections.Entity Mapping
#[Entity], #[Column], #[Relation]). Forgetting them causes Cycle\ORM\Exception\MappingException.cycle:generate to auto-generate mappings (if using cycle-orm/annotations).Transaction Isolation
config/cycle.php:
'orm' => [
'default' => [
'transaction' => [
'isolation' => \PDO::TRANSACTION_READ_COMMITTED,
],
],
],
Relations Bulk Loader
.with('relation') to trigger bulk loading. Without it, relations are loaded lazily.Schema Changes
php artisan cycle:migrate
config/cycle.php:
'debug' => env('APP_DEBUG', false),
Cycle\Database\Connection\Logger to log SQL, including bulk-loaded relations:
$this->cycle->getConnection()->addLogger(new \Cycle\Database\Connection\Logger\FileLogger('/tmp/cycle.log'));
PDOException: Check DSN format (e.g., mysql://user:pass@host/db).InvalidArgumentException: Validate entity annotations with cycle:validate.#[Relation].Custom Repositories with Relations Extend Cycle’s repositories for domain-specific logic, including optimized relation loading:
class UserRepository extends \Cycle\ORM\Select\Repository
{
public function findByNameWithPosts(string $name): ?User
{
return $this->getConnection()
->select()
->from(User::class)
->where('name', '=', $name)
->with('posts')
->fetchOne();
}
}
Middleware for ORM and Relations Use Spiral’s middleware to wrap requests in transactions and bulk-load relations:
public function handle(HandlerInterface $handler): ResponseInterface
{
return $this->cycle->getEntityManager()->transactional(
fn() => $handler->handle($request, $response)
)->with('posts'); // Bulk load relations for all entities
}
Hybrid with Eloquent If using both Cycle and Laravel’s Eloquent, alias the bridge:
$this->bean->alias(CycleBridge::class, 'db.cycle');
Then inject via:
public function __construct(private \DB\CycleBridge $cycle) {}
Dynamic Relation Loading Dynamically load relations based on runtime conditions:
$query = $this->cycle->getEntityManager()
->getRepository(User::class)
->createQueryBuilder();
if ($includePosts) {
$query->with('posts');
}
return $query->fetchAll();
How can I help you explore Laravel packages today?