Урок 02

Модели и конвенции

Создание модели

php artisan make:model Post

Флаги:

php artisan make:model Post -m      # + миграция
php artisan make:model Post -f      # + фабрика
php artisan make:model Post -s      # + сидер
php artisan make:model Post -c      # + контроллер
php artisan make:model Post -msfc   # всё сразу
php artisan make:model Post -a      # всё (включая политику, запросы)

Таблица

По умолчанию Post → posts. Множественное число (англ.).

Своё имя:

protected $table = 'my_posts';

Если отключить автоматическое множественное число:

protected $table = 'post';

Первичный ключ

По умолчанию: id, автоинкремент, целое.

Другое имя:

protected $primaryKey = 'post_id';

Не автоинкремент:

public $incrementing = false;

Строковый (UUID):

protected $keyType = 'string';

Пример UUID-модели:

class Post extends Model
{
    protected $primaryKey = 'uuid';
    protected $keyType = 'string';
    public $incrementing = false;

    protected static function booted(): void
    {
        static::creating(function ($post) {
            $post->uuid ??= (string) Str::uuid();
        });
    }
}

Массовое присваивание

Eloquent защищает от подмены полей. По умолчанию все запрещены.

Белый список ($fillable):

protected $fillable = ['title', 'body', 'published_at'];

Чёрный список ($guarded):

protected $guarded = ['id'];

Разрешить всё:

protected $guarded = [];

⚠️ Опасно — можно подменить is_admin через форму.

Правило: используй $fillable. Никогда не открывай всё.

Что делает:

Post::create($request->all());   // только поля из $fillable

Приведение типов — Casts

Поля из БД приходят строками. Casts приводят их к нужному типу.

protected $casts = [
    'published_at' => 'datetime',       // Carbon
    'is_active' => 'boolean',           // true/false
    'options' => 'array',               // JSON ↔ массив
    'meta' => 'json',                   // JSON
    'price' => 'decimal:2',             // 2 знака
    'settings' => AsCollection::class,  // Laravel Collection
    'status' => OrderStatus::class,     // Enum
];

Что даёт:

$post->published_at;         // Carbon, не строка
$post->is_active;            // bool
$post->options['theme'];     // массив
$post->status;               // OrderStatus::Active

Полный список casts:

  • int, integer
  • real, float, double
  • decimal:2
  • string
  • bool, boolean
  • object
  • array, json
  • collection
  • date, datetime, immutable_date, immutable_datetime
  • timestamp
  • encrypted, encrypted:array
  • hashed
  • Имя Enum-класса
  • Имя класса Cast (AsCollection)

Timestamps

Eloquent автоматически управляет created_at и updated_at.

Отключить:

public $timestamps = false;

Переименовать:

const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';

Изменить формат:

protected $dateFormat = 'U';   // Unix timestamp

Скрытые и видимые поля

Не показывать в JSON:

protected $hidden = ['password', 'remember_token'];

Только эти в JSON:

protected $visible = ['id', 'name', 'email'];

Применяется при toArray() и toJson().

Добавление в JSON

Виртуальное поле (вычисляемое):

protected $appends = ['full_name'];

protected function fullName(): Attribute
{
    return Attribute::make(
        get: fn () => "{$this->first_name} {$this->last_name}",
    );
}

Соединение с БД

По умолчанию — default connection. Своё:

protected $connection = 'mysql';

Проверка модели

php artisan model:show Post

Покажет: таблицу, первичный ключ, поля, casts, связи.

Практика: полная модель

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Post extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'user_id', 'title', 'slug', 'body',
        'published', 'published_at',
    ];

    protected $casts = [
        'published' => 'boolean',
        'published_at' => 'datetime',
        'meta' => 'array',
        'views' => 'integer',
    ];

    protected $hidden = ['deleted_at'];
    protected $appends = ['excerpt'];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }

    protected function excerpt(): Attribute
    {
        return Attribute::make(
            get: fn () => mb_substr($this->body, 0, 100) . '...',
        );
    }
}

Частые ошибки

1. Забыть $fillable:

Post::create($request->all());   // все поля проигнорированы

2. Добавить is_admin в $fillable:

protected $fillable = ['name', 'email', 'is_admin'];   // ⚠️
your

3. $guarded = [] на продакшене. Открывает всё.

4. Забыть про casts для дат:

$post->published_at;   // строка, а не Carbon

5. Ошибка в имени таблицы:

class UserGroup extends Model  // таблица 'user_groups', не 'usergroups'

Итог

  • Модель → таблица во множественном числе.
  • $fillable — белый список.
  • $casts — типы.
  • $hidden — скрыть в JSON.
  • $appends — добавить в JSON.
  • Timestamps — авто, отключаются через $timestamps = false.
  • Правило: заполняй явно, скрывай секреты.

Дальше — CRUD: создание и чтение.