D7-аналоги любимых функций в 1С-Битрикс

Share

Подключение стилей и скриптов

CMain::AddHeadScript, CMain::SetAdditionalCss, CMain::AddHeadString

Давным-давно в далекой-далекой версии Битрикса разработчики вставляли стили и скрипты шаблона в документ банальными тегами <script> и <link>. Потом в фаворе оказались названные выше отложенные функции. Сейчас устарели и они. Нынче на дворе 2016 год и подключать надо так: // Old school $APPLICATION->AddHeadScript(SITE_TEMPLATE_PATH . "/js/fix.js");
$APPLICATION->SetAdditionalCSS(SITE_TEMPLATE_PATH . "/styles/fix.css");
$APPLICATION->AddHeadString("<link href='http://fonts.googleapis.com/css?family=PT+Sans:400&subset=cyrillic' rel='stylesheet' type='text/css'>");
// D7
use Bitrix\Main\Page\Asset;
Asset::getInstance()->addJs(SITE_TEMPLATE_PATH . "/js/fix.js");
Asset::getInstance()->addCss(SITE_TEMPLATE_PATH . "/styles/fix.css");
Asset::getInstance()->addString("<link href='http://fonts.googleapis.com/css?family=PT+Sans:400&subset=cyrillic' rel='stylesheet' type='text/css'>");


Эти методы динамические, а класс реализует паттерн “одиночка” (Singletone) и обратиться к объекту можно через Bitrix\Main\Page\Asset::getInstance().

По слухам, этот класс может быть в скором времени переписан, так что необходимо держать руку на пульсе.

Срочно в номер: на днях была опубликована документация по новому способу подключения JS и CSS в шаблонах компонентов.Подключение модулей

CModule::IncludeModule, CModule::IncludeModuleEx

Уже многие выучили, что вместо старого доброго CModule для подключения модулей нужно применять новый бодрый Bitrix\Main\Loader.

// Old school CModule::IncludeModule("iblock");
CModule::IncludeModuleEx("intervolga.tips");
// D7
use Bitrix\Main\Loader;
Loader::includeModule("iblock"); Loader::includeSharewareModule("intervolga.tips");

Локализация

GetMessage, IncludeModuleLangFile, IncludeTemplateLangFile

Чтобы быть стильным-модным-молодежным рекомендуется пользоваться следующим кодом для обращения к языковым файлам и переменным:

// Old school
IncludeTemplateLangFile(__FILE__);
echo GetMessage("INTERVOLGA_TIPS.TITLE");
// D7
use Bitrix\Main\Localization\Loc;
Loc::loadMessages(__FILE__);
echo Loc::getMessage("INTERVOLGA_TIPS.TITLE");

Настройки модулей

COption::SetOptionInt, COption::SetOptionString, COption::GetOptionInt, COption::GetOptionString, COption::RemoveOption

Претерпел изменения и код для работы с чтением и записью настроек модулей. На смену классу COption пришел Bitrix\Main\Config\Option:

// Old school
COption::SetOptionString("main", "max_file_size", "1024");
$size = COption::GetOptionInt("main", "max_file_size");
COption::RemoveOption("main", "max_file_size", "s2");
// D7
use Bitrix\Main\Config\Option;
Option::set("main", "max_file_size", "1024");
$size = Option::get("main", "max_file_size");
Option::delete("main", array(
"name" => "max_file_size",
"site_id" => "s2"
)
);

Пропало разделение методов на int и string, а при удалении теперь используется массив-фильтр.

В новом классе есть несколько совершенно новых методов, не имеющих “старых” аналогов, так что самостоятельное изучение исходного кода приветствуется.

Кеширование

CPHPCache::StartDataCache, CPHPCache::InitCache, CPHPCache::GetVars, CPHPCache::EndDataCache, CPHPCache::AbortDataCache

Спасибо Андрею Загальскому за пример работы с кешированием в ядре D7. Новый класс Bitrix\Main\Data\Cache почти ничем не отличается в обращении от старого CPHPCache.

