← Назад в блог

Сущности WordPress, свои поля и роль functions.php: нормальная модель данных без плагинов

Практический разбор сущностей WordPress: post, custom post types, таксономии и метаполя. Как хранить данные правильно и не превращать functions.php в свалку.

Сущности WordPress, свои поля и роль functions.php: нормальная модель данных без плагинов

Требования

  • WordPress 6.x
  • Базовое понимание PHP
  • Доступ к functions.php темы

Сущности WordPress, свои поля и роль functions.php

Как собрать нормальную модель данных без зоопарка плагинов

WordPress часто воспринимают как «блог с плагинами» — и потом удивляются, почему данные размазаны по полям, таблицам и шорткодам.

Если смотреть на WordPress как на систему хранения данных, а не только как на движок для страниц, всё становится проще и предсказуемее.

Разбираемся по порядку.


Какие сущности есть в WordPress «из коробки»

WordPress — это не только post и page. В ядре есть чёткая модель данных:

Основные сущности

  • Post — базовая запись (post, page, attachment)
  • Custom Post Type (CPT) — пользовательский тип записи
  • Taxonomy — классификатор (категория, тег, любая своя)
  • Term — конкретное значение таксономии
  • Meta — произвольные данные, привязанные к сущности

Главное: WordPress не запрещает проектировать модель данных — он просто не заставляет это делать. Не думаешь об этом — получается хаос; думаешь — получается нормальный бэкенд.


Custom Post Types и таксономии в коде

Когда нужен Custom Post Type

Если сущность:

  • имеет свой жизненный цикл;
  • имеет отдельный набор полей;
  • должна жить независимо от темы;

значит, это CPT, а не «страница с полями».

Минимальная регистрация CPT

// functions.php
add_action('init', function () {
    register_post_type('project', [
        'label' => 'Проекты',
        'public' => true,
        'supports' => ['title', 'editor'],
    ]);
});

Лишних аргументов нет. Этого достаточно, чтобы в админке появилась сущность «Проекты», записи сохранялись в БД и отображались. Для фронта часто добавляют 'has_archive' => true и 'rewrite' => ['slug' => 'projects'] — тогда будет архив по адресу /projects/. Готовый вариант в ООП (PHP 8.1+) с таксономией: сниппет «CPT и таксономия (OOP)».


Таксономии — не только категории

Таксономия — это справочник, а не просто «категории».

add_action('init', function () {
    register_taxonomy('project_type', ['project'], [
        'label' => 'Тип проекта',
        'hierarchical' => true,
    ]);
});

Теперь:

  • project — сущность;
  • project_type — классификатор;
  • term — конкретное значение («Сайт», «Сервис», «Интеграция»).

Это нормальная реляционная модель, просто поверх MySQL.


Пользовательские поля (meta)

Что такое meta в WordPress

Meta — это ключ → значение, привязанное к сущности.

Есть:

  • post_meta
  • user_meta
  • term_meta
  • comment_meta

Учти: meta не заменяет поля сущности; meta — это дополнение, а не помойка для всего подряд.


Пример: мета-поле для CPT

Допустим, у проекта есть URL репозитория. Ключ метаполя начинается с подчёркивания (_repo_url) — так WordPress не показывает его в блоке «Произвольные поля» в редакторе (рекомендация в документации).

Добавляем метабокс

add_action('add_meta_boxes', function () {
    add_meta_box(
        'project_repo',
        'Репозиторий',
        'render_project_repo_box',
        'project'
    );
});

function render_project_repo_box($post) {
    wp_nonce_field('project_repo_save', 'project_repo_nonce');
    $value = get_post_meta($post->ID, '_repo_url', true);
    ?>
    <input
        type="url"
        name="project_repo_url"
        value="<?= esc_attr($value); ?>"
        style="width:100%;"
    />
    <?php
}

Сохраняем значение (с проверкой nonce и прав)

По документации WordPress при сохранении метаполей нужно: проверить nonce, пропустить автосохранение и убедиться, что у пользователя есть право редактировать запись.

add_action('save_post_project', function ($post_id) {
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    if (!isset($_POST['project_repo_nonce'])
        || !wp_verify_nonce($_POST['project_repo_nonce'], 'project_repo_save')) {
        return;
    }
    if (!current_user_can('edit_post', $post_id)) {
        return;
    }
    if (!isset($_POST['project_repo_url'])) {
        return;
    }

    update_post_meta(
        $post_id,
        '_repo_url',
        esc_url_raw($_POST['project_repo_url'])
    );
});

