Introduction

Artisan generates Eloquent models with sensible defaults — but you can override every convention (table name, primary key, timestamps, connection) when your schema doesn't match the expected shape.

Key Concepts

  • make:model generator: The Artisan command that scaffolds a new model, optionally with a migration, factory, seeder, and controller.
  • $table / $primaryKey: Properties that override the inferred table name and primary key column.
  • HasUuids / HasUlids traits: Enable UUID or ULID primary keys instead of auto-incrementing integers.
  • $fillable / $guarded: Allow-lists (or block-lists) controlling which attributes can be mass-assigned.
  • Strict mode: Model::preventLazyLoading, preventSilentlyDiscardingAttributes, preventAccessingMissingAttributes — development helpers that throw on common mistakes.

Real World Context

Every new entity in your app starts as a php artisan make:model command. Knowing the flags (-mfsc, --all, -mfscr --api) saves hours across a project lifecycle, and getting the conventions right up front prevents schema/model drift.

Deep Dive

Learn how to generate models, configure them, and customize Eloquent's conventions for your specific needs.

Generating Models

Use Artisan to create models:

bash
# Basic model
php artisan make:model Post

# Model with migration
php artisan make:model Post -m

# Model with migration, factory, seeder, and controller
php artisan make:model Post -mfsc

# Model with all related files
php artisan make:model Post --all

# API resource controller
php artisan make:model Post -mfscr --api

Basic Model Structure

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;
}

Customizing Table Names

Override the default table name:

php
class Post extends Model
{
    /**
     * The table associated with the model.
     */
    protected $table = 'blog_posts';
}

Customizing Primary Keys

php
class Post extends Model
{
    /**
     * The primary key for the model.
     */
    protected $primaryKey = 'post_id';

    /**
     * Indicates if the primary key is auto-incrementing.
     */
    public $incrementing = false;

    /**
     * The data type of the primary key.
     */
    protected $keyType = 'string';  // For UUIDs
}

Using UUIDs as Primary Keys

php
use Illuminate\Database\Eloquent\Concerns\HasUuids;

class Post extends Model
{
    use HasUuids;

    // Eloquent automatically generates UUIDs
}

// Or use ULIDs (Universally Unique Lexicographically Sortable Identifier)
use Illuminate\Database\Eloquent\Concerns\HasUlids;

class Post extends Model
{
    use HasUlids;
}

Timestamps Configuration

php
class Post extends Model
{
    /**
     * Disable timestamps entirely.
     */
    public $timestamps = false;

    /**
     * Customize timestamp column names.
     */
    const CREATED_AT = 'creation_date';
    const UPDATED_AT = 'last_updated';
}

Database Connection

Use a different database connection:

php
class Post extends Model
{
    /**
     * The database connection for the model.
     */
    protected $connection = 'mysql_secondary';
}

Default Attribute Values

php
class Post extends Model
{
    /**
     * Default values for attributes.
     */
    protected $attributes = [
        'status' => 'draft',
        'is_featured' => false,
    ];
}

// Usage
$post = new Post;
$post->status;  // 'draft' (default)

Enable strict mode to catch issues early:

php
// In AppServiceProvider boot()
use Illuminate\Database\Eloquent\Model;

public function boot(): void
{
    // Prevent lazy loading (use eager loading instead)
    Model::preventLazyLoading(! $this->app->isProduction());

    // Prevent silently discarding attributes
    Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());

    // Prevent accessing missing attributes
    Model::preventAccessingMissingAttributes(! $this->app->isProduction());
}

Mass Assignment Protection

Control which attributes can be mass-assigned:

php
class Post extends Model
{
    /**
     * Attributes that ARE mass assignable.
     */
    protected $fillable = [
        'title',
        'body',
        'category_id',
    ];
}

// Or specify which are NOT mass assignable
class Post extends Model
{
    /**
     * Attributes that are NOT mass assignable.
     */
    protected $guarded = [
        'id',
        'is_admin',
    ];
}

// In a different model, you might turn guarding off entirely (be careful — this
// is almost never a good idea outside of internal admin tooling):
class LegacyImport extends Model
{
    protected $guarded = [];
}

Why Mass Assignment Protection?

php
// Without protection, malicious users could do:
// POST /users with {"name": "John", "is_admin": true}

User::create($request->all());  // is_admin would be set!

// With $fillable = ['name', 'email']:
User::create($request->all());  // is_admin is ignored

Complete Model Example

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use HasFactory, SoftDeletes;

    /**
     * The attributes that are mass assignable.
     */
    protected $fillable = [
        'title',
        'slug',
        'body',
        'published_at',
        'user_id',
        'category_id',
    ];

    /**
     * The attributes that should be hidden for arrays/JSON.
     */
    protected $hidden = [
        'internal_notes',
    ];

    /**
     * Get the attributes that should be cast.
     */
    protected function casts(): array
    {
        return [
            'published_at' => 'datetime',
            'is_featured' => 'boolean',
            'metadata' => 'array',
        ];
    }
}

Common Pitfalls

  1. Disabling mass assignment with $guarded = [] — Everything becomes mass-assignable, including columns you never expected users to set.
  2. Mismatched primary keys with UUID/ULID without the trait — The column is VARCHAR but Eloquent still tries to auto-increment it, leading to errors.
  3. Forgetting strict mode in development — Subtle lazy-loading bugs survive into production.

Best Practices

  1. Generate related files with -mfsc flags — Model, migration, factory, seeder, controller in one command.
  2. Always declare $fillable explicitly — Never rely on $guarded = [].
  3. Enable strict mode — In AppServiceProvider::boot(), gate on !$this->app->isProduction().

Summary

  • php artisan make:model Name generates a model; add flags for related files.
  • Override conventions with $table, $primaryKey, $incrementing, $keyType, $timestamps, $connection.
  • HasUuids / HasUlids traits handle non-integer primary keys.
  • $fillable (allow-list) or $guarded (block-list) protects against mass assignment.
  • Strict mode helpers catch lazy loading and attribute mistakes in development.
✓ Completed