drahil/stutter

A lightweight, elegant ORM for PHP with intuitive relationship management.

v1.0.0 2025-04-26 12:51 UTC

This package is auto-updated.

Last update: 2025-05-26 13:12:22 UTC


README

A lightweight, elegant ORM for PHP with intuitive relationship management, based on Laravel's Eloquent. Stutter is designed for educational purposes, making it easy for junior developers to understand how Eloquent works by using and reading its code.

Installation

composer require drahil/stutter

Basic Usage

Connection Setup

use drahil\Stutter\Core\ConnectionManager;

ConnectionManager::addConnection([
    'driver' => 'mysql',
    'host' => 'localhost',
    'database' => 'your_database',
    'username' => 'your_username',
    'password' => 'your_password',
    'charset' => 'utf8mb4'
]);

Create a Model

namespace App\Models;

use drahil\Stutter\Core\Model;

class User extends Model
{
    protected static string $table = 'users';
}

Basic CRUD Operations

Retrieve All Records

$allUsers = User::all();

Find a Record by ID

$user = User::find(1);

Find or Fail

try {
    $user = User::findOrFail(1);
} catch (\drahil\Stutter\Exceptions\ModelNotFoundException $e) {
    // Handle not found
}

Create a Record

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

Update a Record

$user = User::find(1);
$user->update([
    'name' => 'Jane Doe'
]);

Delete a Record

$user = User::find(1);
$user->delete();

Query Builder

Basic Where Clause

$users = User::query()->where('active', true)->get();

Select Specific Columns

$users = User::query()->select(['id', 'name'])->get();

Limit Results

$users = User::query()->limit(10)->get();

Order Results

$users = User::query()->orderBy('created_at', 'desc')->get();

Get First Result

$user = User::query()->where('email', 'john@example.com')->first();

Count Results

$count = User::query()->count();

Check Existence

$exists = User::query()->where('email', 'john@example.com')->exists();

Where In Clause

$users = User::query()->whereIn('id', [1, 2, 3])->get();

Relationships

Define Relationships

// User model
public function profile()
{
    return $this->hasOne(Profile::class);
}

public function posts()
{
    return $this->hasMany(Post::class);
}

// Profile model
public function user()
{
    return $this->belongsTo(User::class);
}

// Post model
public function user()
{
    return $this->belongsTo(User::class);
}

public function tags()
{
    return $this->belongsToMany(Tag::class, 'post_tag', 'post_id', 'tag_id');
}

// Tag model
public function posts()
{
    return $this->belongsToMany(Post::class, 'post_tag', 'tag_id', 'post_id');
}

Using Relationships

One-to-One

// Get a user's profile
$profile = User::find(1)->profile()->get();

// Get a profile's user
$user = Profile::find(1)->user()->get();
Create Related Record
// Create a profile for a user
$profile = User::find(1)->profile()->create([
    'bio' => 'New bio for user'
]);

One-to-Many

// Get a user's posts
$posts = User::find(1)->posts()->get();

// Get a post's user
$user = Post::find(1)->user()->get();
Create Related Records
// Create a profile for a user
$profile = User::find(1)->profile()->create([
    'bio' => 'New bio for user'
]);

Many-to-Many

// Get a post's tags
$tags = Post::find(1)->tags()->get();

// Get a tag's posts
$posts = Tag::find(1)->posts()->get();
Attach Related Records
// Attach tags to a post
Post::find(1)->tags()->attach([1, 2, 3]);
Detach Related Records
// Detach tags from a post
Post::find(1)->tags()->detach([2, 3]);

Full Example

Here's a complete example to demonstrate the library usage:

<?php

require_once __DIR__ . '/../vendor/autoload.php';

use drahil\Stutter\Core\ConnectionManager;
use App\Models\User;
use App\Models\Profile;
use App\Models\Post;
use App\Models\Tag;

// Set up database connection
ConnectionManager::addConnection([
    'driver' => 'mysql',
    'host' => 'localhost',
    'database' => 'stutter_demo',
    'username' => 'root',
    'password' => 'password',
    'charset' => 'utf8mb4'
]);

// Basic CRUD operations
$allUsers = User::all();
$user = User::find(1);
$newUser = User::create(['name' => 'John Doe']);
$user->update(['name' => 'Jane Doe']);

// Query builder examples
$activeUsers = User::query()->where('active', true)->get();
$recentUsers = User::query()->orderBy('created_at', 'desc')->limit(5)->get();
$userCount = User::query()->count();

// Relationship examples
$userProfile = User::find(1)->profile()->get();
$userPosts = User::find(1)->posts()->get();
$postTags = Post::find(1)->tags()->get();

Sample Database Schema

To try the examples, you can use the following schema:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE profiles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    bio TEXT,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE posts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    title VARCHAR(255) NOT NULL,
    content TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE tags (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL UNIQUE
);

CREATE TABLE post_tag (
    post_id INT,
    tag_id INT,
    PRIMARY KEY (post_id, tag_id),
    FOREIGN KEY (post_id) REFERENCES posts(id),
    FOREIGN KEY (tag_id) REFERENCES tags(id)
);

-- Insert sample data
INSERT INTO users (name) VALUES 
    ('Alice'),
    ('Bob'),
    ('Charlie');

INSERT INTO profiles (user_id, bio) VALUES
    (1, 'Alice bio'),
    (2, 'Bob bio'),
    (3, 'Charlie bio');

INSERT INTO posts (user_id, title, content) VALUES
    (1, 'Alice Post 1', 'Content 1'),
    (1, 'Alice Post 2', 'Content 2'),
    (2, 'Bob Post 1', 'Content 3');

INSERT INTO tags (name) VALUES
    ('PHP'),
    ('ORM'),
    ('Database');

INSERT INTO post_tag (post_id, tag_id) VALUES
    (1, 1),
    (1, 2),
    (2, 2),
    (2, 3),
    (3, 1);

License

This project is licensed under the MIT License - see the LICENSE file for details.