cycle/orm
Cycle ORM is a fast, flexible PHP DataMapper ORM for long-running apps. Works with plain PHP objects, dynamic schemas, and powerful query builder. Supports relations, eager/lazy loading, migrations, and MySQL/Postgres/SQLite/SQL Server.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require cycle/orm
For annotations support (optional):
composer require cycle/annotated
Define an Entity:
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Column;
#[Entity]
class User
{
#[Column(type: 'primary')]
public int $id;
#[Column(type: 'string')]
public string $name;
}
Configure ORM:
Create a config file (e.g., config/cycle.php):
return [
'orm' => [
'connection' => 'default',
'entities' => [
__DIR__.'/../app/Entities',
],
],
];
Bootstrap ORM:
use Cycle\ORM\ORM;
use Cycle\Database\DatabaseManager;
$db = DatabaseManager::create(['default' => 'pgsql://user:pass@localhost/db']);
$orm = ORM::create($db, include __DIR__.'/config/cycle.php');
First Query:
$user = $orm->getRepository(User::class)->find(1);
// Create
$user = new User();
$user->name = 'John Doe';
$orm->getRepository(User::class)->persist($user)->run();
// Read
$user = $orm->getRepository(User::class)->find(1);
// Update
$user->name = 'Updated Name';
$orm->getRepository(User::class)->persist($user)->run();
// Delete
$orm->getRepository(User::class)->delete($user)->run();
Cycle ORM leverages repositories for database operations. Each entity has a dedicated repository:
$userRepo = $orm->getRepository(User::class);
// Fetch all
$users = $userRepo->findAll();
// Fetch with conditions
$activeUsers = $userRepo->select()->where('active', true)->fetchAll();
Cycle provides a fluent query builder:
$users = $userRepo
->select(['name', 'email'])
->where('age', '>', 18)
->orderBy('name', 'ASC')
->limit(10)
->fetchAll();
Define relationships in your entity:
#[Entity]
class Post
{
#[Column(type: 'primary')]
public int $id;
#[Column(type: 'string')]
public string $title;
#[BelongsTo(target: User::class)]
public User $author;
}
Load relationships:
// Eager loading
$posts = $postRepo
->load('author')
->fetchAll();
// Lazy loading
$post = $postRepo->find(1);
$author = $post->author; // Loaded on-demand
Use Unit of Work (UoW) for transactions:
$uow = $orm->getUow();
$uow->persist($user);
$uow->persist($post);
$uow->run(); // Commits transaction
Define embedded entities for complex data:
#[Embeddable]
class Address
{
#[Column(type: 'string')]
public string $street;
}
#[Entity]
class Customer
{
#[Embedded]
public Address $address;
}
Create reusable query scopes:
#[Entity]
class Product
{
#[Column(type: 'string')]
public string $name;
#[Column(type: 'integer')]
public int $price;
#[Scope]
public function active(): Select
{
return $this->select()->where('active', true);
}
}
// Usage
$activeProducts = $productRepo->active()->fetchAll();
Use cycle/active-record for AR-style entities:
use Cycle\ActiveRecord\ActiveRecord;
class User extends ActiveRecord
{
public int $id;
public string $name;
}
// Usage
$user = User::find(1);
$user->name = 'Updated';
$user->save();
Transaction Management:
run() to execute transactions. Forgetting this will leave operations pending.$uow = $orm->getUow();
$uow->persist($entity);
// $uow->run(); // Don't forget this!
Lazy Loading:
load()) for performance-critical paths.// Avoid N+1 queries
$posts = $postRepo->load('author')->fetchAll();
Entity State:
$user = new User();
$user->name = 'John';
$userRepo->persist($user); // Required to mark as new
Cyclic References:
#[ManyToMany(target: Tag::class)]
public Collection $tags;
#[ManyToMany(target: Post::class, inverse: 'tags')]
public Collection $posts;
Enable Query Logging:
$db = DatabaseManager::create([
'default' => [
'dsn' => 'pgsql://user:pass@localhost/db',
'logging' => true, // Enable query logging
],
]);
Use toSql():
Convert queries to SQL for debugging:
$query = $userRepo->select()->where('active', true);
echo $query->toSql(); // Outputs raw SQL
Check Entity State:
Use getState() to inspect entity state:
$state = $userRepo->getState($user);
Connection Management:
DatabaseManager.$db = DatabaseManager::create([
'default' => [
'dsn' => 'pgsql://user:pass@localhost/db',
'options' => [
'charset' => 'utf8mb4',
],
],
]);
Entity Mapping:
use Cycle\Annotated\Annotation\Entity;
#[Entity]
class User { ... }
Schema Introspection:
cycle/schema-provider to introspect existing databases:
$provider = new DoctrineProvider($db);
$schema = $provider->getSchema();
Custom Mappers:
Cycle\ORM\Mapper\MapperInterface to handle custom mapping logic.class CustomMapper implements MapperInterface
{
public function map(array $data, EntityInterface $entity): void
{
// Custom mapping logic
}
}
Query Scopes:
Cycle\ORM\Select\ScopeInterface.class ActiveScope implements ScopeInterface
{
public function apply(Select $select): Select
{
return $select->where('active', true);
}
}
Event Subscribers:
Cycle\ORM\Event\EventManager to listen to ORM events (e.g., prePersist, postLoad).$eventManager = $orm->getEventManager();
$eventManager->addListener('prePersist', function ($event) {
$entity = $event->getEntity();
$entity->createdAt = new DateTime();
});
Custom Collections:
Cycle\ORM\Collection\CollectionInterface for custom collection behavior.class CustomCollection implements CollectionInterface
{
public function getIterator(): Iterator
{
// Custom iterator logic
}
}
Batch Operations: Use bulk operations for performance:
$userRepo->deleteMany()->where('active', false)->run();
Selective Loading: Load only necessary fields:
$users = $userRepo->select(['id', 'name'])->fetchAll();
Caching:
Use cycle/database caching features for frequent queries:
$db = DatabaseManager::create([
'default' => [
'dsn' =>
How can I help you explore Laravel packages today?