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.