// Old school
$cache = new CPHPCache();
if ($cache->InitCache($cacheTime, $cacheId, $cacheDir))
{
$result = $cache->GetVars();
}
elseif($cache->StartDataCache())
{
$result = array();
// ...
if ($isInvalid)
{
$cache->AbortDataCache();
}
// ...
$cache->EndDataCache($result);
}
// D7
$cache = Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache($cacheTime, $cacheId, $cacheDir))
{
$result = $cache->getVars();
}
elseif ($cache->startDataCache())
{
$result = array();
// ...
if ($isInvalid)
{
$cache->abortDataCache();
}
// ...
$cache->endDataCache($result);
}

События

AddEventHandler, RemoveEventHandler, RegisterModuleDependences, UnRegisterModuleDependences, GetModuleEvents

Теперь за кратко- и долгосрочную регистрацию обработчиков событий отвечает один класс: Bitrix\Main\EventManager.

// Old school
$handler = AddEventHandler("main",
"OnUserLoginExternal",
array(
"Intervolga\\Test\\EventHandlers\\Main",
"onUserLoginExternal"
)
);
RemoveEventHandler(
"main",
"OnUserLoginExternal",
$handler
);
RegisterModuleDependences(
"main",
"OnProlog",
$this->MODULE_ID,
"Intervolga\\Test\\EventHandlers",
"onProlog"
);
UnRegisterModuleDependences(
"main",
"OnProlog",
$this->MODULE_ID,
"Intervolga\\Test\\EventHandlers",
"onProlog"
);
$handlers = GetModuleEvents("main", "OnProlog", true);
// D7
use Bitrix\Main\EventManager;
$handler = EventManager::getInstance()->addEventHandler(
"main",
"OnUserLoginExternal",
array(
"Intervolga\\Test\\EventHandlers\\Main",
"onUserLoginExternal"
)
);
EventManager::getInstance()->removeEventHandler(
"main",
"OnUserLoginExternal",
$handler
);
EventManager::getInstance()->registerEventHandler(
"main",
"OnProlog",
$this->MODULE_ID,
"Intervolga\\Test\\EventHandlers",
"onProlog"
);
EventManager::getInstance()->unRegisterEventHandler(
"main",
"OnProlog",
$this->MODULE_ID,
"Intervolga\\Test\\EventHandlers",
"onProlog"
); $handlers = EventManager::getInstance()->findEventHandlers("main", "OnProlog");

Bitrix\Main\EventManager, так же как Bitrix\Main\Page\Asset, реализует паттерн Одиночка, обращаться к нему нужно через getInstance().

Важное замечание: в обработчики, зарегистрированные с помощью addEventHandler в качестве аргумента будет передан объект события (Bitrix\Main\Event). Если хотите, чтобы передавались старые добрые аргументы, нужно использоватьaddEventHandlerCompatible. Аналогично с registerEventHandler и registerEventHandlerCompatible.

Файловая структура

CheckDirPath, DeleteDirFilesEx, RewriteFile

Тут нынче раздолье для ООП-программиста. Для работы с файлами, папками и всем прочим – отдельные классы, все типизировано и напоминает Java. Самые “главные” классы здесь – Bitrix\Main\IO\Directory и Bitrix\Main\IO\File (ну и немного Bitrix\Main\IO\Path).

// Old school
CheckDirPath($_SERVER["DOCUMENT_ROOT"] . "/foo/bar/baz/");
RewriteFile(
$_SERVER["DOCUMENT_ROOT"] . "/foo/bar/baz/1.txt",
"hello from old school!"
);
DeleteDirFilesEx("/foo/bar/baz/");
// D7
use Bitrix\Main\Application;
use Bitrix\Main\IO\Directory;
use Bitrix\Main\IO\File;
Directory::createDirectory(
Application::getDocumentRoot() . "/foo/bar/baz/"
);
File::putFileContents(
Application::getDocumentRoot() . "/foo/bar/baz/1.txt",
"hello from D7"
);
Directory::deleteDirectory(
Application::getDocumentRoot() . "/foo/bar/baz/"
);

Необходимо только запомнить, что DeleteDirFilesEx принимал путь от корня сайта, а его аналог принимает абсолютный путь к файлу от корня сервера.

Обратите внимание: вместо $_SERVER["DOCUMENT_ROOT"] сейчас можно использовать Bitrix\Main\Application::getDocumentRoot().

