Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Cycle ORM
https://github.com/cycle/orm
Admin
Cycle ORM is a PHP DataMapper and ORM engine designed for classic and daemonized applications,
...
Tokens:
6,958
Snippets:
36
Trust Score:
9.2
Update:
3 months ago
Context
Skills
Chat
Benchmark
68.8
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Cycle ORM Cycle ORM is a PHP DataMapper ORM and Data Modelling engine designed to safely work in classic and daemonized PHP applications (like RoadRunner). It provides flexible configuration options to model datasets, a powerful query builder, and supports dynamic mapping schemas. The engine works with plain PHP objects, supports annotation declarations, and proxies via extensions. The ORM implements the Data Mapper pattern with a Unit of Work for tracking entity changes and managing persistence. It supports various relation types including has-one, has-many, many-to-many, and polymorphic relations. Cycle ORM is designed for long-running applications with an immutable service core and disposable Unit of Work, making it suitable for modern PHP architectures like RoadRunner and Swoole. ## Core Components ### ORM Instance Creation The ORM class is the central entry point that provides access to repositories, mappers, and the entity heap. It requires a Factory and Schema to be initialized. ```php <?php use Cycle\ORM\ORM; use Cycle\ORM\Factory; use Cycle\ORM\Schema; use Cycle\ORM\SchemaInterface; use Cycle\ORM\Mapper\Mapper; use Cycle\Database\DatabaseManager; use Cycle\Database\Config\DatabaseConfig; // Configure database connection $dbal = new DatabaseManager(new DatabaseConfig([ 'default' => 'default', 'databases' => [ 'default' => ['connection' => 'sqlite'] ], 'connections' => [ 'sqlite' => [ 'driver' => \Cycle\Database\Driver\SQLite\SQLiteDriver::class, 'connection' => 'sqlite:database.db', ] ] ])); // Define entity schema $schema = new Schema([ 'user' => [ SchemaInterface::ENTITY => User::class, SchemaInterface::MAPPER => Mapper::class, SchemaInterface::DATABASE => 'default', SchemaInterface::TABLE => 'users', SchemaInterface::PRIMARY_KEY => 'id', SchemaInterface::COLUMNS => ['id', 'email', 'name', 'balance'], SchemaInterface::RELATIONS => [], ], ]); // Create ORM instance $orm = new ORM( factory: new Factory($dbal), schema: $schema, ); // Access services $heap = $orm->getHeap(); $factory = $orm->getFactory(); $schema = $orm->getSchema(); ``` ### Entity Manager - Persisting Entities The EntityManager handles entity persistence through a Unit of Work pattern. It supports both immediate state persistence and deferred persistence. ```php <?php use Cycle\ORM\EntityManager; // Create entity manager $em = new EntityManager($orm); // Create new entity $user = new User(); $user->name = 'John Doe'; $user->email = 'john@example.com'; $user->balance = 100.00; // Persist with deferred state sync (changes after persist() are included) $em->persist($user); $user->balance = 150.00; // This change WILL be saved // Or persist with immediate state capture (changes after persistState() are ignored) $em->persistState($user); $user->balance = 200.00; // This change will NOT be saved // Execute all pending operations $state = $em->run(); if ($state->isSuccess()) { echo "Entity saved with ID: " . $user->id; } else { echo "Error: " . $state->getLastError()->getMessage(); } // Delete an entity $em->delete($user); $em->run(); // Run without throwing exceptions $state = $em->run(throwException: false); if (!$state->isSuccess()) { // Handle error gracefully $error = $state->getLastError(); } // Clean the entity manager (reset pending operations) $em->clean(); // Clean with heap reset $em->clean(cleanHeap: true); ``` ### Repository - Finding Entities Repositories provide a clean interface for querying entities. Each entity role has an associated repository. ```php <?php use Cycle\ORM\Select; // Get repository for an entity $userRepository = $orm->getRepository(User::class); // Find by primary key $user = $userRepository->findByPK(1); // Find one by conditions $user = $userRepository->findOne(['email' => 'john@example.com']); // Find all matching conditions $users = $userRepository->findAll(['status' => 'active']); // Get entity directly from ORM (uses heap cache) $user = $orm->get(User::class, ['id' => 1]); // Force load from database (bypass heap) $user = $orm->get(User::class, ['id' => 1], load: true); // Check if entity exists in heap without loading $user = $orm->get(User::class, ['id' => 1], load: false); ``` ### Select Query Builder The Select class provides a fluent query builder for complex queries with relation loading. ```php <?php use Cycle\ORM\Select; // Create select query $select = new Select($orm, User::class); // Basic where conditions $users = $select ->where('status', 'active') ->where('balance', '>', 100) ->orderBy('name', 'ASC') ->limit(10) ->offset(0) ->fetchAll(); // Find by primary key $user = (new Select($orm, User::class)) ->wherePK(1) ->fetchOne(); // Multiple primary keys $users = (new Select($orm, User::class)) ->wherePK(1, 2, 3) ->fetchAll(); // Complex conditions with callbacks $users = (new Select($orm, User::class)) ->where(function ($q) { $q->where('status', 'active') ->orWhere('role', 'admin'); }) ->fetchAll(); // Aggregations $count = (new Select($orm, User::class))->count(); $avgBalance = (new Select($orm, User::class))->avg('balance'); $maxBalance = (new Select($orm, User::class))->max('balance'); $minBalance = (new Select($orm, User::class))->min('balance'); $totalBalance = (new Select($orm, User::class))->sum('balance'); // Get raw SQL $sql = (new Select($orm, User::class)) ->where('status', 'active') ->sqlStatement(); // Fetch as array data (not entities) $data = (new Select($orm, User::class)) ->fetchData(typecast: true); ``` ### Eager Loading Relations Load related entities efficiently using the `load()` method to prevent N+1 query problems. ```php <?php use Cycle\ORM\Select; // Load single relation $users = (new Select($orm, User::class)) ->load('profile') ->fetchAll(); // Load multiple relations $users = (new Select($orm, User::class)) ->load(['profile', 'comments', 'orders']) ->fetchAll(); // Load nested relations (user -> posts -> comments) $users = (new Select($orm, User::class)) ->load('posts.comments') ->fetchAll(); // Load with conditions using placeholder {@} for table alias $users = (new Select($orm, User::class)) ->load('comments', [ 'where' => ['{@}.approved' => true], 'orderBy' => ['{@}.created_at' => 'DESC'], ]) ->fetchAll(); // Control loading method $users = (new Select($orm, User::class)) ->load('orders', [ 'method' => Select::SINGLE_QUERY, // Use LEFT JOIN 'load' => function ($q) { $q->where('paid', true) ->orderBy('created_at', 'DESC'); } ]) ->fetchAll(); // Load with OUTER_QUERY (separate query, default for has-many) $users = (new Select($orm, User::class)) ->load('orders', ['method' => Select::OUTER_QUERY]) ->fetchAll(); // Many-to-many with pivot conditions $users = (new Select($orm, User::class)) ->load('tags', [ 'wherePivot' => ['{@}.approved' => true] ]) ->fetchAll(); ``` ### Join Relations for Filtering Use `with()` to join relations for filtering without loading the relation data. ```php <?php use Cycle\ORM\Select; // Filter users who have comments $users = (new Select($orm, User::class)) ->distinct() ->with('comments') ->fetchAll(); // Filter by related entity field $users = (new Select($orm, User::class)) ->with('comments') ->where('comments.approved', true) ->fetchAll(); // Nested relation filtering $users = (new Select($orm, User::class)) ->with('posts.comments') ->where('posts_comments.approved', true) ->fetchAll(); // Custom join alias $users = (new Select($orm, User::class)) ->with('posts.comments', ['as' => 'comments']) ->where('comments.approved', true) ->fetchAll(); // Many-to-many pivot table filtering $users = (new Select($orm, User::class)) ->with('tags') ->where('tags_pivot.approved', true) ->fetchAll(); // Combine with() and load() - filter and load $users = (new Select($orm, User::class)) ->with('comments') ->where('comments.approved', true) ->load('comments', [ 'using' => 'comments' // Reuse the joined table ]) ->fetchAll(); ``` ### Schema Definition Define entity schemas programmatically with relations, typecasting, and other options. ```php <?php use Cycle\ORM\Schema; use Cycle\ORM\SchemaInterface; use Cycle\ORM\Relation; use Cycle\ORM\Mapper\Mapper; $schema = new Schema([ // User entity 'user' => [ SchemaInterface::ENTITY => User::class, SchemaInterface::MAPPER => Mapper::class, SchemaInterface::DATABASE => 'default', SchemaInterface::TABLE => 'users', SchemaInterface::PRIMARY_KEY => 'id', SchemaInterface::COLUMNS => [ 'id' => 'id', // property => column 'email' => 'email', 'name' => 'name', 'createdAt' => 'created_at', ], SchemaInterface::TYPECAST => [ 'id' => 'int', 'createdAt' => 'datetime', ], SchemaInterface::RELATIONS => [ 'profile' => [ Relation::TYPE => Relation::HAS_ONE, Relation::TARGET => 'profile', Relation::SCHEMA => [ Relation::CASCADE => true, Relation::INNER_KEY => 'id', Relation::OUTER_KEY => 'user_id', ], ], 'posts' => [ Relation::TYPE => Relation::HAS_MANY, Relation::TARGET => 'post', Relation::SCHEMA => [ Relation::CASCADE => true, Relation::INNER_KEY => 'id', Relation::OUTER_KEY => 'user_id', ], ], ], ], // Profile entity with belongs-to relation 'profile' => [ SchemaInterface::ENTITY => Profile::class, SchemaInterface::MAPPER => Mapper::class, SchemaInterface::DATABASE => 'default', SchemaInterface::TABLE => 'profiles', SchemaInterface::PRIMARY_KEY => 'id', SchemaInterface::COLUMNS => ['id', 'user_id', 'bio', 'avatar'], SchemaInterface::RELATIONS => [ 'user' => [ Relation::TYPE => Relation::BELONGS_TO, Relation::TARGET => 'user', Relation::SCHEMA => [ Relation::CASCADE => true, Relation::INNER_KEY => 'user_id', Relation::OUTER_KEY => 'id', ], ], ], ], // Many-to-many relation example 'post' => [ SchemaInterface::ENTITY => Post::class, SchemaInterface::MAPPER => Mapper::class, SchemaInterface::DATABASE => 'default', SchemaInterface::TABLE => 'posts', SchemaInterface::PRIMARY_KEY => 'id', SchemaInterface::COLUMNS => ['id', 'user_id', 'title', 'content'], SchemaInterface::RELATIONS => [ 'tags' => [ Relation::TYPE => Relation::MANY_TO_MANY, Relation::TARGET => 'tag', Relation::SCHEMA => [ Relation::CASCADE => true, Relation::INNER_KEY => 'id', Relation::OUTER_KEY => 'id', Relation::THROUGH_ENTITY => 'post_tag', Relation::THROUGH_INNER_KEY => 'post_id', Relation::THROUGH_OUTER_KEY => 'tag_id', ], ], ], ], ]); // Access schema information $roles = $schema->getRoles(); // ['user', 'profile', 'post', ...] $relations = $schema->getRelations('user'); // ['profile', 'posts'] $table = $schema->define('user', SchemaInterface::TABLE); // 'users' ``` ### Entity Creation with ORM Create entity instances using the ORM factory with optional data hydration. ```php <?php use Cycle\ORM\Heap\Node; // Create new entity (not tracked) $user = $orm->make(User::class); $user->name = 'Jane Doe'; $user->email = 'jane@example.com'; // Create with initial data $user = $orm->make(User::class, [ 'name' => 'Jane Doe', 'email' => 'jane@example.com', 'balance' => 500.00, ]); // Create as managed entity (tracked in heap) $user = $orm->make(User::class, [ 'id' => 1, 'name' => 'Jane Doe', 'email' => 'jane@example.com', ], Node::MANAGED); // Create with typecast (for raw database data) $user = $orm->make(User::class, [ 'id' => '1', // Will be cast to int 'balance' => '500.00', // Will be cast to float 'created_at' => '2024-01-01 00:00:00', // Will be cast to DateTime ], Node::MANAGED, typecast: true); ``` ### Working with Relations Manage entity relations through property assignment and cascading persistence. ```php <?php use Cycle\ORM\EntityManager; $em = new EntityManager($orm); // Create user with profile (has-one) $user = new User(); $user->name = 'John Doe'; $user->email = 'john@example.com'; $profile = new Profile(); $profile->bio = 'Developer'; $profile->avatar = 'avatar.jpg'; $user->profile = $profile; $em->persist($user); $em->run(); // Both user and profile are saved // Create user with posts (has-many) $user = new User(); $user->name = 'Jane Doe'; $user->posts = []; $post1 = new Post(); $post1->title = 'First Post'; $post1->content = 'Content here'; $post2 = new Post(); $post2->title = 'Second Post'; $post2->content = 'More content'; $user->posts[] = $post1; $user->posts[] = $post2; $em->persist($user); $em->run(); // User and all posts are saved // Many-to-many relations $post = new Post(); $post->title = 'Tagged Post'; $post->tags = []; $tag1 = $orm->getRepository(Tag::class)->findOne(['name' => 'PHP']); $tag2 = new Tag(); $tag2->name = 'ORM'; $post->tags[] = $tag1; $post->tags[] = $tag2; $em->persist($post); $em->run(); // Post, new tag, and pivot records are saved // Remove from relation $user = $orm->getRepository(User::class) ->select() ->load('posts') ->wherePK(1) ->fetchOne(); // Remove specific post $postToRemove = $user->posts[0]; unset($user->posts[array_search($postToRemove, $user->posts)]); $em->persist($user); $em->delete($postToRemove); // Optional: also delete the post $em->run(); ``` ### Heap and Identity Map The Heap maintains an identity map of loaded entities to prevent duplicate instances. ```php <?php // Access the heap $heap = $orm->getHeap(); // Check if entity is tracked $isTracked = $heap->has($user); // Get node for entity (contains state and metadata) $node = $heap->get($user); if ($node !== null) { $role = $node->getRole(); $status = $node->getStatus(); // Node::NEW, Node::MANAGED, Node::DELETED $data = $node->getData(); } // Find entity in heap by primary key $user = $heap->find('user', ['id' => 1]); // Clean heap (detach all entities) $heap->clean(); // Create ORM with fresh heap $freshOrm = $orm->with(heap: new \Cycle\ORM\Heap\Heap()); // Detach specific entity $heap->detach($user); ``` ### Custom Repository Create custom repositories with domain-specific query methods. ```php <?php use Cycle\ORM\Select; use Cycle\ORM\Select\Repository; class UserRepository extends Repository { public function findActiveUsers(): iterable { return $this->select() ->where('status', 'active') ->orderBy('name', 'ASC') ->fetchAll(); } public function findByEmail(string $email): ?User { return $this->select() ->where('email', $email) ->fetchOne(); } public function findWithRecentOrders(int $days = 30): iterable { return $this->select() ->distinct() ->with('orders') ->where('orders.created_at', '>', new \DateTime("-{$days} days")) ->load('orders', [ 'where' => ['{@}.created_at' => ['>' => new \DateTime("-{$days} days")]] ]) ->fetchAll(); } public function countByStatus(string $status): int { return $this->select() ->where('status', $status) ->count(); } } // Register in schema $schema = new Schema([ 'user' => [ SchemaInterface::ENTITY => User::class, SchemaInterface::REPOSITORY => UserRepository::class, // ... other options ], ]); // Use custom repository /** @var UserRepository $userRepo */ $userRepo = $orm->getRepository(User::class); $activeUsers = $userRepo->findActiveUsers(); $user = $userRepo->findByEmail('john@example.com'); ``` ### Scopes (Global Query Constraints) Apply global query constraints using scopes. ```php <?php use Cycle\ORM\Select\ScopeInterface; use Cycle\ORM\Select\QueryBuilder; class ActiveScope implements ScopeInterface { public function apply(QueryBuilder $query): void { $query->where('deleted_at', null); } } class TenantScope implements ScopeInterface { public function __construct( private int $tenantId ) {} public function apply(QueryBuilder $query): void { $query->where('tenant_id', $this->tenantId); } } // Register scope in schema $schema = new Schema([ 'user' => [ SchemaInterface::ENTITY => User::class, SchemaInterface::SCOPE => ActiveScope::class, // ... other options ], ]); // Or apply scope at runtime $users = (new Select($orm, User::class)) ->scope(new TenantScope(tenantId: 42)) ->fetchAll(); // Remove scope for query $allUsers = (new Select($orm, User::class)) ->scope(null) ->fetchAll(); ``` ### Collection Factories Configure custom collection types for has-many and many-to-many relations. ```php <?php use Cycle\ORM\Factory; use Cycle\ORM\Collection\DoctrineCollectionFactory; use Cycle\ORM\Collection\IlluminateCollectionFactory; use Doctrine\Common\Collections\ArrayCollection; use Illuminate\Support\Collection; // Use Doctrine Collections $factory = (new Factory($dbal)) ->withCollectionFactory( 'doctrine', new DoctrineCollectionFactory(), ArrayCollection::class ); // Use Laravel/Illuminate Collections $factory = (new Factory($dbal)) ->withCollectionFactory( 'illuminate', new IlluminateCollectionFactory(), Collection::class ); // Specify collection type in schema $schema = new Schema([ 'user' => [ // ... other options SchemaInterface::RELATIONS => [ 'posts' => [ Relation::TYPE => Relation::HAS_MANY, Relation::TARGET => 'post', Relation::COLLECTION_TYPE => 'doctrine', // or 'illuminate' Relation::SCHEMA => [ // ... ], ], ], ], ]); ``` ## Summary Cycle ORM is well-suited for building data access layers in PHP applications that require complex entity relationships, efficient query building, and safe persistence patterns. Common use cases include web applications with complex domain models, API backends requiring optimized database access, long-running worker processes that benefit from the immutable service core, and applications using modern PHP frameworks like Spiral or custom setups with RoadRunner. Integration typically involves configuring the DatabaseManager with your database connections, defining entity schemas (either programmatically or using the schema-builder extension with annotations), creating the ORM instance, and using repositories and the EntityManager for data access. The architecture supports dependency injection, making it easy to integrate with PSR-11 containers. For large applications, consider using the schema-provider extension to cache compiled schemas and the annotated extension to define schemas using PHP 8 attributes on your entity classes.