Installation Add the package via Composer:
composer require cycle/orm
Register the Cycle ORM service provider in config/app.php:
'providers' => [
Cycle\ORM\CycleServiceProvider::class,
],
Configuration
Define your database connection in config/cycle.php:
'connections' => [
'default' => [
'orm' => 'Cycle\\ORM\\Mapper\\MapperORM',
'db' => [
'driver' => 'pdo_mysql',
'host' => env('DB_HOST'),
'port' => env('DB_PORT'),
'database' => env('DB_DATABASE'),
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
],
],
],
First Model
Define an entity (e.g., User) with annotations:
use Cycle\Annotated;
#[ORM\Entity]
class User
{
#[ORM\Column(type: 'primary')]
public int $id;
#[ORM\Column(type: 'string')]
public string $name;
#[ORM\Column(type: 'string')]
public string $email;
}
First Query Inject the repository into a service or controller:
use Cycle\ORM\Select;
use Cycle\ORM\Repository;
class UserService
{
public function __construct(
private Repository $repository,
) {}
public function findByEmail(string $email): ?User
{
return $this->repository
->get(User::class)
->where('email', '=', $email)
->fetchOne();
}
}
CRUD Operations Use repositories for all CRUD:
// Create
$user = new User();
$user->name = 'John';
$user->email = 'john@example.com';
$this->repository->persist($user);
// Read
$user = $this->repository->get(User::class)->find($user->id);
// Update
$user->name = 'John Doe';
$this->repository->persist($user);
// Delete
$this->repository->remove($user);
Query Building Chain methods for complex queries:
$users = $this->repository
->get(User::class)
->where('active', '=', true)
->orderBy('name')
->limit(10)
->fetchAll();
Relationships Define relationships via annotations:
#[ORM\ManyToMany(target: Post::class)]
public array $posts;
Fetch related data:
$user = $this->repository
->get(User::class)
->with('posts') // Eager load
->find($userId);
Transactions
Use Transaction for atomic operations:
$transaction = $this->repository->transaction();
try {
$transaction->persist($user);
$transaction->persist($post);
$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollBack();
throw $e;
}
Events & Hooks Listen to lifecycle events:
$this->repository->get(User::class)->onPersist(function (User $user) {
// Pre-persist logic
});
Laravel Integration
Bind the repository to Laravel’s container in CycleServiceProvider:
$this->app->bind(Repository::class, function ($app) {
return $app->make('cycle.orm')->getRepository();
});
Use dependency injection in controllers:
public function __construct(private Repository $repository) {}
Migrations Use Cycle’s schema builder for migrations:
use Cycle\ORM\Blueprint;
$schema = $this->repository->getSchema();
$schema->createTable('users', function (Blueprint $table) {
$table->integer('id')->primary();
$table->string('name');
$table->string('email')->unique();
});
Testing
Use Cycle\ORM\Test\ORMTestCase for isolated tests:
use Cycle\ORM\Test\ORMTestCase;
class UserTest extends ORMTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->repository->getSchema()->createTable('users', ...);
}
}
Lazy Loading
Avoid N+1 queries by using with() or fetchMode(FetchMode::JOIN):
$user = $this->repository
->get(User::class)
->with('posts')
->find($userId);
Transaction Isolation Ensure transactions are properly committed/rolled back:
// ❌ Forgetting to commit
$transaction->persist($user); // No commit!
// ✅ Correct
try {
$transaction->persist($user);
$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollBack();
}
Schema Mismatches
Always run schema:update after changing entity annotations:
php artisan cycle:schema:update
Case Sensitivity Column names in annotations must match the database schema exactly (including case).
Circular References Avoid infinite loops in relationships:
// ❌ Circular (User <-> Post <-> User)
#[ORM\ManyToMany(target: User::class)]
public array $authors;
Query Logging
Enable SQL logging in config/cycle.php:
'debug' => [
'query' => true,
],
Entity Dumping
Use dump() to inspect entities:
$user = $this->repository->get(User::class)->find(1);
dump($user->toArray());
Schema Inspection Dump the schema to verify tables/columns:
$schema = $this->repository->getSchema();
dump($schema->getTable('users')->getColumns());
Custom Types
Extend Cycle\ORM\Column\Type for custom column types:
class JsonType extends Type
{
public function getSQLType(): string { return 'json'; }
public function encode($value): string { return json_encode($value); }
public function decode(string $value): mixed { return json_decode($value); }
}
Custom Repositories Create a base repository class:
class BaseRepository
{
public function __construct(protected Repository $repository) {}
public function findOrFail(int $id): User
{
return $this->repository->get(User::class)->findOrFail($id);
}
}
Event Subscribers Listen to global events:
$this->repository->onPersist(function ($entity) {
if ($entity instanceof User) {
// Custom logic
}
});
Custom Query Filters Add reusable query scopes:
$this->repository->get(User::class)->where('active', '=', true)->activeUsers();
Database-Specific Features
Use Cycle\Database for raw queries when needed:
$db = $this->repository->getConnection();
$result = $db->select('SELECT * FROM users WHERE id = ?', [$userId]);
How can I help you explore Laravel packages today?