Cycle ORM
Cycle ORM is a next-generation Object-Relational Mapping (ORM) library for PHP, featuring flexible design that supports both traditional ActiveRecord and DataMapper patterns. It integrates powerful schema builder, automatic migration, relationship mapping, and query builder functionality, while automating SQL injection prevention and data integrity assurance. Supporting major databases including MySQL, PostgreSQL, SQLite, and SQL Server, it provides a modern PHP ORM solution for building high-performance data access layers.
GitHub Overview
Topics
Star History
Library
Cycle ORM
Overview
Cycle ORM is a next-generation Object-Relational Mapping (ORM) library for PHP, featuring flexible design that supports both traditional ActiveRecord and DataMapper patterns. It integrates powerful schema builder, automatic migration, relationship mapping, and query builder functionality, while automating SQL injection prevention and data integrity assurance. Supporting major databases including MySQL, PostgreSQL, SQLite, and SQL Server, it provides a modern PHP ORM solution for building high-performance data access layers.
Details
Cycle ORM 2025 edition maximizes modern PHP features (PHP 8.1+) with a design that balances type safety and performance. It minimizes runtime overhead through compile-time schema analysis and provides efficient data retrieval strategies including lazy loading, eager loading, and batch processing. With annotation/attribute-based entity definition, mixed use of query builder and raw SQL, advanced relationship mapping (1:1, 1:many, many:many), and custom type casting functionality, it enables concise expression of complex business logic.
Key Features
- Dual Pattern Support: Supports both ActiveRecord and DataMapper patterns
- Type-safe Schema: Type-safe entity definition using PHP Attributes
- Advanced Query Builder: Intuitive and powerful query construction functionality
- Automatic Migrations: Automatic detection and application of schema changes
- Relationships: Easy definition and manipulation of complex associations
- Performance Optimization: Lazy loading, batch processing, and caching
Pros and Cons
Pros
- Flexible choice between ActiveRecord and DataMapper accommodates diverse development styles
- Type-safe schema definition enables IDE support and bug prevention
- Automatic migration functionality significantly improves development efficiency
- High-performance query execution and memory-efficient data processing
- Unified operation of major databases including PostgreSQL, MySQL, SQLite
- Rich relationship support allows concise expression of complex data structures
Cons
- Steep learning curve, may be excessive for small projects
- Limited Japanese resources and community compared to Eloquent or Doctrine
- Requires complex configuration with high initial setup costs
- May need understanding of ORM internals during debugging
- Performance tuning requires specialized knowledge
- Fewer third-party integration tools and plugins compared to other ORMs
Reference Pages
Code Examples
Basic Setup
<?php
use Cycle\ORM;
use Cycle\Database;
// Database connection configuration
$dbal = new Database\DatabaseManager(new Database\Config\DatabaseConfig([
'default' => 'default',
'databases' => [
'default' => [
'connection' => 'mysql'
]
],
'connections' => [
'mysql' => new Database\Config\MySQLDriverConfig(
connection: new Database\Config\MySQL\TcpConnectionConfig(
database: 'test_db',
host: '127.0.0.1',
port: 3306,
user: 'root',
password: 'password',
),
),
]
]));
// Schema provider and ORM initialization
$schema = new ORM\Schema\GeneratedSchemaProvider();
$orm = new ORM\ORM(new ORM\Factory($dbal), $schema);
echo "Cycle ORM initialization completed" . PHP_EOL;
Model Definition and Basic Operations
<?php
use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\ORM\Entity\Behavior;
// User entity definition
#[Entity(table: 'users')]
#[Behavior\CreatedAt(field: 'createdAt')]
#[Behavior\UpdatedAt(field: 'updatedAt')]
class User
{
#[Column(type: 'primary')]
public int $id;
#[Column(type: 'string', length: 64)]
public string $name;
#[Column(type: 'string', unique: true)]
public string $email;
#[Column(type: 'int')]
public int $age;
#[Column(type: 'datetime')]
public \DateTimeImmutable $createdAt;
#[Column(type: 'datetime', nullable: true)]
public ?\DateTimeImmutable $updatedAt = null;
public function __construct(string $name, string $email, int $age)
{
$this->name = $name;
$this->email = $email;
$this->age = $age;
$this->createdAt = new \DateTimeImmutable();
}
}
// Basic CRUD operations
class UserService
{
private ORM\ORMInterface $orm;
public function __construct(ORM\ORMInterface $orm)
{
$this->orm = $orm;
}
// Create user
public function createUser(string $name, string $email, int $age): User
{
$user = new User($name, $email, $age);
$manager = new ORM\EntityManager($this->orm);
$manager->persist($user);
$manager->run();
return $user;
}
// Get user
public function getUserById(int $id): ?User
{
$repository = $this->orm->getRepository(User::class);
return $repository->findByPK($id);
}
// Get all users
public function getAllUsers(): array
{
$repository = $this->orm->getRepository(User::class);
return $repository->findAll();
}
// Update user
public function updateUser(User $user): void
{
$manager = new ORM\EntityManager($this->orm);
$manager->persist($user);
$manager->run();
}
// Delete user
public function deleteUser(User $user): void
{
$manager = new ORM\EntityManager($this->orm);
$manager->delete($user);
$manager->run();
}
// Conditional search
public function getUsersByAge(int $minAge, int $maxAge): array
{
$repository = $this->orm->getRepository(User::class);
return $repository->select()
->where('age', '>=', $minAge)
->where('age', '<=', $maxAge)
->orderBy('age')
->fetchAll();
}
}
Advanced Query Operations
<?php
class AdvancedUserQueries
{
private ORM\ORMInterface $orm;
public function __construct(ORM\ORMInterface $orm)
{
$this->orm = $orm;
}
// Complex conditional queries
public function searchUsers(array $criteria): array
{
$repository = $this->orm->getRepository(User::class);
$query = $repository->select();
// Dynamic filtering
if (!empty($criteria['name'])) {
$query->where('name', 'LIKE', "%{$criteria['name']}%");
}
if (!empty($criteria['email_domain'])) {
$query->where('email', 'LIKE', "%@{$criteria['email_domain']}");
}
if (!empty($criteria['age_range'])) {
[$min, $max] = $criteria['age_range'];
$query->where('age', 'BETWEEN', $min, $max);
}
if (!empty($criteria['created_after'])) {
$query->where('createdAt', '>', $criteria['created_after']);
}
return $query->orderBy('createdAt', 'DESC')->fetchAll();
}
// Pagination
public function getUsersPaginated(int $page, int $limit): array
{
$repository = $this->orm->getRepository(User::class);
$offset = ($page - 1) * $limit;
$users = $repository->select()
->limit($limit)
->offset($offset)
->orderBy('id')
->fetchAll();
$total = $repository->select()->count();
return [
'users' => $users,
'total' => $total,
'page' => $page,
'pages' => ceil($total / $limit)
];
}
// Aggregate queries
public function getUserStatistics(): array
{
$db = $this->orm->getDatabase();
$stats = $db->select('COUNT(*) as total, AVG(age) as avg_age, MIN(age) as min_age, MAX(age) as max_age')
->from('users')
->fetchOne();
// Age group statistics
$ageGroups = $db->select([
'CASE
WHEN age < 20 THEN "Teens"
WHEN age < 30 THEN "20s"
WHEN age < 40 THEN "30s"
WHEN age < 50 THEN "40s"
ELSE "50+"
END as age_group',
'COUNT(*) as count'
])
->from('users')
->groupBy('age_group')
->orderBy('age_group')
->fetchAll();
return [
'overall' => $stats,
'age_groups' => $ageGroups
];
}
// Raw SQL query execution
public function executeCustomQuery(string $sql, array $params = []): array
{
$db = $this->orm->getDatabase();
return $db->query($sql, $params)->fetchAll();
}
// Bulk operations
public function bulkUpdateUserAges(array $updates): void
{
$manager = new ORM\EntityManager($this->orm);
foreach ($updates as $update) {
$user = $this->orm->getRepository(User::class)->findByPK($update['id']);
if ($user) {
$user->age = $update['age'];
$manager->persist($user);
}
}
$manager->run();
}
}
Relationship Operations
<?php
// Post entity
#[Entity(table: 'posts')]
#[Behavior\CreatedAt(field: 'createdAt')]
class Post
{
#[Column(type: 'primary')]
public int $id;
#[Column(type: 'string')]
public string $title;
#[Column(type: 'text')]
public string $content;
#[Column(type: 'bool', default: false)]
public bool $published = false;
#[Column(type: 'datetime')]
public \DateTimeImmutable $createdAt;
// Relationship with User (many-to-one)
#[Cycle\Annotated\Annotation\Relation\BelongsTo(target: User::class)]
public User $user;
public function __construct(string $title, string $content, User $user)
{
$this->title = $title;
$this->content = $content;
$this->user = $user;
$this->createdAt = new \DateTimeImmutable();
}
}
// Tag entity
#[Entity(table: 'tags')]
class Tag
{
#[Column(type: 'primary')]
public int $id;
#[Column(type: 'string', unique: true)]
public string $name;
// Many-to-many relationship with Post
#[Cycle\Annotated\Annotation\Relation\ManyToMany(target: Post::class, through: 'post_tags')]
public array $posts = [];
public function __construct(string $name)
{
$this->name = $name;
}
}
// Extended User entity with posts relationship
class User // Extended version
{
// ... existing properties
// Relationship with Post (one-to-many)
#[Cycle\Annotated\Annotation\Relation\HasMany(target: Post::class)]
public array $posts = [];
// ... existing methods
}
// Service for relationship operations
class PostService
{
private ORM\ORMInterface $orm;
public function __construct(ORM\ORMInterface $orm)
{
$this->orm = $orm;
}
// Create post with tags
public function createPostWithTags(string $title, string $content, User $user, array $tagNames): Post
{
$post = new Post($title, $content, $user);
$manager = new ORM\EntityManager($this->orm);
$tagRepo = $this->orm->getRepository(Tag::class);
// Get or create tags
foreach ($tagNames as $tagName) {
$tag = $tagRepo->findOne(['name' => $tagName]);
if (!$tag) {
$tag = new Tag($tagName);
$manager->persist($tag);
}
$post->tags[] = $tag;
}
$manager->persist($post);
$manager->run();
return $post;
}
// Get user posts with related data
public function getUserPostsWithTags(int $userId): array
{
$repository = $this->orm->getRepository(Post::class);
return $repository->select()
->load('user')
->load('tags')
->where('user.id', $userId)
->orderBy('createdAt', 'DESC')
->fetchAll();
}
// Get published posts with author information
public function getPublishedPostsWithAuthors(): array
{
$repository = $this->orm->getRepository(Post::class);
return $repository->select()
->with('user')
->where('published', true)
->orderBy('createdAt', 'DESC')
->fetchAll();
}
// Get posts by specific tag
public function getPostsByTag(string $tagName): array
{
$repository = $this->orm->getRepository(Post::class);
return $repository->select()
->load('user')
->load('tags')
->with('tags', ['where' => ['name' => $tagName]])
->fetchAll();
}
// Top 10 users with most posts
public function getTopActiveUsers(int $limit = 10): array
{
$db = $this->orm->getDatabase();
return $db->select([
'u.id',
'u.name',
'u.email',
'COUNT(p.id) as post_count'
])
->from('users', 'u')
->leftJoin('posts', 'p')->on('u.id', 'p.user_id')
->groupBy('u.id', 'u.name', 'u.email')
->orderBy('post_count', 'DESC')
->limit($limit)
->fetchAll();
}
}
Practical Examples
<?php
// Practical application example
class BlogApplication
{
private ORM\ORMInterface $orm;
private UserService $userService;
private PostService $postService;
public function __construct(ORM\ORMInterface $orm)
{
$this->orm = $orm;
$this->userService = new UserService($orm);
$this->postService = new PostService($orm);
}
// Setup initial blog data
public function setupBlogData(): void
{
// Create sample users
$users = [
['John Doe', '[email protected]', 30],
['Jane Smith', '[email protected]', 25],
['Bob Johnson', '[email protected]', 35]
];
$createdUsers = [];
foreach ($users as [$name, $email, $age]) {
$createdUsers[] = $this->userService->createUser($name, $email, $age);
}
// Create sample posts
$posts = [
['PHP 8.2 New Features', 'Explaining new features added in PHP 8.2...', ['PHP', 'Web Development']],
['Cycle ORM Usage', 'How to effectively use Cycle ORM...', ['PHP', 'ORM', 'Database']],
['Modern PHP Development', 'Best practices for modern PHP development...', ['PHP', 'Best Practices']]
];
foreach ($posts as $index => [$title, $content, $tags]) {
$user = $createdUsers[$index % count($createdUsers)];
$post = $this->postService->createPostWithTags($title, $content, $user, $tags);
// Publish some posts
if ($index % 2 === 0) {
$post->published = true;
$manager = new ORM\EntityManager($this->orm);
$manager->persist($post);
$manager->run();
}
}
echo "Blog data setup completed" . PHP_EOL;
}
// Get dashboard information
public function getDashboardData(): array
{
$userStats = (new AdvancedUserQueries($this->orm))->getUserStatistics();
$topUsers = $this->postService->getTopActiveUsers(5);
$recentPosts = $this->postService->getPublishedPostsWithAuthors();
// Post statistics
$db = $this->orm->getDatabase();
$postStats = $db->select([
'COUNT(*) as total_posts',
'COUNT(CASE WHEN published = 1 THEN 1 END) as published_posts',
'COUNT(CASE WHEN published = 0 THEN 1 END) as draft_posts'
])
->from('posts')
->fetchOne();
return [
'user_stats' => $userStats,
'post_stats' => $postStats,
'top_users' => $topUsers,
'recent_posts' => array_slice($recentPosts, 0, 5)
];
}
// Transaction processing example
public function transferPostOwnership(int $postId, int $newUserId): bool
{
try {
$manager = new ORM\EntityManager($this->orm);
// Begin transaction
$transaction = $this->orm->getDatabase()->transaction();
$post = $this->orm->getRepository(Post::class)->findByPK($postId);
$newUser = $this->orm->getRepository(User::class)->findByPK($newUserId);
if (!$post || !$newUser) {
$transaction->rollback();
return false;
}
$oldUserId = $post->user->id;
$post->user = $newUser;
$manager->persist($post);
// Log ownership transfer (hypothetical example)
$db = $this->orm->getDatabase();
$db->insert('ownership_transfers')
->values([
'post_id' => $postId,
'old_user_id' => $oldUserId,
'new_user_id' => $newUserId,
'transferred_at' => new \DateTimeImmutable()
])
->run();
$manager->run();
$transaction->commit();
return true;
} catch (\Exception $e) {
$transaction->rollback();
error_log("Post ownership transfer error: " . $e->getMessage());
return false;
}
}
// Cached data retrieval
public function getCachedPopularPosts(int $limit = 10): array
{
$cacheKey = "popular_posts_{$limit}";
// Assuming use of PSR-16 compatible cache library
// $cache = new SomeCache();
/*
if ($cache->has($cacheKey)) {
return $cache->get($cacheKey);
}
*/
$db = $this->orm->getDatabase();
$popularPosts = $db->select([
'p.*',
'u.name as author_name',
'COUNT(c.id) as comment_count' // Hypothetical comment count
])
->from('posts', 'p')
->join('users', 'u')->on('p.user_id', 'u.id')
->leftJoin('comments', 'c')->on('p.id', 'c.post_id') // Hypothetical comments table
->where('p.published', true)
->groupBy('p.id', 'u.name')
->orderBy('comment_count', 'DESC')
->limit($limit)
->fetchAll();
// $cache->set($cacheKey, $popularPosts, 3600); // 1 hour cache
return $popularPosts;
}
// Report generation
public function generateMonthlyReport(\DateTime $month): array
{
$startDate = $month->format('Y-m-01');
$endDate = $month->format('Y-m-t');
$db = $this->orm->getDatabase();
// Monthly new users count
$newUsers = $db->select('COUNT(*) as count')
->from('users')
->where('createdAt', '>=', $startDate)
->where('createdAt', '<=', $endDate . ' 23:59:59')
->fetchOne();
// Monthly posts count
$newPosts = $db->select('COUNT(*) as count')
->from('posts')
->where('createdAt', '>=', $startDate)
->where('createdAt', '<=', $endDate . ' 23:59:59')
->fetchOne();
// Daily activity
$dailyActivity = $db->select([
'DATE(createdAt) as date',
'COUNT(*) as post_count'
])
->from('posts')
->where('createdAt', '>=', $startDate)
->where('createdAt', '<=', $endDate . ' 23:59:59')
->groupBy('DATE(createdAt)')
->orderBy('date')
->fetchAll();
return [
'period' => $month->format('F Y'),
'new_users' => $newUsers['count'],
'new_posts' => $newPosts['count'],
'daily_activity' => $dailyActivity
];
}
}
// Application execution example
$app = new BlogApplication($orm);
$app->setupBlogData();
$dashboard = $app->getDashboardData();
echo "=== Dashboard ===" . PHP_EOL;
echo "Total Users: " . $dashboard['user_stats']['overall']['total'] . PHP_EOL;
echo "Total Posts: " . $dashboard['post_stats']['total_posts'] . PHP_EOL;
echo "Published Posts: " . $dashboard['post_stats']['published_posts'] . PHP_EOL;
$report = $app->generateMonthlyReport(new \DateTime());
echo "\n=== Monthly Report ===" . PHP_EOL;
echo "Period: " . $report['period'] . PHP_EOL;
echo "New Users: " . $report['new_users'] . " users" . PHP_EOL;
echo "New Posts: " . $report['new_posts'] . " posts" . PHP_EOL;