Как эффективно использовать functions.php в разработке тем
Файл functions.php является центральным элементом любой темы WordPress. Он позволяет добавлять пользовательские функции, подключать стили и скрипты, регистрировать произвольные типы записей, таксономии и многое другое. В этой статье рассматриваются основные приёмы работы с этим файлом, приводятся примеры кода и поясняются типичные ошибки.
Основное использование и эффективное решение
Самый распространённый сценарий - подключение стилей и скриптов через хуки. Для этого используется функция wp_enqueue_style и wp_enqueue_script, обёрнутые в функцию, которая прикрепляется к действию wp_enqueue_scripts.
// Подключение стилей и скриптов темы
function my_theme_assets() {
wp_enqueue_style( 'my-theme-style', get_stylesheet_uri() );
wp_enqueue_script( 'my-theme-script', get_template_directory_uri() . '/js/script.js', array(), '1.0', true );
}
add_action( 'wp_enqueue_scripts', 'my_theme_assets' );
Пояснение: первый параметр - уникальное имя, второй - путь к файлу. Для стилей можно использовать get_stylesheet_uri(), для скриптов - get_template_directory_uri() или get_stylesheet_directory_uri() в дочерней теме. Параметр true в wp_enqueue_script означает, что скрипт будет загружен в подвале.
Типичная ошибка: вызов wp_enqueue_style вне хука wp_enqueue_scripts. Это может привести к неправильному порядку загрузки или полному отсутствию стилей. Решение: всегда оборачивать подключение в функцию и вешать её на соответствующий хук.
Как добавить произвольный тип записи (например, «Отзывы»)?
function create_testimonial_post_type() {
register_post_type( 'testimonial',
array(
'labels' => array(
'name' => __( 'Отзывы' ),
'singular_name' => __( 'Отзыв' )
),
'public' => true,
'has_archive' => true,
'supports' => array( 'title', 'editor', 'thumbnail' )
)
);
}
add_action( 'init', 'create_testimonial_post_type' );
Проблема: после добавления кода тип записи не появляется. Частая причина - неиспользование функции flush_rewrite_rules() после регистрации. Решение: на время разработки можно добавить вызов flush_rewrite_rules() внутри функции, но потом его лучше убрать и выполнить сброс правил перезаписи через админку (Настройки → Постоянные ссылки → Сохранить).
Как изменить длину отрывка (excerpt) записей?
function custom_excerpt_length( $length ) {
return 20; // количество слов
}
add_filter( 'excerpt_length', 'custom_excerpt_length' );
Ошибка: фильтр не срабатывает, если отрывок задан вручную через поле «Отрывок» в админке - тогда фильтр игнорируется. Решение: использовать get_the_excerpt только для автоматически сгенерированных отрывков.
Как добавить поддержку вложенных меню и произвольные поля?
// Включение поддержки меню в теме
function theme_setup() {
register_nav_menus( array(
'primary' => __( 'Основное меню' )
) );
}
add_action( 'after_setup_theme', 'theme_setup' );
Как добавить поддержку произвольных полей (meta boxes) для страниц?
function add_custom_meta_box() {
add_meta_box(
'custom_meta',
'Дополнительные настройки',
'render_custom_meta_box',
'page',
'normal',
'default'
);
}
add_action( 'add_meta_boxes', 'add_custom_meta_box' );
function render_custom_meta_box( $post ) {
$value = get_post_meta( $post->ID, '_custom_field', true );
echo '<input type="text" name="custom_field" value="' . esc_attr( $value ) . '">';
}
Проблема: при сохранении данные не сохраняются. Забыта функция сохранения через хук save_post. Пример:
function save_custom_meta_box( $post_id ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( ! current_user_can( 'edit_page', $post_id ) ) return;
if ( isset( $_POST['custom_field'] ) ) {
update_post_meta( $post_id, '_custom_field', sanitize_text_field( $_POST['custom_field'] ) );
}
}
add_action( 'save_post', 'save_custom_meta_box' );
Как изменить текст «Читать далее» в отрывках?
function custom_read_more( $more ) {
return '... <a class="read-more" href="' . get_permalink() . '">Подробнее</a>';
}
add_filter( 'excerpt_more', 'custom_read_more' );
Расширенные примеры и нестандартные сценарии
Ниже приведены более сложные примеры использования functions.php, включающие работу с REST API, кешированием и объектно-ориентированным подходом.
Создание шорткода с атрибутами и кешированием
// Шорткод [latest_posts count="5" category="news"]
function latest_posts_shortcode( $atts ) {
$atts = shortcode_atts( array(
'count' => 3,
'category' => ''
), $atts );
$cache_key = 'latest_posts_' . md5( serialize( $atts ) );
$output = get_transient( $cache_key );
if ( false === $output ) {
$args = array(
'posts_per_page' => intval( $atts['count'] ),
'category_name' => sanitize_title( $atts['category'] )
);
$query = new WP_Query( $args );
$output = '<ul>';
while ( $query->have_posts() ) {
$query->the_post();
$output .= '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
}
$output .= '</ul>';
wp_reset_postdata();
set_transient( $cache_key, $output, HOUR_IN_SECONDS );
}
return $output;
}
add_shortcode( 'latest_posts', 'latest_posts_shortcode' );
Результат: выводит список последних записей из указанной категории (или всех) в виде ненумерованного списка. При повторном вызове в течение часа данные берутся из Transients API, что снижает нагрузку на базу данных.
Добавление кастомной конечной точки REST API
function custom_rest_endpoint() {
register_rest_route( 'myplugin/v1', '/data/', array(
'methods' => 'GET',
'callback' => 'handle_custom_data',
'permission_callback' => function() { return current_user_can( 'manage_options' ); }
) );
}
add_action( 'rest_api_init', 'custom_rest_endpoint' );
function handle_custom_data( $request ) {
$param = $request->get_param( 'id' );
// логика получения данных
return array( 'status' => 'ok', 'id' => $param );
}
Результат: по URL /wp-json/myplugin/v1/data?id=5 авторизованный пользователь получает JSON-ответ с переданным id.
Использование классов для организации кода
class ThemeCustomizer {
public function __construct() {
add_action( 'customize_register', array( $this, 'register_settings' ) );
add_action( 'wp_head', array( $this, 'output_custom_css' ) );
}
public function register_settings( $wp_customize ) {
$wp_customize->add_section( 'theme_colors', array(
'title' => 'Цветовые настройки'
) );
$wp_customize->add_setting( 'primary_color', array(
'default' => '#0073aa',
'sanitize_callback' => 'sanitize_hex_color'
) );
$wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, 'primary_color', array(
'label' => 'Основной цвет',
'section' => 'theme_colors'
) ) );
}
public function output_custom_css() {
$color = get_theme_mod( 'primary_color', '#0073aa' );
echo '<style>.primary-btn { background-color: ' . esc_attr( $color ) . '; }</style>';
}
}
new ThemeCustomizer();
Результат: в разделе «Настройки темы» появляется секция для выбора основного цвета, а во фронтенде кнопки с классом primary-btn получают заданный цвет фона.
Условное подключение скриптов только на определённых страницах
function conditional_scripts() {
if ( is_page( 'contact' ) ) {
wp_enqueue_script( 'contact-form-js', get_template_directory_uri() . '/js/contact.js', array(), '1.0', true );
}
if ( is_single() && has_category( 'video' ) ) {
wp_enqueue_style( 'video-style', get_template_directory_uri() . '/css/video.css' );
}
}
add_action( 'wp_enqueue_scripts', 'conditional_scripts' );
Результат: скрипт contact.js загружается только на странице «Контакты», а стили video.css - только на страницах отдельных записей из категории «video».