Models ====== In LacePHP, a **Model** is your gateway to the database. It wraps a single table and provides simple methods for: - **Creating**, **reading**, **updating** and **deleting** rows (CRUD) - **Querying** with fluent `where`, `orderBy`, `limit`, etc. - **Mass assignment** of allowed fields - **Automatic timestamps** or refreshing generated columns - **Defining relations** (`belongsTo`, `hasMany`, `hasOne`, `belongsToMany`) and eager-loading By using Models, you avoid repeating SQL in controllers and keep your data logic in one place. Why Models Matter ----------------- - **DRY & Safe** Put all your table logic in one class, and only allow writing of explicit columns via `fillable`. - **Readable Code** `User::find(1)` is easier to read and refactor than writing raw SQL everywhere. - **Relations** Load related rows (e.g. a post’s author or comments) with simple methods instead of manual joins. - **Testability** Swap in-memory arrays or mocks for Models in your tests, no need to hit a real database. Defining Your First Model ------------------------- Create a file `weave/Models/Post.php`: .. code-block:: php json($posts); } public function show($id): string { // Find by primary key (id) $post = Post::find($id); if (! $post) { return kickback()->notFound("Post not found"); } return kickback()->json($post); } public function store(): string { $data = sole_request()->only(['title','body','user_id']); // Mass-assign and save $post = new Post($data); $post->save(); return kickback()->json($post, 201); } public function update(): string { $post = Post::find($id); if (! $post) { return kickback()->notFound("Post not found"); } // Only fillable fields are updated foreach (sole_request()->only(['title','body']) as $k => $v) { $post->$k = $v; } $post->save(); return kickback()->json($post); } public function destroy($id): string { $post = Post::find($id); if ($post) { $post->delete(); } return kickback()->text('Deleted', 204); } } Query Builder & Fluent Queries ------------------------------- For more complex queries you can use the `query()` method: .. code-block:: php // Get latest 5 posts by user 42 $recent = Post::query() ->where('user_id', '=', 42) ->orderBy('created_at', 'desc') ->limit(5) ->get(); // Count how many posts contain “LacePHP” in the title $count = Post::query() ->where('title', 'LIKE', '%LacePHP%') ->count(); return kickback()->json(['recent' => $recent, 'count' => $count]); Relations & Eager Loading -------------------------- Define relations inside your Model by adding methods that call the helpers: .. code-block:: php class Post extends Model { protected $fillable = ['title', 'body', 'user_id']; // A Post belongs to one User protected function author() { return $this->belongsTo(User::class, 'user_id'); } // A Post has many Comments protected function comments() { return $this->hasMany(Comment::class, 'post_id'); } } Then in your controller: .. code-block:: php // Without eager loading (N+1 problem) foreach (Post::all() as $post) { $post->author; // fires one query per post } // With eager loading—loads authors in one query $posts = Post::query() ->with(['author', 'comments']) ->get(); return kickback()->json($posts); .. note:: **Eager loading** means fetching related data all at once instead of one item at a time. Without eager loading you might run into the “N+1 query” problem: - **Without eager loading**: .. code-block:: php $posts = Post::all(); # 1 query for posts foreach ($posts as $post) { echo $post->author->name; # 1 extra query per post } - **With eager loading**: .. code-block:: php $posts = Post::query() ->with(['author']) ->get(); # 1 query for posts + 1 for authors foreach ($posts as $post) { echo $post->author->name; # no extra queries } **Why it matters** - Performance: far fewer database trips - Predictability: you know exactly how many queries will run - Readability: no hidden queries inside loops Best Practices -------------- - **Fillable** Always set `$fillable` to avoid accidental mass-assignment of sensitive columns. - **Custom table names** If your table isn’t `snake_case` plural, set `protected static $table = 'my_table';`. - **Single responsibility** Keep business logic in services or separate classes—Models should focus on data access. - **Cache heavy queries** Combine with `ShoeCacheKnots` to cache expensive list or report queries. By following these patterns, junior developers can harness LacePHP’s Model class to write clear, maintainable database code—no SQL required. | |