Классы для работы с файловой системой умеют многое, в этом вы можете убедиться сами, изучив исходники.

Исключения

CMain::ThrowException, CMain::ResetException, CMain::GetException

Открою небольшой секрет: в нашем дружном коллективе возник локальный холивар. Мы спорили, стоит ли помещать в эту статью раздел про исключения и обработку ошибок. Ведь тут не просто одни методы заменили другие – тут изменился подход (о чем я говорил в начале статьи), были переосмыслены некоторые базовые принципы многих классов. Есть две причины, почему этот раздел все-таки опубликован:

  1. Это важная тема, и говоря о переходе на D7 про нее нельзя не упомянуть
  2. Автор статьи все-таки я :P

Итак, одним из наиболее существенных изменений в D7 стала обработка ошибок при помощи механизма исключений (полное соблюдение религии исключений в php). Если происходит ошибка – выбрасываем исключение. Если хотим обработать ошибку – ловим исключение. Базовый класс для всех исключений в системе: Bitrix\Main\SystemException.

// Old school
global $APPLICATION;
$APPLICATION->ResetException();
$APPLICATION->ThrowException("Error");
//...
if ($exception = $APPLICATION->GetException())
{
echo $exception->GetString();
}
// D7
use Bitrix\Main\SystemException;
try
{
// ...
throw new SystemException("Error");
}
catch (SystemException $exception)
{
echo $exception->getMessage(); }

Отладка

AddMessage2Log, mydump

Ах, сколько раз выручал бывалого разработчика этот метод (AddMessage2Log). Неизменный товарищ при отладке ajax-запросов, крон-файлов и всего, невидимого глазу администратора. Но сегодня устарел и он, а на смену ему спешат два новых “молодца”:

// Old school
define("LOG_FILENAME", $_SERVER["DOCUMENT_ROOT"]."/bitrix/log-intervolga.txt");
AddMessage2Log($_SERVER);
echo "<pre>" . mydump($_SERVER) . "</pre>";
// D7
use Bitrix\Main\Diag\Debug;
Debug::dumpToFile($_SERVER);
// or
Debug::writeToFile($_SERVER);
Debug::dump($_SERVER);

Первый для любителей var_dump, второй для адептов print_r’а.

Так же в этом классе есть несколько совершенно новых методов, не имеющих аналогов в старом ядре. Например, методы для измерения времени:

// D7
use Bitrix\Main\Diag\Debug;
Debug::startTimeLabel("foo");
foo();
Debug::endTimeLabel("foo");
Debug::startTimeLabel("bar");
bar();
Debug::endTimeLabel("bar");
print_r(Debug::getTimeLabels());

Почтовые события

CEvent::Send, CEvent::SendImmediate

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

// Old school
CEvent::Send(
"NEW_USER",
"s1",
array(
"EMAIL" => "info@intervolga.ru",
"USER_ID" => 42
)
);
// D7
use Bitrix\Main\Mail\Event;
Event::send(array(
"EVENT_NAME" => "NEW_USER",
"LID" => "s1",
"C_FIELDS" => array(
"EMAIL" => "info@intervolga.ru",
"USER_ID" => 42
), ));

Работа с GET- и POST-параметрами страницы

$_GET, $_POST, $_REQUEST

Ошибки использования $_POST

Чтобы избавиться от глобальных переменных в коде (суперглобальных в том числе) в D7 изобрели класс Bitrix\Main\HttpRequest. Забирать get- и post-переменные теперь можно через него.

// Old school
$name = $_POST["name"];
$email = htmlspecialchars($_GET["email"]);
// D7
use Bitrix\Main\Application;
$request = Application::getInstance()->getContext()->getRequest();
$name = $request->getPost("name");
$email = htmlspecialchars($request->getQuery("email"));

Конструировать объект самому не нужно, добраться до него можно через приложение и контекст (как в примере кода выше).

Работа с cookie

CMain::set_cookie, CMain::get_cookie

Класс CMain слишком много себе позволяет. Он и компоненты подключал, и CSS/JS регистрирует, и хлебные крошки собирает, и т.п. В этом разделе мы рассмотрим, куда “сбежали” методы работы с куками. В старом ядре было 2 метода: для создания и для получения кук. Теперь всё стало гораздо глубже: задавать куки нужно через класс “ответа сервера” – Bitrix\Main\HttpResponse, получать их нужно через класс “запроса к серверу” – Bitrix\Main\HttpRequest.

