Добавление нового типа контента

← Конфигурация · Назад к README · Harness →

Обзор

Тип контента — это одна Eloquent-модель, наследующая абстрактный Yazar\Models\Document, реализующая Yazar\Contracts\Documentable, в паре с классом, реализующим Yazar\Contracts\Exporter для статической сборки, и зарегистрированная под своим ключом в config('yazar.content_types'). Все типы контента делят одну таблицу documents (см. Конфигурация) и один пайплайн импорта Markdown (DocumentImportService) — добавление типа не требует ни новой миграции, ни нового контроллера, ни нового кода импорта.

Готовые заготовки, с которых можно скопировать: Post, Page, Category в src/Models/ и их экспортёры (PostExporter, PageExporter, CategoryExporter, а также NullExporter для типов, полностью отказывающихся от статического экспорта) в src/Exporters/.

1. Реализовать модель

namespace App\Models; // где угодно в автозагрузке хост-приложения — тип контента не обязан жить в пакете

use Yazar\Exporters\NullExporter; // или собственный экспортёр, см. шаг 2
use Yazar\Models\Document;

class Note extends Document
{
    public static function documentType(): string
    {
        return 'note'; // сохраняется в documents.type; Document::booted() валидирует это при сохранении
    }

    public static function documentsPath(): string
    {
        return 'notes'; // подпапка на диске `content`: {content_path}/notes/*.md
    }

    public static function exporterClass(): string
    {
        return NullExporter::class;
    }

    public static function permalink(): string
    {
        return '/notes/:slug'; // см. URL — на данный момент реализован только :slug
    }
}

Все четыре статических метода приходят из Yazar\Contracts\Documentable (src/Contracts/Documentable.php) и обязательны — сам Document объявлен abstract и не может быть инстанцирован напрямую. documentType() проверяется при сохранении: Document::booted() выбрасывает InvalidArgumentException, если попытаться присвоить атрибут type, не совпадающий с ним, а global scope (document_type) прозрачно фильтрует любой запрос к модели строками этого типа — Note::all() никогда не увидит строку Post, хотя они лежат в одной таблице documents.

Если типу нужны собственные связи (как Category::posts()) — добавляйте их обычным образом Eloquent: помимо контракта Documentable, Note — самая обычная модель.

2. Реализовать (или переиспользовать) экспортёр

Каждый экспортёр реализует Yazar\Contracts\Exporter::export(): void, который BuildCommand вызывает как new $exporterClass($modelClass). Три готовых формы для копирования:

Переменные, которые ваш экспортёр передаёт в view(), должны совпадать с тем, что для той же вьюхи передаёт динамический маршрут — точный набор, который использует ContentController, см. в Шаблонах, включая оговорку про атрибуты (previousPage/nextPage/category), существующие только в динамическом рендеринге.

3. Зарегистрировать тип

// config/yazar.php
'content_types' => [
    'posts' => Post::class,
    'pages' => Page::class,
    'categories' => Category::class,
    'notes' => Note::class, // новый тип
],

Ключ обходится обобщённо везде, где это важно (DocumentImportService/ContentImporter, BuildCommand::exportContentType(), ContentController::show()) — для совершенно нового типа подходит любое имя ключа. Исключение: posts и categories дополнительно читаются по фиксированному ключу в трёх местах внутри ContentController (config('yazar.content_types.posts') в renderMainPage()/renderDocument(), config('yazar.content_types.categories') в showCategoryPage()/renderDocument()). Добавление notes этого не затрагивает — но переименование самих posts или categories потребовало бы правки и этих мест.

4. Добавить контент и вьюху

Создайте {content_path}/notes/ (совпадает с documentsPath()) и положите туда Markdown-файл:

---
view::extends: note
title: First note
created_at: "2026-01-01"
---

Тело заметки здесь.

view::extends: note означает, что где-то среди путей вьюх хост-приложения должна существовать Blade-вьюха note — иначе DocumentImportService отклонит документ на импорте (проверка view()->exists() внутри isValidOptions()). Минимальная вьюха:

@extends('layout')

@section('main')
    <h1>{{ $page->meta->title }}</h1>
    {!! $page->html_content !!}
@endsection

5. Импортировать и проверить

php artisan migrate   # нужно один раз на проект — новая миграция для нового типа не требуется
php artisan build     # либо дождитесь автоимпорта через ImportEmptyContent при следующем запросе в динамическом режиме

Новая миграция не нужна: notes попадают в ту же таблицу documents, что и все остальные типы, различаясь только колонкой type и global scope, объявленным на Note.

Смотри также