ApnaPHP

Documentation

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.