// Old school
global $APPLICATION;
$APPLICATION->set_cookie("TEST", 42, false, "/", "example.com");
// Cookie будет доступна только на следующем хите!
echo $APPLICATION->get_cookie("TEST");
// D7
use Bitrix\Main\Application;
use Bitrix\Main\Web\Cookie;
$cookie = new Cookie("TEST", 42);
$cookie->setDomain("example.com");
Application::getInstance()->getContext()->getResponse()->addCookie($cookie);
// Cookie будет доступна только на следующем хите!
echo Application::getInstance()->getContext()->getRequest()->getCookie("TEST");

Также работа с куками может вестись силами класса Bitrix\Main\Web\HttpClient, но это совсем другая история.

Важное замечание: запись куки, добавленной через D7, произойдет только при подключении эпилога (там вызывается метод Bitrix\Main\HttpResponse::flush())

Работа со ссылками

CMain::GetCurPageParam, DeleteParam

Для работы со ссылками в ядре D7 есть отдельный класс: Bitrix\Main\Web\Uri. Правда, работа с ним не так “компактна”, как с его предшественником. Но это лишь следствие того, что в этом классе соблюдается принцип “1 метод – 1 ответственность”.

// Old school
global $APPLICATION;
$redirect = $APPLICATION->GetCurPageParam("foo=bar", array("baz"));
// D7
use Bitrix\Main\Application;
use Bitrix\Main\Web\Uri;
$request = Application::getInstance()->getContext()->getRequest();
$uriString = $request->getRequestUri();
$uri = new Uri($uriString);
$uri->deleteParams(array("baz"));
$uri->addParams(array("foo" => "bar")); $redirect = $uri->getUri();

ORM-классы

Их реально много. Их просто нужно “знать в лицо”, так как это будет основной инструмент на многие годы, как когда-то был CIBlockElement::GetList сотоварищи. В таблице в конце статьи я приведу основные ORM-классы, на которые стоит обратить внимание уже сегодня.

Запросы к БД

CDatabase::Query

Хоть это и не рекомендуется, но возможность поработать с БД прямыми запросами в продукте остается. Теперь доступ получается не через глобальный объект $DB класса CDatabase, а через Bitrix\Main\DB\Connection. “Добираться” до подключения нужно через Bitrix\Main\Application:

// Old school
global $DB;
$record = $DB->Query("select 1+1;")->Fetch();
AddMessage2Log($record);
// D7
use Bitrix\Main\Application;
use Bitrix\Main\Diag\Debug;
$record = Application::getConnection()
->query("select 1+1;")
->fetch();
Debug::writeToFile($record);

Хозяйке на заметку: больше про ORM и работу с БД в D7 можно почерпнуть из другой нашей статьи.

Итоговая таблица аналогов старых методов и классов в новом ядре D7 (с примерами вызова)

