Models
ApnaPHP provides a powerful Eloquent-inspired ORM that makes working with your database intuitive and expressive. Models allow you to interact with your database tables using PHP classes.
Creating Models
Using CLI Command
The easiest way to create a model is using the CLI:
php apna make:model User
This creates a new model file at models/User.php:
<?php
namespace App\Models;
use ApnaPHP\Database\Model;
class User extends Model
{
protected string $table = 'users';
protected string $primaryKey = 'id';
// Enable auto-migration
protected bool $autoMigrate = true;
// Define the schema for this model
protected array $schema = [
'name' => 'required|type:string|length:255',
'email' => 'required|unique|type:string|length:255',
'password' => 'required|type:string',
];
// Define fillable attributes
protected array $fillable = [
'name', 'email', 'password'
];
// Define hidden attributes
protected array $hidden = [
'password'
];
}
Manual Creation
You can also create models manually by creating a new PHP file in the models/ directory.
Model Properties
Basic Configuration
class User extends Model
{
// Table name (optional - defaults to pluralized class name)
protected string $table = 'users';
// Primary key (optional - defaults to 'id')
protected string $primaryKey = 'id';
// Enable auto-migration (optional - defaults to false)
protected bool $autoMigrate = true;
}
Schema Definition
Define your table structure using the $schema property:
protected array $schema = [
'name' => 'required|type:string|length:255',
'email' => 'required|unique|type:string|length:255',
'age' => 'type:integer|min:18|max:120',
'bio' => 'type:text|nullable',
'is_active' => 'type:boolean|default:true',
'created_at' => 'type:timestamp|default:CURRENT_TIMESTAMP',
'updated_at' => 'type:timestamp|default:CURRENT_TIMESTAMP|on_update:CURRENT_TIMESTAMP'
];
Schema Rules
| Rule | Description | Example |
|---|---|---|
required |
Field is required | 'name' => 'required' |
type:type |
Data type | 'age' => 'type:integer' |
length:n |
String length | 'name' => 'length:255' |
unique |
Unique constraint | 'email' => 'unique' |
nullable |
Allow null values | 'bio' => 'nullable' |
default:value |
Default value | 'status' => 'default:active' |
min:n |
Minimum value | 'age' => 'min:18' |
max:n |
Maximum value | 'age' => 'max:120' |
Fillable & Hidden Attributes
class User extends Model
{
// Attributes that can be mass assigned
protected array $fillable = [
'name', 'email', 'password', 'bio'
];
// Attributes that should be hidden from JSON output
protected array $hidden = [
'password', 'remember_token'
];
// Attributes that should be guarded from mass assignment
protected array $guarded = [
'id', 'created_at', 'updated_at'
];
}
Basic Operations
Creating Records
// Using create method
$user = User::create([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'secret123'
]);
// Using new instance
$user = new User();
$user->name = 'Jane Doe';
$user->email = 'jane@example.com';
$user->password = 'secret123';
$user->save();
Retrieving Records
// Get all users
$users = User::all();
// Find by ID
$user = User::find(1);
// Find or fail (throws exception if not found)
$user = User::findOrFail(1);
// Get first record
$user = User::first();
// Get first or fail
$user = User::firstOrFail();
Updating Records
// Update specific record
$user = User::find(1);
$user->name = 'Updated Name';
$user->save();
// Mass update
User::where('status', 'inactive')->update(['status' => 'active']);
// Update or create
$user = User::updateOrCreate(
['email' => 'john@example.com'],
['name' => 'John Updated', 'status' => 'active']
);
Deleting Records
// Delete specific record
$user = User::find(1);
$user->delete();
// Mass delete
User::where('status', 'inactive')->delete();
// Soft delete (if enabled)
$user->delete(); // Marks as deleted but keeps in database
Query Builder Methods
Where Clauses
// Simple where
$users = User::where('status', 'active')->get();
// Multiple conditions
$users = User::where('status', 'active')
->where('age', '>', 18)
->get();
// Where in
$users = User::whereIn('id', [1, 2, 3])->get();
// Where between
$users = User::whereBetween('age', [18, 65])->get();
// Where null
$users = User::whereNull('deleted_at')->get();
// Where not null
$users = User::whereNotNull('email_verified_at')->get();
Ordering & Limiting
// Order by
$users = User::orderBy('created_at', 'desc')->get();
// Multiple order by
$users = User::orderBy('status')
->orderBy('name', 'asc')
->get();
// Limit
$users = User::limit(10)->get();
// Skip and take
$users = User::skip(10)->take(5)->get();
Aggregates
// Count
$count = User::count();
// Sum
$total = User::sum('age');
// Average
$average = User::avg('age');
// Min/Max
$min = User::min('age');
$max = User::max('age');
Relationships
One-to-Many
// User model
class User extends Model
{
protected string $table = 'users';
protected bool $autoMigrate = true;
protected array $schema = [
'name' => 'required|type:string|length:255',
'email' => 'required|unique|type:string|length:255',
];
// Define relationship
public function posts()
{
return $this->hasMany(Post::class, 'user_id');
}
}
// Post model
class Post extends Model
{
protected string $table = 'posts';
protected bool $autoMigrate = true;
protected array $schema = [
'title' => 'required|type:string|length:255',
'content' => 'required|type:text',
'user_id' => 'required|type:integer|foreign_key:users,id',
];
// Define relationship
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
}
Using Relationships
// Get user with posts
$user = User::with('posts')->find(1);
// Get post with user
$post = Post::with('user')->find(1);
// Access related data
echo $user->posts->count();
echo $post->user->name;
// Create related record
$user->posts()->create([
'title' => 'New Post',
'content' => 'Post content here'
]);
Model Events
Lifecycle Hooks
class User extends Model
{
// Called before saving
protected function beforeSave()
{
// Hash password before saving
if (isset($this->password)) {
$this->password = password_hash($this->password, PASSWORD_DEFAULT);
}
}
// Called after saving
protected function afterSave()
{
// Send welcome email after user creation
if ($this->wasRecentlyCreated) {
// Send email logic here
}
}
// Called before deleting
protected function beforeDelete()
{
// Clean up related data
$this->posts()->delete();
}
}
Custom Methods
Instance Methods
class User extends Model
{
// Check if user is admin
public function isAdmin()
{
return $this->role === 'admin';
}
// Get full name
public function getFullNameAttribute()
{
return $this->first_name . ' ' . $this->last_name;
}
// Verify password
public function verifyPassword($password)
{
return password_verify($password, $this->password);
}
}
Static Methods
class User extends Model
{
// Get active users
public static function active()
{
return static::where('status', 'active');
}
// Get users by role
public static function byRole($role)
{
return static::where('role', $role);
}
// Search users
public static function search($query)
{
return static::where('name', 'like', "%{$query}%")
->orWhere('email', 'like', "%{$query}%");
}
}
// Usage
$activeUsers = User::active()->get();
$admins = User::byRole('admin')->get();
$searchResults = User::search('john')->get();
Scopes
Local Scopes
class User extends Model
{
// Active scope
public function scopeActive($query)
{
return $query->where('status', 'active');
}
// Age scope
public function scopeAgeBetween($query, $min, $max)
{
return $query->whereBetween('age', [$min, $max]);
}
// Recent scope
public function scopeRecent($query, $days = 30)
{
return $query->where('created_at', '>=', now()->subDays($days));
}
}
// Usage
$activeUsers = User::active()->get();
$adults = User::ageBetween(18, 65)->get();
$recentUsers = User::recent(7)->get();
Model Factories (Coming Soon)
Model factories will help you generate fake data for testing:
// Future feature
$user = User::factory()->create();
$users = User::factory()->count(10)->create();
Best Practices
1. Use Fillable Properties
Always define $fillable to prevent mass assignment vulnerabilities:
protected array $fillable = [
'name', 'email', 'password'
];
2. Hide Sensitive Data
Use $hidden to hide sensitive information from JSON output:
protected array $hidden = [
'password', 'remember_token', 'api_token'
];
3. Use Auto-Migration
Enable $autoMigrate for development to automatically create tables:
protected bool $autoMigrate = true;
4. Define Relationships
Create proper relationships between models:
public function posts()
{
return $this->hasMany(Post::class, 'user_id');
}
5. Use Scopes for Reusable Queries
Create scopes for commonly used query patterns:
public function scopeActive($query)
{
return $query->where('status', 'active');
}
Common Patterns
User Authentication Model
class User extends Model
{
protected string $table = 'users';
protected bool $autoMigrate = true;
protected array $schema = [
'name' => 'required|type:string|length:255',
'email' => 'required|unique|type:string|length:255',
'password' => 'required|type:string',
'role' => 'type:string|default:user|length:50',
'status' => 'type:string|default:active|length:50',
'email_verified_at' => 'type:timestamp|nullable',
'remember_token' => 'type:string|nullable|length:100',
];
protected array $fillable = [
'name', 'email', 'password', 'role', 'status'
];
protected array $hidden = [
'password', 'remember_token'
];
// Authentication methods
public function isAdmin()
{
return $this->role === 'admin';
}
public function isActive()
{
return $this->status === 'active';
}
public function verifyPassword($password)
{
return password_verify($password, $this->password);
}
// Scopes
public function scopeActive($query)
{
return $query->where('status', 'active');
}
public function scopeByRole($query, $role)
{
return $query->where('role', $role);
}
}
This covers the essential aspects of working with models in ApnaPHP. Models provide a powerful and intuitive way to interact with your database while maintaining clean, readable code.
