PHPackages                             paigejulianne/nanoorm - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. paigejulianne/nanoorm

ActiveLibrary[Database &amp; ORM](/categories/database)

paigejulianne/nanoorm
=====================

A lightweight, full-featured PHP ORM with fluent query builder, relationships, and migrations

v1.0.0(4mo ago)041MITPHPPHP &gt;=8.1

Since Jan 4Pushed 4mo agoCompare

[ Source](https://github.com/paigejulianne/nanoorm)[ Packagist](https://packagist.org/packages/paigejulianne/nanoorm)[ RSS](/packages/paigejulianne-nanoorm/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (1)Versions (2)Used By (1)

NanoORM
=======

[](#nanoorm)

A lightweight, full-featured PHP ORM with fluent query builder, relationships, migrations, and zero dependencies.

Features
--------

[](#features)

- **Single file** (~1,800 lines) - easy to audit and include
- **Zero dependencies** - only PHP 8.1+ and PDO required
- **Fluent query builder** - chainable, expressive queries
- **Relationships** - HasOne, HasMany, BelongsTo, BelongsToMany with eager loading
- **Soft deletes** - built-in support with `withTrashed()`, `onlyTrashed()`
- **Timestamps** - automatic `created_at`/`updated_at` management
- **Attribute casting** - JSON, datetime, boolean, and more
- **Identity map** - prevents duplicate model instances
- **Migrations** - simple migration system with schema builder
- **Query logging** - built-in debugging tools
- **Multi-database** - MySQL, PostgreSQL, SQLite, SQL Server

Installation
------------

[](#installation)

```
composer require paigejulianne/nanoorm
```

Or simply include `NanoORM.php` directly.

Quick Start
-----------

[](#quick-start)

### Configuration

[](#configuration)

Create a `.connections` file in your project root:

```
[default]
DSN=mysql:host=localhost;dbname=myapp;charset=utf8mb4
USER=root
PASS=secret

[testing]
DSN=sqlite::memory:
```

Or configure programmatically:

```
use NanoORM\Model;

Model::addConnection('default', 'mysql:host=localhost;dbname=myapp', 'user', 'pass');
```

### Define Models

[](#define-models)

```
use NanoORM\Model;

class User extends Model
{
    // Optional: customize table name (default: users)
    protected const ?string TABLE = 'users';

    // Enable features
    protected const bool TIMESTAMPS = true;
    protected const bool SOFT_DELETES = true;

    // Attribute casting
    protected const array CASTS = [
        'is_admin' => 'boolean',
        'settings' => 'json',
    ];

    // Hide from JSON/array output
    protected const array HIDDEN = ['password'];

    // Relationships
    public function posts(): \NanoORM\HasMany
    {
        return $this->hasMany(Post::class);
    }

    public function profile(): \NanoORM\HasOne
    {
        return $this->hasOne(Profile::class);
    }

    public function roles(): \NanoORM\BelongsToMany
    {
        return $this->belongsToMany(Role::class);
    }
}

class Post extends Model
{
    protected const bool TIMESTAMPS = true;

    public function author(): \NanoORM\BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function comments(): \NanoORM\HasMany
    {
        return $this->hasMany(Comment::class);
    }
}
```

### Basic CRUD

[](#basic-crud)

```
// Create
$user = User::create([
    'name' => 'John Doe',
    'email' => 'john@example.com',
]);

// Or create manually
$user = new User(['name' => 'Jane']);
$user->email = 'jane@example.com';
$user->save();

// Read
$user = User::find(1);
$user = User::findOrFail(1);
$users = User::all();

// Update
$user->name = 'John Smith';
$user->save();

// Delete
$user->delete();          // Soft delete if enabled
$user->forceDelete();     // Permanent delete
$user->restore();         // Restore soft-deleted
```

### Query Builder

[](#query-builder)

```
// Fluent queries
$users = User::where('active', true)
    ->where('role', 'admin')
    ->orderBy('created_at', 'DESC')
    ->limit(10)
    ->get();

// Multiple conditions
$users = User::where([
    'active' => true,
    'verified' => true,
])->get();

// OR conditions
$users = User::where('role', 'admin')
    ->orWhere('role', 'moderator')
    ->get();

// Nested conditions
$users = User::where('active', true)
    ->where(function ($query) {
        $query->where('role', 'admin')
              ->orWhere('is_super', true);
    })
    ->get();

// Various WHERE clauses
User::whereIn('id', [1, 2, 3])->get();
User::whereNotIn('status', ['banned', 'suspended'])->get();
User::whereNull('deleted_at')->get();
User::whereNotNull('verified_at')->get();
User::whereBetween('age', 18, 65)->get();
User::whereRaw('YEAR(created_at) = ?', [2024])->get();

// Ordering
User::orderBy('name')->get();
User::orderBy('created_at', 'DESC')->get();
User::latest()->get();           // ORDER BY created_at DESC
User::oldest()->get();           // ORDER BY created_at ASC

// Pagination
$result = User::where('active', true)->paginate(15, $page);
// Returns: ['data' => [...], 'total' => 100, 'per_page' => 15, ...]

// Chunking (memory efficient)
User::where('active', true)->chunk(100, function ($users) {
    foreach ($users as $user) {
        // Process user
    }
});

// Aggregates
$count = User::where('active', true)->count();
$total = Order::where('status', 'completed')->sum('amount');
$avg = Product::avg('price');
$max = Order::max('total');
$min = Product::min('stock');

// Pluck single column
$emails = User::where('active', true)->pluck('email');
$names = User::pluck('name', 'id');  // ['id' => 'name', ...]

// Check existence
if (User::where('email', $email)->exists()) {
    // Email taken
}
```

### Relationships

[](#relationships)

```
// Lazy loading (loads when accessed)
$user = User::find(1);
$posts = $user->posts;           // Triggers query
$profile = $user->profile;

// Eager loading (prevents N+1)
$users = User::with('posts', 'profile')->get();

// Nested eager loading
$users = User::with('posts.comments')->get();

// Relationship queries
$recentPosts = $user->posts()
    ->where('published', true)
    ->orderBy('created_at', 'DESC')
    ->limit(5)
    ->get();

// Many-to-many operations
$user->roles()->attach(1);                    // Add role
$user->roles()->attach([1, 2, 3]);            // Add multiple
$user->roles()->detach(1);                    // Remove role
$user->roles()->detach();                     // Remove all
$user->roles()->sync([1, 2, 3]);              // Replace all
$user->roles()->toggle([1, 2]);               // Toggle roles

// With pivot attributes
$user->roles()->attach(1, ['assigned_at' => now()]);
```

### Soft Deletes

[](#soft-deletes)

```
class Post extends Model
{
    protected const bool SOFT_DELETES = true;
}

$post->delete();                              // Sets deleted_at

// Querying
Post::all();                                  // Excludes deleted
Post::withTrashed()->get();                   // Includes deleted
Post::onlyTrashed()->get();                   // Only deleted

// Restoring
$post->restore();

// Permanent delete
$post->forceDelete();

// Check if deleted
if ($post->trashed()) {
    // ...
}
```

### Timestamps

[](#timestamps)

```
class Post extends Model
{
    protected const bool TIMESTAMPS = true;

    // Customize column names (optional)
    protected const string CREATED_AT = 'created_at';
    protected const string UPDATED_AT = 'updated_at';
}

// Timestamps are automatically managed
$post = Post::create(['title' => 'Hello']);
echo $post->created_at;  // Set automatically

$post->title = 'Updated';
$post->save();
echo $post->updated_at;  // Updated automatically
```

### Attribute Casting

[](#attribute-casting)

```
class User extends Model
{
    protected const array CASTS = [
        'is_admin' => 'boolean',
        'settings' => 'json',
        'birthday' => 'datetime',
        'score' => 'float',
        'views' => 'integer',
    ];
}

$user = User::find(1);

// Casts automatically applied
$user->settings = ['theme' => 'dark'];  // Stored as JSON
$settings = $user->settings;             // Retrieved as array

$user->is_admin = true;                  // Stored as 1/0
if ($user->is_admin) { }                 // Retrieved as boolean
```

### Atomic Operations

[](#atomic-operations)

```
$post->increment('views');           // views + 1
$post->increment('views', 5);        // views + 5
$post->decrement('stock');           // stock - 1
$post->decrement('stock', 2);        // stock - 2

// With additional updates
$post->increment('views', 1, ['last_viewed_at' => date('Y-m-d H:i:s')]);
```

### Transactions

[](#transactions)

```
use NanoORM\Model;

// Manual transactions
Model::beginTransaction();
try {
    $user = User::create(['name' => 'John']);
    $profile = Profile::create(['user_id' => $user->getKey()]);
    Model::commit();
} catch (Exception $e) {
    Model::rollback();
    throw $e;
}

// Callback-based (auto rollback on exception)
Model::transaction(function () {
    $user = User::create(['name' => 'John']);
    Profile::create(['user_id' => $user->getKey()]);
});
```

### Bulk Operations

[](#bulk-operations)

```
// Bulk insert (single query)
User::insert([
    ['name' => 'John', 'email' => 'john@example.com'],
    ['name' => 'Jane', 'email' => 'jane@example.com'],
]);

// Bulk insert with IDs returned
$ids = User::insertGetIds([
    ['name' => 'John', 'email' => 'john@example.com'],
    ['name' => 'Jane', 'email' => 'jane@example.com'],
]);

// Bulk update
User::where('active', false)->update(['status' => 'inactive']);

// Bulk delete
User::where('last_login', '