Было (старое ядро)Стало (новое ядро D7)
CMain::AddHeadScriptBitrix\Main\Page\Asset::addJs
CMain::SetAdditionalCssBitrix\Main\Page\Asset::addCss
CMain::AddHeadStringBitrix\Main\Page\Asset::addString
CModule::IncludeModuleBitrix\Main\Loader::includeModule
CModule::IncludeModuleExBitrix\Main\Loader::includeSharewareModule
GetMessageBitrix\Main\Localization\Loc::getMessage
IncludeModuleLangFile, IncludeTemplateLangFileBitrix\Main\Localization\Loc::loadMessages
COption::SetOptionInt, COption::SetOptionString,Bitrix\Main\Config\Option::set
COption::GetOptionInt, COption::GetOptionStringBitrix\Main\Config\Option::get
COption::RemoveOptionBitrix\Main\Config\Option::delete
CPHPCache::StartDataCacheBitrix\Main\Data\Cache::startDataCache
CPHPCache::InitCacheBitrix\Main\Data\Cache::initCache
CPHPCache::GetVarsBitrix\Main\Data\Cache::getVars
CPHPCache::EndDataCacheBitrix\Main\Data\Cache::endDataCache
CPHPCache::AbortDataCacheBitrix\Main\Data\Cache::abortDataCache
AddEventHandlerBitrix\Main\EventManager::addEventHandler (новый формат), Bitrix\Main\EventManager::addEventHandlerCompatible
RemoveEventHandlerBitrix\Main\EventManager::removeEventHandler (новый формат), Bitrix\Main\EventManager::registerEventHandlerCompatible
RegisterModuleDependencesBitrix\Main\EventManager::registerEventHandler
UnRegisterModuleDependencesBitrix\Main\EventManager::unRegisterEventHandler
GetModuleEventsBitrix\Main\EventManager::findEventHandlers
CheckDirPathBitrix\Main\IO\Directory::createDirectory
DeleteDirFilesExBitrix\Main\IO\Directory::deleteDirectory
RewriteFileBitrix\Main\IO\File::putFileContents
CMain::ThrowExceptionBitrix\Main\SystemException
CMain::ResetExceptionBitrix\Main\SystemException
CMain::GetExceptionBitrix\Main\SystemException
AddMessage2LogBitrix\Main\Diag\Debug::dumpToFile,Bitrix\Main\Diag\Debug::writeToFile
mydumpBitrix\Main\Diag\Debug::dump
CEvent::SendBitrix\Main\Mail\Event::send
CEvent::SendImmediateBitrix\Main\Mail\Event::sendImmediate
$_REQUESTBitrix\Main\HttpRequest::get
$_GETBitrix\Main\HttpRequest::getQuery
$_POSTBitrix\Main\HttpRequest::getPost
CMain::set_cookieBitrix\Main\HttpResponse::addCookie
CMain::get_cookieBitrix\Main\HttpRequest::getCookie
CMain::GetCurPageParamBitrix\Main\Web\Uri::addParams,Bitrix\Main\Web\Uri::deleteParams,Bitrix\Main\Web\Uri::getUri
DeleteParamBitrix\Main\Web\Uri::deleteParams,Bitrix\Main\Web\Uri::getUri
CDatabase::QueryBitrix\Main\DB\Connection::query

ORM-классы

Класс в старом ядреКласс в новом ядре D7Таблица БД
CUserBitrix\Main\UserTableb_user
CFileBitrix\Main\FileTableb_file
CGroupBitrix\Main\GroupTableb_group
CSiteBitrix\Main\SiteTableb_lang
CIBlockElementBitrix\Iblock\ElementTableb_iblock_element
CIBlockBitrix\Iblock\IblockTableb_iblock
CIBlockPropertyBitrix\Iblock\PropertyTableb_iblock_property
CIBlockSectionBitrix\Iblock\SectionTableb_iblock_section
CIBlockPropertyEnumBitrix\Iblock\PropertyEnumerationTableb_iblock_property_enum
CCatalogStoreBitrix\Catalog\StoreTableb_catalog_store
CCatalogProductBitrix\Catalog\ProductTableb_catalog_product
CCatalogGroupBitrix\Catalog\GroupTableb_catalog_group
CSaleOrderBitrix\Sale\Internals\OrderTableb_sale_order
CSaleBasketBitrix\Sale\Internals\BasketTableb_sale_basket
CSaleOrderPropsBitrix\Sale\Internals\OrderPropsTableb_sale_order_props
CSaleOrderPropsValueBitrix\Sale\Internals\OrderPropsValueTableb_sale_order_props_value

Выводы

Проблемы с использованием Битрикс D7

Складывается интересная ситуация. Компания 1С-Битрикс активно рекламирует новое ядро D7, на каждой конференции все чаще слышны фразы “компонент/модуль полностью переписан на новом ядре, классах и т.п.”, старый код все чаще отмечается как @deprecated. Но при всем при этом никакой документации нет. Документация посвящена только старому ядру и если нужно познакомиться с D7 – то нужно держать руку на пульсе событий: читать исходники, форумы, блоги, общаться с техподдержкой, изучать официальные видеокурсы( Разработка на D7.Введение и D7. Разработка собственного модуля). С одной стороны, это может повысить “порог вхождения” в “клуб программистов D7” и сделать их еще более бородатыми, с другой – может отпугнуть новичков.