Это не ACF и не магия — чистый WordPress Core API: get_post_meta(), update_post_meta(). Вариант в ООП с nonce и проверкой прав: сниппет «Метабокс с безопасным сохранением (OOP)».

Вывод в шаблоне

В цикле или на single-странице проекта URL репозитория можно вывести так:

$repo_url = get_post_meta(get_the_ID(), '_repo_url', true);
if ($repo_url) {
    printf(
        '<a href="%s" target="_blank" rel="noopener">Репозиторий</a>',
        esc_url($repo_url)
    );
}

Третий параметр true у get_post_meta() возвращает одно значение (строку); без него вернётся массив всех значений по ключу — это важно, если у одного ключа может быть несколько записей. Хелпер для типобезопасного чтения/записи мета в ООП: сниппет «Post meta helper (OOP)».


Роль functions.php: почему это не «помойка»

Что такое functions.php на самом деле

functions.php — это bootstrap темы, а не место для логики.

Его задача:

  • подключить код;
  • зарегистрировать хуки;
  • ничего не «решать» самому.

Плохая практика

// 800 строк кода
// CPT
// AJAX
// SQL
// API
// cron

Такой functions.php:

  • нельзя переиспользовать;
  • страшно трогать;
  • больно переносить на другую тему.

Хорошая практика

// functions.php
require_once __DIR__ . '/inc/cpt.php';
require_once __DIR__ . '/inc/taxonomies.php';
require_once __DIR__ . '/inc/meta.php';

Мини-гайд по структуре кода

Пример структуры темы

theme/
├── functions.php
├── inc/
│   ├── cpt.php
│   ├── taxonomies.php
│   ├── meta.php
│   └── hooks.php
  • functions.php — точка входа
  • inc/* — логика
  • данные не привязаны к шаблонам

Готовый загрузчик inc-модулей в ООП: сниппет «Theme bootstrap (inc loader)».


Подготовка к смене темы

Если:

  • CPT,
  • таксономии,
  • бизнес-логика

живут в теме — это технический долг. В документации прямо сказано: при смене темы зарегистрированные в ней типы записей пропадут из админки. Данные в БД останутся, но управлять ими будет нечем.

Дальше: вынести это в отдельный плагин или mu-plugin и оставить в теме только отображение.


Где грань: тема или плагин

Можно оставить в теме, если:

  • это учебный проект;
  • данные не критичны;
  • тема не будет меняться.

Нужно выносить в плагин, если:

  • данные — бизнес-ценность;
  • проект живёт годами;
  • возможна смена темы.

WordPress это допускает. Ответственность за выбор — на разработчике.


Итоговый чек-лист для разработчика

Перед тем как писать код, задай себе вопросы:

  • Что здесь является отдельной сущностью?
  • Это page или нужен CPT?
  • Это поле или справочник (taxonomies)?
  • Meta — действительно лучший вариант?
  • Где лежит бизнес-логика?
  • Смогу ли я сменить тему без потери данных?

Если на эти вопросы есть ответы — ты используешь WordPress как платформу, а не как конструктор.


Коротко

WordPress не мешает делать правильно — он просто не заставляет. Дальше либо выстраиваешь нормальную модель данных, либо плодишь плагины «на всё подряд». Выбор за разработчиком.


FAQ

Чем CPT отличается от обычной страницы с произвольными полями?

CPT — отдельный тип записи со своей таблицей в БД, своими правами доступа и своим URL (например, /project/slug/). Страница с полями — это одна запись типа page; для десятков «проектов» придётся плодить страницы и метаполя, без нормальной архивации и выборок.

Зачем префикс подчёркивания у ключа метаполя (_repo_url)?

Ключи, начинающиеся с _, WordPress не показывает в блоке «Произвольные поля» в редакторе. Так свои служебные поля не путаются с теми, что редактор может случайно править.

Обязательно ли проверять nonce и права при сохранении мета?

Да. Иначе любой запрос с подставленным $_POST может изменить мета любой записи. В документации add_meta_box явно рекомендуют nonce и current_user_can('edit_post', $post_id).

Пропадут ли данные при смене темы, если CPT зарегистрирован в теме?

Данные в БД останутся, но в админке тип записей исчезнет — управлять ими будет нечем, пока не подключишь плагин или mu-plugin с той же регистрацией CPT. Поэтому для «долгих» проектов регистрацию лучше выносить в плагин.


Полезные ссылки

Сниппеты по теме статьи (OOP, PHP 8+):

0 просмотров

Комментарии

Загрузка комментариев...
Пока нет комментариев. Будьте первым!