Стандарт разработки сайтов на 1С-Битрикс и WordPress
Разработка сайта не заканчивается после того, как нужная функция появилась
на экране. Важно, чтобы решение оставалось безопасным, производительным
и понятным, не мешало обновлению CMS и могло развиваться без постоянного
переписывания уже созданного функционала.
Я разрабатываю сайты и отдельные модули по собственному техническому стандарту,
основанному на официальной документации 1С-Битрикс и WordPress, отраслевых
практиках PHP-разработки и опыте поддержки реальных проектов. Конкретная
реализация зависит от архитектуры, версии CMS, состава установленных модулей
и задач проекта, но основные принципы остаются неизменными.
Восемь правил, которые действуют одинаково на 1С-Битрикс и на WordPress.
Всё, что описано ниже в разделах по конкретным платформам, — это способы
соблюсти именно их. В каждом подразделе примеры даны отдельно для каждой CMS.
01
Ядро не трогаем
Доработки живут отдельно от платформы и переживают обновления.
02
Документированные API
ORM, события, публичные функции — вместо запросов в служебные таблицы.
03
Разделение слоёв
Данные, бизнес-правила, интеграции и представление — в разных файлах.
04
История в Git
Видно, когда и зачем внесена доработка, и как вернуть рабочее состояние.
05
Копия перед боем
Опасные изменения проверяются на тестовой среде, а не на живом сайте.
06
Безопасность в проекте
Проверка прав и данных закладывается при проектировании, а не после.
07
Измеримая скорость
Запросы, кеш и вес ресурсов контролируются по замерам, а не «на глаз».
08
Описанные решения
Нестандартный модуль или обмен сопровождается технической запиской.
Не изменять ядро системы
Системные файлы 1С-Битрикс, WordPress и сторонних расширений не предназначены
для размещения проектных доработок. Прямое редактирование таких файлов может
привести к потере изменений при обновлении, конфликтам версий и сложностям
при дальнейшем сопровождении сайта.
Новый функционал размещается отдельно от ядра:
в каталоге /local/, собственных компонентах и модулях на 1С-Битрикс;
в теме, дочерней теме или отдельном плагине на WordPress;
в самостоятельных классах и сервисах, если задача требует отдельного слоя бизнес-логики.
1С-Битрикс
/bitrix/modules/iblock/classes/general/result.phpКак не надоPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
// Правка прямо в файле модуля: «быстро добавить наценку».// Обновление модуля перезапишет файл — доработка исчезнет// без следов, а поиск причины займёт часы.publicfunctionGetNext($textHtmlAuto=true,$useTilda=true){$result=parent::GetNext($textHtmlAuto,$useTilda);if($result&&isset($result['PROPERTY_PRICE_VALUE'])){$result['PROPERTY_PRICE_VALUE']*=1.15;}return$result;}
/local/php_interface/init.phpКак надоPHP
1
2
3
4
5
6
7
8
9
useBitrix\Main\EventManager;// Та же задача через штатную точку расширения: файл лежит// вне ядра, обновление модуля его не касается.EventManager::getInstance()->addEventHandler('iblock','OnAfterIBlockElementUpdate',['\Project\Catalog\PriceHandler','onElementUpdate']);
Обработчик лежит в /local/, регистрируется в init.php и переживает любое обновление модуля «Информационные блоки».
WordPress
wp-content/plugins/woocommerce/includes/wc-product-functions.phpКак не надоPHP
1
2
3
4
5
6
7
8
9
10
// Правка в файле стороннего плагина. Автообновление// WooCommerce вернёт исходный файл, наценка тихо пропадёт,// и «цены сломались» придётся искать с нуля.functionwc_get_price_to_display($product,$args=array()){$price=$args['price']??$product->get_price();$price=(float)$price*1.15;return$price;}
// Та же задача через документированный фильтр WooCommerce.// Код лежит в своём плагине, обновление магазина его не трогает.add_filter('woocommerce_product_get_price','project_apply_markup',10,2);/**
* @param string $price Цена товара.
* @param WC_Product $product Товар.
* @return string
*/functionproject_apply_markup($price,$product){if($price===''||!$product->is_type('simple')){return$price;}return(string)round((float)$price*1.15,2);}
Фильтр можно снять, заменить и протестировать отдельно; при удалении плагина сайт возвращается к исходному поведению.
Исключение. Правка стороннего решения допустима только при
исправлении критической проблемы, когда штатного механизма расширения нет.
Такое изменение документируется и учитывается при последующих обновлениях.
Использовать документированные API
Для работы с данными и системными функциями применяются официальные API
платформы. Прямые обращения к внутренним таблицам, недокументированным классам
и служебным файлам используются только тогда, когда задача не имеет
поддерживаемого решения и риски такого подхода заранее понятны.
Это позволяет сохранить совместимость с новыми версиями CMS и уменьшить
зависимость проекта от внутреннего устройства конкретной версии платформы.
1С-Битрикс
/local/php_interface/include/catalog.phpКак не надоPHP
1
2
3
4
5
6
7
8
9
10
global$DB;// Прямой запрос в служебные таблицы инфоблоков: не учитывает// права, сайт, активность по датам и кеш. Структура таблиц —// внутреннее дело платформы и меняется между версиями.$rs=$DB->Query("SELECT ID, NAME FROM b_iblock_element
WHERE IBLOCK_ID = 12 AND ACTIVE = 'Y'
ORDER BY SORT ASC LIMIT 20");
useBitrix\Iblock\ElementTable;useBitrix\Main\Loader;Loader::includeModule('iblock');// Штатный ORM: учитывает права, активность, кеш и переживает// смену внутренней структуры таблиц.$elements=ElementTable::getList(['select'=>['ID','NAME'],'filter'=>['=IBLOCK_ID'=>12,'=ACTIVE'=>'Y'],'order'=>['SORT'=>'ASC'],'limit'=>20,'cache'=>['ttl'=>3600],])->fetchAll();
Ключ cache включает штатное кеширование выборки — оптимизация не требует отказа от API.
WordPress
wp-content/themes/project-theme/functions.phpКак не надоPHP
1
2
3
4
5
6
7
8
9
global$wpdb;// Прямой запрос в служебные таблицы: не учитывает статусы,// языки, права доступа и объектный кеш. Любое изменение схемы// или префикса ломает выборку.$rows=$wpdb->get_results("SELECT ID, post_title FROM wp_posts
WHERE post_type = 'product' ORDER BY post_date DESC LIMIT 10");
// Штатный API: учитывает статусы, права, фильтры и объектный кеш.$query=newWP_Query(['post_type'=>'product','post_status'=>'publish','posts_per_page'=>10,'orderby'=>'date','order'=>'DESC','no_found_rows'=>true,// пагинация не нужна'update_post_term_cache'=>false,// термины не читаем'fields'=>'ids',]);$ids=$query->posts;
Флаги no_found_rows и update_post_term_cache убирают лишние запросы — API не мешает оптимизации, если знать его параметры.
Разделять ответственность компонентов
Получение данных, бизнес-логика, формирование представления и интеграция
с внешними системами не должны смешиваться в одном файле. Код разделяется
на логические уровни:
получение и сохранение данных;
проверка и преобразование информации;
бизнес-правила;
интеграции и обмены;
подготовка данных для отображения;
HTML-шаблоны и клиентская логика.
Такой подход упрощает тестирование, поиск ошибок и развитие проекта.
namespaceProject\Order;/**
* Правила скидок. Класс ничего не знает ни о CMS, ни о HTML:
* получает данные на вход, возвращает результат. Поэтому его
* можно покрыть тестами и переиспользовать в обмене с 1С.
*/finalclassDiscountCalculator{privateconstMAX_PERCENT=30;/** @var array<string,int> Скидка по коду группы покупателя. */private$rules;publicfunction__construct(array$rules){$this->rules=$rules;}publicfunctionapply(float$sum,string$group):float{$percent=$this->rules[$group]??0;$percent=min($percent,self::MAX_PERCENT);returnround($sum*(1-$percent/100),2);}}
Компонент вызывает класс в class.php, кладёт результат в arResult и передаёт в шаблон — бизнес-правило описано в одном месте.
namespaceProject\Core\Order;/**
* Тот же слой бизнес-правил в плагине WordPress: ни хуков,
* ни вывода, ни обращений к базе — только расчёт.
*/finalclassDiscountCalculator{privateconstMAX_PERCENT=30;/** @var array<string,int> */privatearray$rules;publicfunction__construct(array$rules){$this->rules=$rules;}publicfunctionapply(float$sum,string$group):float{$percent=min($this->rules[$group]??0,self::MAX_PERCENT);returnround($sum*(1-$percent/100),2);}}
useProject\Core\Order\DiscountCalculator;/**
* Слой интеграции с CMS: хук только собирает данные, вызывает
* расчёт и отдаёт результат. Правил скидок здесь нет.
*/add_filter('woocommerce_calculated_total','project_apply_discount',10,2);functionproject_apply_discount(float$total,WC_Cart$cart):float{$group=(string)get_user_meta(get_current_user_id(),'project_group',true);return(newDiscountCalculator(project_discount_rules()))->apply($total,$group);}
Шаблон темы такой код не содержит вовсе: его дело — вывести уже посчитанные значения.
Хранить историю изменений
Проектный код ведётся в системе контроля версий Git, если это допускают
условия проекта и доступная инфраструктура. История изменений помогает
определить, когда и почему была внесена доработка, сравнить версии файлов
и при необходимости вернуть рабочее состояние.
Системные файлы, резервные копии, кеш, временные данные, пользовательские
загрузки и конфиденциальные настройки не должны без необходимости попадать
в репозиторий. Состав исключений у платформ разный, поэтому и файл
.gitignore у каждой свой.
1С-Битрикс
.gitignoregitignore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Ядро и модули: ставятся и обновляются штатным обновлением платформы
/bitrix/
# Конфигурация с доступами к базе и ключами
/bitrix/.settings.php
/bitrix/php_interface/dbconn.php# Пользовательские загрузки, кеш, логи, бэкапы
/upload/
/bitrix/cache/
/bitrix/managed_cache/
/bitrix/stack_cache/
/bitrix/backup/
/bitrix/tmp/
*.log
# В репозитории — только проектный код
!/local/
Каталог /bitrix/ исключается целиком: там нет ничего нашего. Всё проектное лежит в /local/ — см. принцип «не изменять ядро».
# Ядро: обновляется из админки или через WP-CLI
/wp-admin/
/wp-includes/
/wp-*.php
!/wp-config-sample.php# Конфигурация с доступами к базе, солями и ключами
/wp-config.php# Медиафайлы, кеш, логи, бэкапы
/wp-content/uploads/
/wp-content/cache/
/wp-content/upgrade/
/wp-content/backup*/
/wp-content/debug.log
*.sql
*.tar.gz
# Сторонние темы и плагины ставятся из репозитория WordPress
/wp-content/themes/*
/wp-content/plugins/*
# В репозитории — только собственные тема и плагины
!/wp-content/themes/project-child/
!/wp-content/plugins/project-core/
Сторонние плагины исключаются по маске, а свои — возвращаются точечно. Так репозиторий не разрастается чужим кодом, который и без того версионируется автором.
Разрабатывать и проверять изменения на копии сайта
Сложные и потенциально опасные изменения сначала выполняются в локальной
или тестовой среде. На рабочем сайте проводятся только необходимые операции
по установке и проверке подготовленного решения.
Перед изменением структуры данных, обновлением системы или развёртыванием
крупной доработки создаётся резервная копия файлов и базы данных либо
проверяется наличие актуальной копии со стороны хостинга.
1С-Битрикс
deploy/backup-bitrix.shShell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Доступы читаем из конфигурации платформы, а не храним в скрипте.
STAMP=$(date +%Y-%m-%d_%H-%M)
# --single-transaction не блокирует таблицы InnoDB на живом сайтеmysqldump--single-transaction--quick--default-character-set=utf8mb4 \
-u"$DB_USER"-p"$DB_PASS""$DB_NAME" | gzip > "backup/db-$STAMP.sql.gz"# Проектный код и пользовательские файлы. Кеш не архивируем.tar-czf"backup/code-$STAMP.tar.gz" \
--exclude='bitrix/cache'--exclude='bitrix/managed_cache' \
local/ upload/ bitrix/.settings.php# Копия считается сделанной только после проверкиgzip-t"backup/db-$STAMP.sql.gz" && echo"backup ok"
На копии сначала проверяется обновление платформы, затем доработка — особенно если в проекте есть сторонние решения.
WordPress
deploy/backup-wp.shShell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# WP-CLI сам берёт доступы из wp-config.php — пароль в скрипте не нужен.
STAMP=$(date +%Y-%m-%d_%H-%M)
wp db export - --single-transaction--quick | gzip > "backup/db-$STAMP.sql.gz"# Своя тема, свои плагины и медиафайлы. Кеш и upgrade пропускаем.tar-czf"backup/content-$STAMP.tar.gz" \
--exclude='wp-content/cache'--exclude='wp-content/upgrade' \
wp-content/uploads wp-content/themes/project-child \
wp-content/plugins/project-core wp-config.phpgzip-t"backup/db-$STAMP.sql.gz" && echo"backup ok"# Развёртывание копии: адреса в базе меняются штатной командой,# а не поиском-заменой по дампу — сериализованные данные не поедут.wp search-replace 'https://example.com''https://staging.example.com'--precise
Резервная копия считается сделанной только после проверки — файл нулевого размера обнаруживается в самый неподходящий момент.
Учитывать безопасность на этапе разработки
Безопасность не добавляется после завершения проекта отдельной настройкой.
Она учитывается при проектировании форм, обработчиков, API-методов,
административных разделов и интеграций. В рамках разработки выполняются:
проверка входящих данных;
ограничение допустимых форматов и значений;
безопасный вывод информации;
проверка авторизации и прав доступа;
защита форм и изменяющих данные запросов;
безопасная работа с базой данных;
ограничение доступа к служебным файлам;
защита конфиденциальных ключей и настроек;
безопасная обработка загружаемых файлов.
Проверка технического токена или защитного кода не заменяет
проверку полномочий пользователя. Для каждого действия отдельно определяется,
кто имеет право его выполнять.
namespaceProject\Order;useBitrix\Main\Engine\ActionFilter;useBitrix\Main\Engine\Controller;useBitrix\Main\Engine\Response\AjaxJson;finalclassStatusControllerextendsController{/** Защита запроса: авторизация и CSRF — на уровне контроллера. */protectedfunctiongetDefaultPreFilters():array{return[newActionFilter\Authentication(),newActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_POST]),newActionFilter\Csrf(),];}publicfunctionupdateAction(int$orderId,string$status):?AjaxJson{// Право на операцию проверяется отдельно от CSRF-токена.if(!\CModule::IncludeModule('sale')||$GLOBALS['APPLICATION']->GetGroupRight('sale')<'W'){$this->addError(new\Bitrix\Main\Error('Недостаточно прав'));returnnull;}// Входящие значения — по белому списку, а не «что пришло».if(!in_array($status,['N','P','F'],true)){$this->addError(new\Bitrix\Main\Error('Недопустимый статус'));returnnull;}returnAjaxJson::createSuccess(['status'=>$status]);}}
Типы аргументов объявлены в сигнатуре — ядро приводит и проверяет их до входа в метод.
add_action('wp_ajax_project_update_status','project_update_status');functionproject_update_status():void{// 1. Защита запроса: nonce отсекает сторонний вызов…check_ajax_referer('project_order_action','nonce');// 2. …но право на действие проверяется отдельно.if(!current_user_can('edit_shop_orders')){wp_send_json_error(['message'=>'Недостаточно прав'],403);}// 3. Входящие данные приводим к ожидаемому типу и диапазону.$order_id=isset($_POST['order_id'])?absint($_POST['order_id']):0;$status=isset($_POST['status'])?sanitize_key($_POST['status']):'';if($order_id===0||!in_array($status,['new','paid','done'],true)){wp_send_json_error(['message'=>'Некорректные данные'],400);}// 4. Проверяем, что объект вообще доступен этому пользователю.if(get_post_type($order_id)!=='shop_order'){wp_send_json_error(['message'=>'Заказ не найден'],404);}update_post_meta($order_id,'_project_status',$status);wp_send_json_success(['status'=>$status]);}
Четыре проверки: подлинность запроса, полномочия, формат данных и доступность объекта. Отсутствие любой из них — уязвимость, а не мелочь.
Контролировать производительность
При разработке учитывается не только корректность функции, но и её влияние
на скорость сайта и нагрузку на сервер. Особое внимание уделяется:
количеству запросов к базе данных;
объёму выбираемых данных;
повторным вычислениям;
кешированию;
размеру подключаемых CSS- и JavaScript-файлов;
обработке изображений;
работе фоновых задач;
обращениям к внешним API;
поведению сайта при росте каталога или количества пользователей.
1С-Битрикс
Запрос в циклеКак не надоPHP
1
2
3
4
5
6
7
8
9
10
// N+1: на 500 товарах — 501 запрос к базе.// На витрине это видно сразу, в админке — только под нагрузкой.foreach($productIdsas$id){$price=PriceTable::getList(['filter'=>['=PRODUCT_ID'=>$id],'select'=>['PRICE'],])->fetch();$result[$id]=$price['PRICE']??null;}
Одна выборкаКак надоPHP
1
2
3
4
5
6
7
// Один запрос на всю пачку, выбираем только нужные поля.$rows=PriceTable::getList(['filter'=>['@PRODUCT_ID'=>$productIds],'select'=>['PRODUCT_ID','PRICE'],])->fetchAll();$result=array_column($rows,'PRICE','PRODUCT_ID');
WordPress
Мета в циклеКак не надоPHP
1
2
3
4
5
6
7
8
// Кеш метаданных не прогрет: каждый get_post_meta()// уходит в базу отдельным запросом.$ids=get_posts(['post_type'=>'product','fields'=>'ids','numberposts'=>500]);foreach($idsas$id){$result[$id]=get_post_meta($id,'_price',true);}
Прогрев кеша метаданныхКак надоPHP
1
2
3
4
5
6
7
8
9
10
$ids=get_posts(['post_type'=>'product','fields'=>'ids','numberposts'=>500]);// Один запрос поднимает мету всех записей в объектный кеш —// дальше get_post_meta() читает из памяти.update_meta_cache('post',$ids);foreach($idsas$id){$result[$id]=get_post_meta($id,'_price',true);}
Оптимизация проводится на основании измерений, а не только субъективной оценки
скорости загрузки страницы.
Документировать нестандартные решения
Если в проекте появляется собственный модуль, интеграция, сложный обмен
или нетипичная логика, фиксируются ключевые сведения: назначение решения,
расположение основных файлов, необходимые настройки, зависимости,
используемые события и точки расширения, формат обмена, особенности запуска,
порядок обновления или отключения.
Документация должна позволять разобраться в устройстве функционала
без длительного анализа всего проекта.
1С-Битрикс
/local/modules/project.exchange/include.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Обмен заказами с 1С:УТ 11.
*
* Назначение : выгрузка новых заказов и загрузка статусов оплаты.
* Файлы : /local/modules/project.exchange/lib/
* Настройки : Настройки модуля → project.exchange (URL, логин, ключ)
* Зависимости: модули sale, catalog; PHP 8.1+; curl
* События : OnSaleOrderSaved (постановка в очередь)
* Агент : \Project\Exchange\Agent::run() — каждые 5 минут
* Формат : JSON, спецификация — docs/exchange-1c.md
* Запуск : php -f /local/modules/project.exchange/cli/run.php --once
* Отключение : снять агент и обработчик, данные очереди сохраняются
* Журнал : /local/logs/exchange-YYYY-MM-DD.log, хранится 30 дней
*/
/**
* Plugin Name: Project Exchange
* Description: Обмен заказами и остатками с 1С:УТ 11.
* Version: 2.1.0
* Requires at least: 6.4
* Requires PHP: 8.1
* Requires Plugins: woocommerce
*
* Назначение : выгрузка заказов WooCommerce, загрузка остатков и цен.
* Файлы : src/ (Api, Queue, Cli), шаблоны писем — templates/
* Настройки : WooCommerce → Настройки → Обмен с 1С (URL, ключ)
* Хуки : woocommerce_new_order → постановка в очередь;
* project_exchange_before_push — фильтр полезной нагрузки
* Расписание : событие project_exchange_run, интервал five_minutes,
* запускается системным cron (DISABLE_WP_CRON = true)
* Формат : JSON, спецификация — docs/exchange-1c.md
* Запуск : wp project exchange run --limit=200
* Отключение : деактивация плагина снимает расписание, очередь
* остаётся в таблице {prefix}_project_exchange_queue
* Журнал : WooCommerce → Статус → Журналы, источник project-exchange
*/
Шапка отвечает на вопросы, которые возникают у следующего разработчика в первые пять минут. Заголовок Requires Plugins заодно не даёт активировать плагин без WooCommerce.
Платформа
Стандарт разработки на 1С-Битрикс
Bitrix Framework даёт готовые точки расширения почти для всего: события,
собственные компоненты, модули, ORM, управляемый кеш. Стандарт сводится
к тому, чтобы ими пользоваться, а не обходить их.
Размещение доработок в каталоге /local/
Собственные компоненты, шаблоны, классы, обработчики событий, модули
и другие проектные файлы размещаются преимущественно в каталоге
/local/. Каталог /bitrix/ считается частью платформы
и не используется для хранения собственных доработок, кроме случаев, когда
это обусловлено устройством старого проекта и немедленный перенос может
нарушить его работу.
Структура может отличаться в зависимости от масштаба проекта, но проектный код должен быть отделён от ядра.
Использование D7 и актуального API
Для нового функционала преимущественно используются возможности современного ядра D7:
пространства имён;
автозагрузка классов;
ORM;
события;
конфигурация;
локализация;
штатные классы модулей;
объектный подход к работе с сущностями.
Старое API может применяться в тех частях системы, где современная альтернатива
отсутствует, ограничена или её внедрение создаст неоправданный риск для
существующего проекта. Цель стандарта — не формальный отказ от всех старых
методов, а выбор наиболее поддерживаемого и предсказуемого инструмента
для конкретной задачи.
useBitrix\Main\Loader;// Автозагрузка проектных классов: пространство имён Project\// отображается на /local/classes/Project/ по правилам PSR-4.Loader::registerAutoLoadClasses(null,[]);spl_autoload_register(staticfunction(string$class):void{$prefix='Project\\';if(strpos($class,$prefix)!==0){return;}$relative=substr($class,strlen($prefix));$path=$_SERVER['DOCUMENT_ROOT'].'/local/classes/Project/'.str_replace('\\','/',$relative).'.php';if(is_readable($path)){require_once$path;}});// Обработчики подключаются отдельными файлами по назначению,// а не сваливаются в init.php одним списком.require_once__DIR__.'/include/events/catalog.php';require_once__DIR__.'/include/events/sale.php';
Работа с данными через ORM и API модулей
Для чтения и изменения данных используются ORM и официальные методы
соответствующего модуля. Прямые SQL-запросы допускаются только в обоснованных
случаях: обработка больших объёмов данных, сложная аналитическая выборка
или отсутствие подходящего API.
При работе с базой данных:
выбираются только необходимые поля;
ограничивается количество записей;
учитываются индексы и сортировка;
исключаются запросы внутри циклов;
контролируется количество обращений к базе;
операции изменения выполняются с проверкой результата;
useBitrix\Main\Application;$connection=Application::getConnection();$connection->startTransaction();try{foreach($itemsas$item){$result=OrderItemTable::update($item['ID'],['QUANTITY'=>$item['QUANTITY']]);// Результат операции проверяется всегда: «молча не сохранилось» —// самый дорогой в поддержке класс ошибок.if(!$result->isSuccess()){thrownew\RuntimeException(implode('; ',$result->getErrorMessages()));}}$connection->commitTransaction();}catch(\Throwable$e){$connection->rollbackTransaction();\Bitrix\Main\Diag\Debug::writeToFile($e->getMessage(),'','/local/logs/order.log');throw$e;}
Компоненты и шаблоны
Компонент отвечает за получение и подготовку данных, а шаблон — за их
отображение. Сложная бизнес-логика не размещается непосредственно
в HTML-шаблоне. При разработке компонентов:
параметры имеют понятные названия и значения по умолчанию;
входящие параметры проверяются;
результат содержит только необходимые шаблону данные;
кеш зависит от параметров, влияющих на результат;
динамические области отделяются от кешируемой части;
учитывается работа в составе комплексных компонентов;
AJAX-запросы защищаются и проверяют права пользователя;
useBitrix\Main\Engine\Contract\Controllerable;useBitrix\Main\Localization\Loc;classProjectCatalogListComponentextendsCBitrixComponentimplementsControllerable{/**
* Входящие параметры проверяются и приводятся к типу здесь,
* а не в шаблоне и не в местах использования.
*/publicfunctiononPrepareComponentParams($params):array{$params['SECTION_ID']=(int)($params['SECTION_ID']??0);$params['COUNT']=max(1,min(100,(int)($params['COUNT']??20)));$params['CACHE_TIME']=isset($params['CACHE_TIME'])?(int)$params['CACHE_TIME']:3600;return$params;}publicfunctionexecuteComponent():void{// Кеш зависит от всех параметров, влияющих на результат,// иначе разные разделы получат одинаковый вывод.$cacheKeys=[$this->arParams['SECTION_ID'],$this->arParams['COUNT']];if($this->startResultCache($this->arParams['CACHE_TIME'],$cacheKeys)){$repository=new\Project\Catalog\ElementRepository();$this->arResult['ITEMS']=$repository->findBySection($this->arParams['SECTION_ID'],$this->arParams['COUNT']);// Тегированный кеш: сбросится сам при изменении инфоблока.$this->includeComponentTemplate();}$this->setResultCacheKeys(['ITEMS']);}/** Действия AJAX объявляются явно и проверяют права. */publicfunctionconfigureActions():array{return[];}}
Шаблоны стандартных компонентов копируются в шаблон сайта. Исходные файлы компонента в ядре не редактируются.
Собственные модули
Связанный функционал, который используется в нескольких разделах сайта
или имеет собственные настройки, оформляется в виде отдельного модуля.
Модуль должен самостоятельно:
useBitrix\Main\EventManager;useBitrix\Main\ModuleManager;classproject_exchangeextendsCModule{public$MODULE_ID='project.exchange';publicfunctionDoInstall():bool{// Зависимости проверяются до установки, а не в момент работы.if(!ModuleManager::isModuleInstalled('sale')){$GLOBALS['APPLICATION']->ThrowException('Требуется модуль «Интернет-магазин»');returnfalse;}ModuleManager::registerModule($this->MODULE_ID);$this->installDatabase();$this->installEvents();returntrue;}publicfunctionDoUninstall():bool{// Удаление — зеркало установки: снимаем обработчики,// убираем таблицы, снимаем регистрацию модуля.$this->uninstallEvents();$this->uninstallDatabase();ModuleManager::unRegisterModule($this->MODULE_ID);returntrue;}privatefunctioninstallEvents():void{EventManager::getInstance()->registerEventHandler('sale','OnSaleOrderSaved',$this->MODULE_ID,'\Project\Exchange\Queue','onOrderSaved');}}
Не каждая небольшая доработка требует создания модуля. Архитектура выбирается соразмерно задаче, чтобы не усложнять проект без практической пользы.
Обработчики событий
Для изменения поведения платформы используются события Bitrix Framework.
Обработчики не должны содержать неконтролируемую тяжёлую логику, которая
будет выполняться при каждом сохранении элемента, заказа или пользователя.
Перед регистрацией обработчика определяется:
при каком событии он должен запускаться;
может ли событие вызываться повторно;
требуется ли защита от рекурсии;
как обработчик влияет на скорость операции;
что произойдёт при ошибке внешнего сервиса;
нужен ли перенос тяжёлой операции в очередь или агент.
namespaceProject\Catalog;finalclassPriceHandler{/** Защита от рекурсии: обновление элемента внутри обработчика
* снова вызовет это же событие. */privatestaticbool$inProgress=false;publicstaticfunctiononElementUpdate(array&$fields):void{if(self::$inProgress){return;}self::$inProgress=true;try{// В обработчике — только быстрая операция.// Тяжёлое (обмен, письма, пересчёт каталога) ставим в очередь.Queue::push('recalculate',['id'=>(int)$fields['ID']]);}catch(\Throwable$e){// Ошибка фоновой задачи не должна ломать сохранение элемента.\Bitrix\Main\Diag\Debug::writeToFile($e->getMessage(),'PriceHandler','/local/logs/catalog.log');}finally{self::$inProgress=false;}}}
Обработчики группируются по назначению и не распределяются без системы по одному большому файлу.
Кеширование
Кеширование проектируется вместе с компонентом или сервисом, а не добавляется
после появления проблем с производительностью. В стандарте разработки
учитываются:
useBitrix\Main\Application;useBitrix\Main\Data\Cache;$cache=Cache::createInstance();$cacheTime=86400;$cacheDir='/project/catalog-menu';// Ключ включает всё, что влияет на результат: сайт, язык,// группы пользователя и раздел. Иначе один посетитель увидит// содержимое, подготовленное для другого.$cacheKey=implode('|',[SITE_ID,LANGUAGE_ID,implode(',',\CUser::GetUserGroup($USER->GetID())),$sectionId,]);if($cache->initCache($cacheTime,$cacheKey,$cacheDir)){$items=$cache->getVars();}elseif($cache->startDataCache()){$taggedCache=Application::getInstance()->getTaggedCache();$taggedCache->startTagCache($cacheDir);$taggedCache->registerTag('iblock_id_'.IBLOCK_CATALOG);$items=(newElementRepository())->findBySection($sectionId);$taggedCache->endTagCache();$cache->endDataCache($items);}
Тегированный кеш сбрасывается сам при изменении инфоблока. Кеш не должен скрывать ошибки в архитектуре или заменять оптимизацию неэффективных запросов.
Агенты и фоновые задачи
Регулярные и длительные операции не должны без необходимости выполняться
при открытии страниц пользователями. Для фоновых задач используются агенты
1С-Битрикс, выполнение агентов через cron, отдельные cron-скрипты и очереди,
если они предусмотрены архитектурой проекта.
namespaceProject\Exchange;useBitrix\Main\Config\Option;finalclassAgent{privateconstBATCH=200;// порция за один запускprivateconstLOCK='project_exchange_lock';privateconstEXPIRE=900;// «протухание» блокировки, сек/** Возвращает строку перезапуска — так требует планировщик агентов. */publicstaticfunctionrun():string{$self='\Project\Exchange\Agent::run();';// Один процесс за раз: параллельный запуск обработал бы// одни и те же записи дважды.$lockedAt=(int)Option::get('project.exchange',self::LOCK,0);if($lockedAt>0&&(time()-$lockedAt)<self::EXPIRE){return$self;}Option::set('project.exchange',self::LOCK,(string)time());try{// Данные обрабатываются частями: ограничения времени// и памяти не зависят от объёма очереди.$processed=Queue::process(self::BATCH);Logger::info('Обработано записей: '.$processed);}catch(\Throwable$e){Logger::error($e->getMessage());}finally{Option::set('project.exchange',self::LOCK,'0');}return$self;}}
Задача ведёт журнал выполнения, корректно обрабатывает повторный запуск и не создаёт несколько параллельных процессов над одними данными.
Безопасность на 1С-Битрикс
При создании обработчиков, форм и административных страниц применяются
штатные механизмы платформы. Проверяются:
useBitrix\Main\Application;useBitrix\Main\Loader;Loader::includeModule('project.exchange');$request=Application::getInstance()->getContext()->getRequest();// 1. Право на модуль: 'D' — доступ запрещён.if($APPLICATION->GetGroupRight('project.exchange')<'W'){$APPLICATION->AuthForm('Доступ запрещён');}if($request->isPost()&&$request->getPost('action')==='run'){// 2. Защита изменяющего запроса.if(!check_bitrix_sessid()){ShowError('Сессия истекла, повторите действие');return;}// 3. Входящие параметры: тип, диапазон, белый список.$limit=min(1000,max(1,(int)$request->getPost('limit')));$mode=(string)$request->getPost('mode');if(!in_array($mode,['orders','stocks'],true)){ShowError('Неизвестный режим обмена');return;}$count=\Project\Exchange\Queue::process($limit,$mode);// 4. Безопасный вывод: данные экранируются по контексту.echo'Обработано: '.htmlspecialcharsbx((string)$count);}
Конфиденциальные ключи, пароли и токены не размещаются в публичных файлах, JavaScript-коде и открытом репозитории — их место в .settings.php или переменных окружения.
Обмены и интеграции
Интеграции с 1С, CRM, службами доставки, платёжными системами, маркетплейсами
и другими сервисами проектируются с учётом нестабильности внешнего соединения.
Для интеграции предусматриваются:
журнал запросов и ответов без раскрытия секретных данных;
useBitrix\Main\Web\HttpClient;useBitrix\Main\Web\Json;/**
* Запрос к внешнему сервису: тайм-аут, повторы с нарастающей паузой,
* проверка формата ответа и журнал без секретных данных.
*/publicfunctionsend(string$method,array$payload):array{$client=newHttpClient(['socketTimeout'=>5,// соединение'streamTimeout'=>20,// чтение ответа'waitResponse'=>true,]);$client->setHeader('Content-Type','application/json');$client->setHeader('Authorization','Bearer '.$this->token);for($attempt=1;$attempt<=self::RETRIES;$attempt++){// Ключ идемпотентности: повтор не создаст второй заказ.$client->setHeader('Idempotency-Key',$payload['uuid']);$raw=$client->post($this->url.$method,Json::encode($payload));$status=$client->getStatus();Logger::info($method,['status'=>$status,'attempt'=>$attempt]);if($status===200){$data=Json::decode((string)$raw);// Ответ проверяется: 200 не гарантирует ожидаемую структуру.if(!isset($data['result'])){thrownew\RuntimeException('Некорректный формат ответа');}return$data['result'];}if($status<500){break;// ошибка запроса — повторять бессмысленно}sleep($attempt*2);}thrownew\RuntimeException('Сервис недоступен: '.$method);}
Остановка внешнего сервиса не должна без необходимости блокировать весь сайт.
Проверка проекта на 1С-Битрикс
Перед запуском или передачей крупной доработки проверяются:
работа основных пользовательских сценариев;
административные операции;
права доступа;
кеширование;
отправка почты;
фоновые задачи;
журнал ошибок;
совместимость с используемой версией PHP;
отсутствие изменений в ядре;
показатели производительности;
результаты доступных тестов Монитора качества.
Обновление платформы сначала проверяется на копии сайта — особенно если
в проекте установлены сторонние решения или присутствует унаследованный код.
Платформа
Стандарт разработки на WordPress
WordPress почти не ограничивает разработчика — и именно поэтому нужен стандарт.
Ключевой вопрос каждый раз один: что должно пережить смену темы, а что нет.
Разделение темы и функциональности
Тема отвечает преимущественно за внешний вид сайта и отображение контента.
Функции, которые должны сохраниться после смены дизайна, выносятся
в отдельный плагин.
Остаётся в теме
шаблоны страниц и записей;
вывод контента и меню;
оформление, стили и скрипты вида;
небольшие правки представления.
Уходит в плагин
интеграции и обмены с внешними системами;
формы со сложной обработкой;
собственные типы данных и REST API;
фоновые задачи и бизнес-правила;
административные инструменты;
синхронизация товаров и заказов.
Небольшие изменения оформления могут размещаться в теме. Крупный функционал
не должен зависеть от конкретного шаблона без технической необходимости.
Собственная или дочерняя тема
Если используется готовая сторонняя тема, изменения выполняются в дочерней
теме либо через предусмотренные разработчиком точки расширения. Редактирование
файлов родительской темы не используется как основной способ кастомизации:
обновление перезапишет внесённые изменения.
Для индивидуального проекта может разрабатываться собственная тема с понятной
структурой шаблонов, компонентов интерфейса и подключаемых ресурсов.
/**
* Дочерняя тема: свои стили подключаются зависимостью от родительских,
* версия берётся из filemtime — правки подхватываются без ручного
* сброса кеша браузера.
*/add_action('wp_enqueue_scripts','project_child_assets',20);functionproject_child_assets():void{$path=get_stylesheet_directory().'/assets/css/project.css';if(!is_readable($path)){return;}wp_enqueue_style('project-child',get_stylesheet_directory_uri().'/assets/css/project.css',['parent-style'],filemtime($path));}
Плагины и структура кода
Собственный плагин получает уникальный префикс или пространство имён, чтобы
его функции, классы, настройки и хуки не конфликтовали с другими расширениями.
Внутри плагина разделяются: загрузка и инициализация, административная
и публичная части, работа с данными, API, интеграции, фоновые процессы,
шаблоны, ресурсы, установка и удаление.
/**
* Plugin Name: Project Core
* Description: Бизнес-логика проекта: интеграции, типы данных, обмены.
* Version: 1.4.0
* Requires at least: 6.4
* Requires PHP: 8.1
* Text Domain: project-core
*/declare(strict_types=1);namespaceProject\Core;// Прямое обращение к файлу плагина — не точка входа.if(!defined('ABSPATH')){exit;}constVERSION='1.4.0';define('PROJECT_CORE_FILE',__FILE__);define('PROJECT_CORE_DIR',plugin_dir_path(__FILE__));require_oncePROJECT_CORE_DIR.'vendor/autoload.php';// Инициализация отделена от загрузки файла: так плагин можно// подключить в тестах, не запуская его побочные эффекты.add_action('plugins_loaded',[Plugin::class,'boot']);register_activation_hook(__FILE__,[Installer::class,'activate']);register_deactivation_hook(__FILE__,[Installer::class,'deactivate']);
Для небольшой задачи не создаётся искусственно сложная архитектура. При росте функциональности код разбивается на классы и сервисы.
Использование WordPress API
Для разработки применяются штатные механизмы WordPress:
actions и filters;
Options API;
Settings API;
Metadata API;
Transients API;
HTTP API;
REST API;
Cron API;
Filesystem API;
роли и capabilities;
функции локализации;
штатные функции работы с пользователями, записями и таксономиями.
Прямое подключение внутренних файлов WordPress и копирование логики ядра
избегаются, если задача решается публичным API.
WordPress Coding Standards
PHP, HTML, CSS и JavaScript оформляются с учётом официальных стандартов
WordPress: единое форматирование, понятные названия, документирование функций
и классов, совместимость с экосистемой, локализация интерфейсов, безопасная
обработка данных и корректное использование API.
composer require --devwp-coding-standards/wpcs dealerdirect/phpcodesniffer-composer-installer
# Отчёт по нарушениям стандартаvendor/bin/phpcs
# Автоисправление того, что исправляется механическиvendor/bin/phpcbf
В автономных PHP-библиотеках, не зависящих от устройства WordPress, могут применяться PSR и другие подходящие стандарты. Внутри темы или плагина приоритет отдаётся соглашениям WordPress.
Actions и filters
Изменение поведения WordPress и сторонних расширений выполняется через
доступные actions и filters. Собственные решения также могут предоставлять
документированные хуки для дальнейшего расширения. При подключении обработчика
учитываются момент выполнения, приоритет, количество аргументов, контекст
административной или публичной части, возможность повторного вызова и влияние
на производительность.
Анонимная функцияКак не надоPHP
1
2
3
4
5
// Такой обработчик невозможно снять: remove_filter()// требует ту же ссылку на функцию, а её больше нет.add_filter('the_content',function($content){return$content.'<p>Подпись</p>';},20);
Именованный обработчикКак надоPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
add_filter('the_content','project_append_signature',20);/**
* @param string $content Содержимое записи.
* @return string
*/functionproject_append_signature(string$content):string{// Контекст проверяется явно: фильтр вызывается и в лентах,// и в выдержках, и в письмах.if(!is_singular('post')||!in_the_loop()||!is_main_query()){return$content;}return$content.'<p class="project-signature">Подпись</p>';}// При необходимости обработчик снимается или подменяется:// remove_filter('the_content', 'project_append_signature', 20);
Анонимные функции не используются там, где обработчик в дальнейшем потребуется отключить, заменить или протестировать отдельно.
Работа с базой данных
Для стандартных сущностей применяются функции WordPress, WP_Query
и соответствующие API. Прямые запросы через $wpdb используются
только при необходимости. В прямых запросах пользовательские значения
передаются через подготовленные выражения, выбираются только необходимые поля,
учитываются индексы, исключаются запросы в циклах, контролируется объём
результата и проверяется результат выполнения.
global$wpdb;$table=$wpdb->prefix.'project_orders';// Прямой запрос оправдан: агрегация по миллиону строк через// WP_Query потребовала бы выборки всех записей в память.// Значения подставляются только через prepare().$rows=$wpdb->get_results($wpdb->prepare("SELECT status, COUNT(*) AS cnt, SUM(total) AS amount
FROM {$table}
WHERE created_at BETWEEN %s AND %s
GROUP BY status
LIMIT %d",$from,$to,100),ARRAY_A);if($wpdb->last_error!==''){thrownew\RuntimeException('Ошибка выборки: '.$wpdb->last_error);}
Создание собственных таблиц оправдано для структурированных данных, которые плохо соответствуют модели записей, метаданных и таксономий WordPress.
Безопасная обработка данных
Входящие данные сначала проверяются и приводятся к ожидаемому формату.
При выводе используется функция экранирования, соответствующая контексту.
Функция подбирается под место вывода, а не «одна на все случаи»
Контекст
На входе
На выводе
Текст в HTML
sanitize_text_field()
esc_html()
Значение атрибута
sanitize_key()
esc_attr()
Ссылка
esc_url_raw()
esc_url()
Разметка от редактора
wp_kses_post()
wp_kses_post()
Данные в JavaScript
absint(), sanitize_key()
wp_json_encode(), esc_js()
Запрос к базе
приведение типа
$wpdb->prepare()
Данные из базы, стороннего API или настроек не считаются
автоматически безопасными для вывода. Экранирование выполняется в момент
вывода, а не «когда-то раньше при сохранении».
Nonce и проверка прав
Nonce применяется для защиты форм и запросов от нежелательного повторного
или стороннего вызова. Одновременно с nonce всегда проверяются полномочия
пользователя. Серверный обработчик должен определить:
/** Форма: nonce кладётся в разметку рядом с полями. */functionproject_render_box(WP_Post$post):void{wp_nonce_field('project_save_'.$post->ID,'project_nonce');$value=get_post_meta($post->ID,'_project_code',true);printf('<input type="text" name="project_code" value="%s" class="widefat">',esc_attr($value));}/** Обработчик: пять проверок до единственной операции записи. */add_action('save_post_product','project_save_box',10,2);functionproject_save_box(int$post_id,WP_Post$post):void{// Автосохранение и ревизии — не пользовательское действие.if(defined('DOING_AUTOSAVE')&&DOING_AUTOSAVE){return;}// Подлинность запроса…$nonce=isset($_POST['project_nonce'])?sanitize_key($_POST['project_nonce']):'';if(!wp_verify_nonce($nonce,'project_save_'.$post_id)){return;}// …и отдельно — право на этот конкретный объект.if(!current_user_can('edit_post',$post_id)){return;}$code=isset($_POST['project_code'])?sanitize_text_field(wp_unslash($_POST['project_code'])):'';if($code!==''&&!preg_match('/^[A-Z]{2}-\d{4,8}$/',$code)){return;// формат не соответствует ожидаемому}update_post_meta($post_id,'_project_code',$code);}
Скрытая кнопка или отсутствие ссылки в интерфейсе не являются ограничением доступа. Защита выполняется на стороне сервера.
Подключение CSS и JavaScript
Стили и скрипты подключаются через штатный механизм WordPress, а не вставляются
без системы непосредственно в шаблоны. Для ресурсов указываются уникальный
идентификатор, зависимости, версия, место подключения и условия загрузки.
Файлы подключаются только на тех страницах, где они необходимы.
add_action('wp_enqueue_scripts','project_assets');functionproject_assets():void{// Условие загрузки: калькулятор нужен только на странице услуги.if(!is_singular('service')){return;}$file=PROJECT_CORE_DIR.'assets/js/calculator.js';wp_enqueue_script('project-calculator',// идентификаторplugins_url('assets/js/calculator.js',PROJECT_CORE_FILE),[],// зависимостиfilemtime($file),// версия['in_footer'=>true,'strategy'=>'defer']// место и стратегия);// Данные для JS передаются контролируемо: только то, что можно// показать посетителю. Ключи и внутренние идентификаторы — нельзя.wp_add_inline_script('project-calculator','window.projectCalc = '.wp_json_encode(['ajaxUrl'=>admin_url('admin-ajax.php'),'nonce'=>wp_create_nonce('project_calc'),'currency'=>'₽',]).';','before');}
/**
* Курсы валют: внешний запрос выполняется не чаще раза в час
* и никогда — синхронно на каждой странице.
*/functionproject_get_rates():array{$cached=get_transient('project_rates');if(is_array($cached)){return$cached;}$response=wp_remote_get('https://api.example.com/rates',['timeout'=>5,// ограничение времени ожидания'redirection'=>2,'user-agent'=>'project-core/1.4',]);if(is_wp_error($response)||wp_remote_retrieve_response_code($response)!==200){// Сервис недоступен — короткий кеш, чтобы не долбить его// на каждом хите, и работа по последним известным данным.set_transient('project_rates',[],5*MINUTE_IN_SECONDS);return[];}$rates=json_decode(wp_remote_retrieve_body($response),true);$rates=is_array($rates)?$rates:[];set_transient('project_rates',$rates,HOUR_IN_SECONDS);return$rates;}
Кеширование не должно нарушать актуальность цен, остатков, персональных данных и другой динамической информации.
WP-Cron и фоновые процессы
WP-Cron зависит от посещаемости сайта и не всегда обеспечивает запуск строго
в назначенное время. Для важных регулярных процессов используется системный
cron с запуском планировщика WordPress.
wp-config.php + crontabShell
1
2
3
4
5
6
7
8
# 1. Отключаем запуск планировщика на хитах посетителей:# define('DISABLE_WP_CRON', true); — в wp-config.php# 2. Дёргаем планировщик системным cron каждые 5 минут
*/5 * * * * curl-s"https://example.com/wp-cron.php?doing_wp_cron" > /dev/null
# Альтернатива для тяжёлых задач — WP-CLI без HTTP-слоя
*/5 * * * * cd /var/www/example.com && wp cron event run --due-now--quiet
Фоновые задачи должны:
обрабатывать данные частями;
исключать параллельный повторный запуск;
вести журнал ошибок;
иметь возможность продолжить обработку;
ограничивать количество запросов к внешнему сервису;
не блокировать открытие страниц пользователями.
Для объёмных импортов и синхронизаций применяются очереди или поэтапная обработка.
/**
* Зависимость проверяется до использования, а не в момент падения.
* Отсутствие WooCommerce отключает часть плагина, а не весь сайт.
*/add_action('plugins_loaded','project_boot_woocommerce',20);functionproject_boot_woocommerce():void{if(!class_exists('WooCommerce')){add_action('admin_notices','project_notice_missing_wc');return;}if(version_compare(WC_VERSION,'8.0','<')){add_action('admin_notices','project_notice_old_wc');return;}(new\Project\Core\Shop\Sync())->register();}
Сторонние плагины выбираются по функциональной необходимости, качеству кода, истории обновлений, совместимости и репутации разработчика. Установка нескольких расширений с одинаковыми функциями избегается.
Тестирование WordPress-проекта
Перед публикацией проверяются:
основные страницы и шаблоны;
формы;
роли пользователей;
административные настройки;
мобильная версия;
различные объёмы контента;
изображения разных пропорций;
отсутствие PHP- и JavaScript-ошибок;
отправка почты;
фоновые задачи;
интеграции;
кеширование;
работа после очистки кеша;
сохранение данных;
обновление разработанного плагина или темы.
Для критичной бизнес-логики и сложных преобразований данных могут создаваться
автоматизированные тесты. Для остальных изменений проводится функциональное
и регрессионное тестирование по сценарию задачи.
Требования к вёрстке и интерфейсам
Независимо от CMS, интерфейс должен корректно работать на распространённых
размерах экранов и не зависеть от идеального объёма контента.
семантическая структура HTML;
последовательность заголовков;
адаптивность;
управление с клавиатуры;
видимое состояние фокуса;
подписи полей формы;
сообщения об ошибках;
альтернативные описания изображений;
контрастность текста;
поведение длинных заголовков;
отсутствие неконтролируемых сдвигов страницы.
Поле без подписиКак не надоHTML
1
2
3
4
5
6
<!-- Placeholder не подпись: он исчезает при вводе,
не читается скринридером как label и не кликабелен. -->
<div class="field">
<input type="email" name="email" placeholder="E-mail">
<div class="error">Ошибка</div>
</div>
Поле с подписью и сообщением об ошибкеКак надоHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="field">
<label class="field__label" for="order-email">E-mail</label>
<input
class="field__input"
id="order-email"
type="email"
name="email"
autocomplete="email"
required
aria-describedby="order-email-error"
aria-invalid="true">
<!-- Роль alert: сообщение будет озвучено сразу после появления -->
<p class="field__error" id="order-email-error" role="alert">
Укажите адрес в формате name@example.com
</p>
</div>
Подпись связана с полем через for/id, ошибка — через aria-describedby. Это работает и для мыши, и для клавиатуры, и для скринридера.
/* Видимый фокус — обязательное состояние, а не «мешает дизайну».
:focus-visible показывает кольцо только при клавиатурной навигации. */
.field__input:focus-visible,
.btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Длинный заголовок не должен ломать сетку */
.card__title {
overflow-wrap: anywhere;
hyphens: auto;
}
/* Резерв места под изображение — нет сдвига макета при загрузке */
.card__image {
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
object-fit: cover;
}
Полное соответствие определённому уровню WCAG является отдельной задачей
и требует специализированного аудита. В стандартной разработке учитываются
основные требования доступности, применимые к конкретному интерфейсу.
Порядок разработки
Последовательность одинакова и для отдельной доработки, и для нового модуля —
меняется только глубина каждого этапа.
01
Анализ задачи
Уточняются цель, пользовательские сценарии, ограничения платформы и ожидаемый результат. Для сложной функции составляется техническое описание или согласованный перечень требований.
02
Анализ существующего проекта
Проверяются версия CMS и PHP, структура сайта, активные модули и плагины, существующие доработки, состояние ядра, доступность резервного копирования и возможные конфликтующие решения.
03
Выбор архитектуры
Определяется, где должен находиться новый функционал, какие API и события будут использоваться, требуется ли отдельный модуль или плагин, как будут храниться данные и выполняться фоновые процессы.
04
Реализация
Код создаётся с учётом правил платформы, безопасности, производительности и последующего сопровождения. Связанные изменения объединяются в логические этапы.
05
Тестирование
Проверяются основной сценарий, граничные случаи, ошибки ввода, права пользователей, мобильная версия и влияние изменения на существующий функционал.
06
Развёртывание
Перед установкой на рабочий сайт проверяется наличие резервной копии. Изменения переносятся контролируемо, после чего выполняется повторная проверка основных сценариев.
07
Документирование
Для нетипичных решений фиксируются важные особенности настройки, запуска и сопровождения. При необходимости заказчику передаётся инструкция по работе с новым функционалом.
Что получает заказчик
Соблюдение стандарта не означает искусственное усложнение проекта. Его задача —
сделать результат предсказуемым и пригодным для дальнейшей эксплуатации.
доработки, отделённые от ядра CMS;
возможность устанавливать обновления с меньшим риском;
понятную структуру проектного кода;
контролируемую работу с данными;
проверку прав доступа и входящей информации;
оптимизированные запросы и загрузку ресурсов;
историю изменений;
возможность передать проект другому квалифицированному разработчику;
документирование критичных и нестандартных решений;
предварительно протестированный функционал.
При работе с существующим сайтом невозможно автоматически привести весь
унаследованный код к новому стандарту в рамках одной локальной задачи.
Однако новые доработки создаются по описанным правилам, а обнаруженные
критичные проблемы фиксируются и обсуждаются отдельно.
Официальная основа стандарта
Стандарт сформирован с учётом:
1С-Битрикс
официальный курс «Разработчик Bitrix Framework»;
документация по безопасной кастомизации;
рекомендации по использованию каталога /local/;
документация ядра D7 и ORM;
требования Монитора качества.
WordPress и общие практики
WordPress Coding Standards;
WordPress Plugin Handbook;
WordPress Theme Handbook;
WordPress Common APIs Handbook;
официальные рекомендации по безопасности и доступности;
применимые рекомендации PHP и OWASP.
Это не формальная сертификация каждого проекта и не обещание использовать
одинаковую архитектуру для любой задачи. Стандарт определяет принципы, по которым
выбирается технически обоснованное, безопасное и сопровождаемое решение.
Частые вопросы
Можно ли обновлять сайт после доработок?
Да, если доработки отделены от ядра и используют поддерживаемые механизмы расширения. Перед крупным обновлением всё равно рекомендуется создать резервную копию и проверить совместимость на тестовой среде.
Почему нельзя просто изменить файл ядра или плагина?
Такое изменение быстрее только на первом этапе. При следующем обновлении оно может быть потеряно, а поиск причины ошибки потребует дополнительного времени. Расширение через предусмотренные CMS механизмы надёжнее для дальнейшей поддержки.
Всегда ли нужен отдельный модуль или плагин?
Нет. Архитектура должна соответствовать масштабу задачи. Небольшое изменение не следует превращать в сложную систему, но самостоятельная бизнес-функция не должна бесконтрольно распределяться по файлам темы или шаблона.
Можно ли применять стандарт к уже работающему сайту?
Да. Сначала проводится анализ существующей архитектуры. Новые функции создаются по стандарту, а старый код перерабатывается поэтапно там, где это необходимо для безопасности, обновления или дальнейшего развития проекта.
Гарантирует ли стандарт отсутствие ошибок?
Ни один процесс разработки не может полностью исключить ошибки. Стандарт снижает их вероятность, делает изменения контролируемыми и упрощает обнаружение и исправление проблем.
Входит ли проверка безопасности в разработку?
Базовые меры безопасности входят в реализацию каждой функции. Полный аудит сайта, тестирование на проникновение и анализ всех установленных компонентов являются отдельными услугами.
Разработка с учётом дальнейшей поддержки
Я разрабатываю и дорабатываю сайты на 1С-Битрикс и WordPress, создаю
собственные модули и плагины, интеграции, обмены между системами
и нестандартные PHP-решения.
Перед началом работ изучаю текущую архитектуру проекта, выбираю подходящий
способ реализации и оцениваю влияние изменений на безопасность,
производительность и возможность дальнейшего обновления сайта.
Этот сайт использует куки-файлы. Оставаясь на сайте, Вы соглашаетесь на их использование. Для получения дополнительной информации, пожалуйста, ознакомьтесь с политикой в отношении персональных данных.