Skip to content

kzxl/LiteORM

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LiteORM 🔮

Lightweight, high-performance PHP 8.2+ ORM with native attribute mapping, LINQ-style query builder, and automatic entity generation from database.

PHP 8.2+ License: Apache-2.0


✨ Features

Feature Description
PHP 8.2 Attributes #[Entity], #[Column], #[Id], #[HasMany]... — zero config
LINQ-Style QueryBuilder where(), firstOrFail(), single(), distinct(), toList()
Unit of Work Batch INSERT/UPDATE/DELETE in single transaction
Dirty Checking Only UPDATE changed fields (snapshot pattern)
Identity Map Same entity = same instance, no duplicate queries
Eager Loading with('posts') — batch queries, avoid N+1
Read/Write Splitting Auto-route SELECT to read replicas
Entity Generator Reverse-engineer DB tables → PHP entity classes
Auto Timestamps #[CreatedAt], #[UpdatedAt] auto-managed

⚠️ PHP Version Requirements

Minimum: PHP 8.2+ (PHP 7.4 is NOT supported)

LiteORM relies heavily on modern PHP features that have no backwards-compatible alternatives:

Feature Used Minimum PHP Why?
Native Attributes (#[Entity]) 8.0 Core mapping mechanism — replaces XML/YAML/annotations
readonly properties 8.1 Immutable metadata objects
Constructor property promotion 8.0 Clean attribute constructors
Union types (string|array) 8.0 Flexible API signatures
match expression 8.0 Type resolution, SQL generation
Enums, str_ends_with(), etc. 8.0–8.2 Various utility functions

Important

PHP 7.4 reached End-of-Life on November 28, 2022. If you need PHP 7.x support, consider Doctrine ORM (annotation-based) or Eloquent (convention-based).


⚖️ Ưu & Nhược Điểm (Pros & Cons)

👍 Ưu điểm (Pros)

  • Hiệu năng cực cao (High Performance): Cơ chế Prepared Statement Cache (tái sử dụng PDOStatement) và Batch Insert (O(1) prepare) giúp query lặp lại cực khứ.
  • Không cần cấu hình (Zero Config): Khai báo mapping trực tiếp trên class bằng PHP 8.2 Attributes (#[Entity], #[Column]), code gập gọn, dễ đọc.
  • Tối ưu RAM & DB (Memory & DB Optimized):
    • Identity Map đảm bảo mỗi ID chép ra 1 instance duy nhất trên memory.
    • Dirty Checking (chụp snapshot) đảm bảo chỉ gen câu lệnh UPDATE cho những trường (fields) thực sự bị sửa đổi.
    • AsNoTracking: Hỗ trợ ngắt tracking hoàn toàn cho các truy vấn chỉ đọc (read-only), tiết kiệm CPU và bộ nhớ cực tốt.
  • Ngăn chặn triệt để N+1 Queries: Eager Loading (with()) sử dụng cơ chế gom ID (WHERE IN) để select dữ liệu quan hệ, chỉ tốn đúng 2 query thay vì N+1.
  • Trải nghiệm code mượt mà như C# LINQ: Collection phong phú các method như firstOrFail(), single(), whereNotIn() dành cho developer quen C# / LINQ.
  • Tạo Entity tự động (Entity Generator CLI): Có sẵn tool console line (bin/liteorm generate) reverse-engineering từ Database thẳng ra file PHP Entity tích hợp đầy đủ Attributes.

👎 Nhược điểm (Cons)

  • Rào cản môi trường: Yêu cầu bắt buộc PHP 8.2+, hoàn toàn không tương thích với dự án PHP 7.x cũ.
  • Chưa có Versioning Migrations: Framework mới chỉ hỗ trợ build bảng (createTable()) và gen Entity CLI. Chưa có flow chạy file Up/Down migrations (như Phinx hay Laravel).
  • Database Dialects: Mặc dù hoạt động hoàn hảo với MySQL, SQLite nhưng vì QueryBuilder đang dùng toán tử khá tiêu chuẩn, các query filter có syntax độc lạ trên SQL Server / PostgreSQL có thể cần custom thêm.

📦 Installation

composer require kzxl/liteorm

🚀 Quick Start

1. Define Entity

use LiteORM\Attribute\{Entity, Table, Column, Id, AutoIncrement, CreatedAt, UpdatedAt, HasMany};

#[Entity]
#[Table('users')]
class User
{
    #[Id, AutoIncrement]
    public int $id;

    #[Column(length: 100)]
    public string $name;

    #[Column(unique: true)]
    public string $email;

    #[Column(nullable: true)]
    public ?int $age = null;

    #[CreatedAt]
    public ?\DateTimeImmutable $createdAt = null;

    #[UpdatedAt]
    public ?\DateTimeImmutable $updatedAt = null;

    #[HasMany(target: Post::class, foreignKey: 'user_id')]
    public array $posts = [];
}

2. CRUD Operations

$em = new EntityManager('mysql:host=localhost;dbname=myapp', 'root', 'pass');

// Create
$user = new User();
$user->name = 'John';
$user->email = 'john@example.com';
$em->persist($user);
$em->flush();  // INSERT + auto-set $user->id + timestamps

// Read
$user = $em->find(User::class, 1);

// Update (only dirty fields)
$user->name = 'Updated';
$em->flush();  // UPDATE users SET name='Updated' WHERE id=1

// Delete
$em->remove($user);
$em->flush();

3. LINQ-Style Queries

// First (= FirstOrDefault)
$user = $em->query(User::class)
    ->where('email', 'john@example.com')
    ->first();  // returns null if not found

// FirstOrFail (= LINQ First)
$user = $em->query(User::class)
    ->where('id', 1)
    ->firstOrFail();  // throws RuntimeException if not found

// Single (= SingleOrDefault)
$user = $em->query(User::class)
    ->where('email', 'unique@test.com')
    ->single();  // throws if >1 result

// SingleOrFail (= LINQ Single)
$user = $em->query(User::class)
    ->where('id', 1)
    ->singleOrFail();  // throws if 0 or >1

// Complex queries
$users = $em->query(User::class)
    ->where('age', '>', 18)
    ->whereNotIn('name', ['Admin', 'System'])
    ->orderBy('name')
    ->distinct()
    ->take(10)           // alias for limit()
    ->skip(20)           // alias for offset()
    ->toList();          // alias for get()

// Last
$newest = $em->query(User::class)
    ->orderBy('created_at', 'ASC')
    ->last();  // auto-reverses to DESC, returns first

// Aggregates
$count = $em->query(User::class)->where('active', true)->count();
$avgAge = $em->query(User::class)->avg('age');
$maxAge = $em->query(User::class)->max('age');

// Exists / Any
if ($em->query(User::class)->where('email', $email)->exists()) { ... }

// Bulk operations
$em->query(User::class)->where('active', false)->delete();
$em->query(User::class)->where('role', 'guest')->update(['role' => 'user']);

4. Eager Loading (N+1 Prevention)

// HasMany
$users = $em->query(User::class)
    ->with('posts')
    ->get();
// Only 2 queries: SELECT * FROM users + SELECT * FROM posts WHERE user_id IN (...)

// BelongsTo
$posts = $em->query(Post::class)
    ->with('author')
    ->get();

5. Read/Write Splitting

$em = new EntityManager([
    'write' => 'mysql:host=master;dbname=app',
    'read'  => [
        'mysql:host=replica1;dbname=app',
        'mysql:host=replica2;dbname=app',
    ],
], 'user', 'pass');

// SELECT → auto-routed to replica (round-robin)
// INSERT/UPDATE/DELETE → master

6. Code Generator (CLI)

LiteORM includes a CLI tool to reverse-engineer database tables into PHP entity classes automatically.

# Generate entities for ALL tables
php bin/liteorm generate --dsn="mysql:host=127.0.0.1;dbname=app" --user=root --pass=secret --namespace="App\Entity" --out="./src/Entity"

# Generate for a specific table ONLY
php bin/liteorm generate --dsn="sqlite:database.sqlite" --namespace="App\Entity" --out="./src/Entity" --table="users"

7. AsNoTracking (Read-Only Queries)

If you are querying data only for display and do not intend to update it, use asNoTracking(). This bypasses the Unit of Work identity map, saving memory and CPU time during snapshot creation.

$users = $em->query(User::class)
    ->where('role', 'admin')
    ->asNoTracking()
    ->toList();

// Changes will NOT be saved
$users[0]->name = 'Hacked';
$em->flush(); // (0 queries executed)

📋 LINQ Mapping

C# LINQ LiteORM PHP Note
Where(x => ...) ->where('col', '>', 10) Fluent, supports operator
FirstOrDefault() ->first() Returns null
First() ->firstOrFail() Throws
SingleOrDefault() ->single() Throws if >1
Single() ->singleOrFail() Throws if ≠1
Last() ->last() Auto-reverse order
Any() ->exists() Returns bool
Count() ->count()
Sum/Avg/Max/Min ->sum/avg/max/min()
Take(n) ->take(n) or ->limit(n)
Skip(n) ->skip(n) or ->offset(n)
Distinct() ->distinct()
ToList() ->toList() or ->get()
Contains() ->whereIn()
OrderBy/Desc ->orderBy('col', 'DESC')
GroupBy ->groupBy('col')
Select() ->select('col1', 'col2')
AsNoTracking() ->asNoTracking() Bypasses UoW tracking

🏗️ Architecture

LiteORM/
├── bin/
│   └── liteorm             # CLI Generator tool
├── src/
│   ├── Attribute/          # PHP 8.2 attributes (Entity, Column, Id, Relations...)
│   ├── Metadata/           # AttributeReader, EntityMetadata, ColumnMetadata
│   ├── Connection/         # ConnectionManager (R/W split, pooling)
│   ├── Query/              # QueryBuilder (LINQ-style fluent API)
│   ├── Schema/             # EntityGenerator (reverse DB → PHP)
│   └── EntityManager.php   # Main entry point (UoW, Identity Map)
├── tests/                  # PHPUnit test suite (67 tests)
├── composer.json
└── phpunit.xml

Design Principles:

  • Zero config — PHP attributes replace XML/YAML mapping
  • Dirty checking — snapshot pattern, only UPDATE changed fields
  • Identity map — one entity instance per primary key
  • Batch eager loadingWHERE IN (...) instead of N+1 queries
  • Type safety — strict types, PHPDoc generics

🧪 Testing

composer install
vendor/bin/phpunit

📄 License

Apache License 2.0 — see LICENSE.

About

A lightweight, high-performance PHP ORM and Database Abstraction Layer built for speed. Features UnitOfWork, Entity mapping, QueryBuilder, and AsNoTracking for massive scalability.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages