GitHub

Database Models

TinyMVC provides an active record implementation through the Model class, allowing you to interact with your database tables using object-oriented syntax.

Creating Models

To create a new model, use the Spark CLI:

php spark make:model User

Models are stored in the app/Models folder and extend the base Spark\Database\Model class:

<?php

namespace App\Models;

use Spark\Database\Model;

class User extends Model
{
    /**
     * The database table name
     */
    public static string $table = 'users';

    /**
     * Fields that are not mass assignable
     */
    protected array $guarded = [];
}

Basic CRUD Operations

Creating Records

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

// Create a new model then save
$user = new User();
$user->name = 'John Doe';
$user->email = 'john@example.com';
$user->save();

// Load a model from an array
$user = User::load([
    'name' => 'John Doe',
    'email' => 'john@example.com'
]);
$user->save();

// Load a model from an input object
$user = User::load(input(['name', 'email']));
$user->save();

// Insert multiple records
User::insert([
    ['name' => 'Jane Doe', 'email' => 'jane@example.com'],
    ['name' => 'Alice Smith', 'email' => 'alice@example.com']
]);

Reading Records

// Find by ID
$user = User::find(1);

// This will throw a 404 error if not found
$user = User::findOrFail(1);

// Get all records
$users = User::all();

// Get first record
$user = User::first();

// Get last record
$user = User::last();

// Get Models using query builder
$users = User::where('status', 'active')
    ->orderDesc('created_at')
    ->get();

Updating Records

// Find then update
$user = User::find(1);
$user->name = 'Updated Name';
$user->save();

// Update directly
User::update(['name' => 'Updated Name'], ['id' => 1]);

// Mass update
User::where('status', 'pending')
    ->update(['status' => 'active']);

Deleting Records

// Find then delete
$user = User::find(1);
$user->remove();

// Destroy
User::destroy(1);

// Direct delete
User::delete(['id' => 1]);

// Direct delete with where clause
User::where('last_login', '<', '2020-01-01')->delete();

Query Builder Integration

Models inherit all QueryBuilder methods:

// Complex queries
$users = User::where('status', 'active')
    ->where('created_at', '>', '2023-01-01')
    ->orderAsc('name')
    ->all();

// Joins
$posts = Post::select(['p.*', 'u.name as author'])
    ->as('p')
    ->join('users as u', 'u.id = p.user_id')
    ->where('p.published', 1)
    ->all();

Pagination

The paginate method makes it easy to paginate query results:

// Basic pagination (10 items per page)
$users = User::paginate();

// Custom pagination (25 items per page)
$users = User::paginate(25);

// Displaying paginated results
foreach ($users as $user) {
    echo $user->name;
}

// Rendering pagination links
echo $users->links();

// Get the pagination as array
$data = $users->toArray();

Pagination Methods

// Check if data exists
if ($users->hasData()) {
    // Process data
}

// Get paginated data
$data = $users->items();

// Custom pagination links
if ($users->hasLinks()) {
    echo $users->links(
        links: 3, // Number of links to show
        classes: [
            'container' => 'pagination',
            'item' => 'page-item',
            'link' => 'page-link'
        ],
        entity: [
            'previous' => '«',
            'next' => '»'
        ]
    );
}

Mass Assignment

Control which fields can be mass assigned:

class User extends Model
{
    // Only these fields can be mass assigned
    protected array $fillable = ['name', 'email'];
    
    // Or specify guarded fields (opposite of fillable)
    protected array $guarded = ['id', 'is_admin'];
}

Model Methods

// Convert model to array
$array = $user->toArray();

// Create model from array
$user = User::load(['name' => 'John', 'email' => 'john@example.com']);

// Get query builder instance
$query = User::query();

Full Example

// Blog controller example
public function index()
{
    $posts = Post::with('author')
        ->where('published', 1)
        ->orderDesc('created_at')
        ->paginate(15);

    return view('posts.index', ['posts' => $posts]);
}

// User registration example
public function store(Request $request)
{
    $user = User::create(
        $request->input(['name', 'email', 'password'])
    );

    return redirect('/dashboard')
        ->with('message', 'Registration successful!');
}

ORM Relationships

TinyMVC provides powerful ORM capabilities, allowing you to define and work with model relationships.

Defining Relationships

Relationships are defined in your model:

<?php

namespace App\Models;

use Spark\Database\Model;
use Spark\Database\Relation\HasMany;
use Spark\Database\Relation\HasOne;
use Spark\Database\Relation\BelongsToMany;


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

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

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

Relationship Types

One-to-One

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

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

One-to-Many

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

Many-to-Many

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

Loading Relationships

Eager Loading (with)

// Load single relationship
$users = User::with('posts')->all();

// Load multiple relationships
$users = User::with(['posts', 'profile'])->all();

Lazy Loading

$user = User::find(1);

// Access relationship (loaded when first accessed)
$posts = $user->posts;
$profile = $user->profile;

Manual Relationship Loading

$post = Post::findOrFail(1);

// Load user and comments for the post
$post->attachRelation('user', 'comments');

Full Example

// Blog controller with eager loading
public function show($userId)
{
    $user = User::with(['posts', 'profile'])
        ->where('id', $userId)
        ->first();
    
    return view('users.show', compact('user'));
}

// Accessing relationships in views
<h1>{{ $user->name }}</h1>
<h2>Profile</h2>
<p>{{ $user->profile->bio }}</p>

<h2>Posts</h2>
@foreach ($user->posts as $post)
    <article>
        <h3>{{ $post->title }}</h3>
        <p>{{ $post->excerpt }}</p>
    </article>
@endforeach
Performance Tip: Always use eager loading (with()) when you know you'll need related data to avoid the N+1 query problem.