Refactoring Legacy API to D7 in 1C-Bitrix
Bitrix projects that are 5–10 years old are written using the "old API": the global $APPLICATION, CIBlockElement::GetList(), $DB->Query() with string concatenation, functions from CModule. This code works — for now. Problems arise when trying to scale, test, or hand it over to another team. Refactoring to D7 is not "rewriting for the sake of rewriting" — it is about reducing the cost of maintenance and eliminating a class of errors typical of procedural code.
What Exactly Changes When Moving to D7
Global functions → DataManager methods:
// Before — old API
$res = CIBlockElement::GetList(
['SORT' => 'ASC'],
['IBLOCK_ID' => 5, 'ACTIVE' => 'Y'],
false,
['nPageSize' => 20],
['ID', 'NAME', 'PROPERTY_PRICE']
);
while ($arItem = $res->GetNextElement()) {
$arFields = $arItem->GetFields();
$arProps = $arItem->GetProperties();
}
// After — D7 ORM
$result = \Bitrix\Iblock\Elements\ElementProductsTable::getList([
'select' => ['ID', 'NAME', 'PROPERTY_PRICE_VALUE'],
'filter' => ['=IBLOCK_ID' => 5, '=ACTIVE' => 'Y'],
'order' => ['SORT' => 'ASC'],
'limit' => 20,
]);
foreach ($result->fetchCollection() as $element) {
$name = $element->getName();
$price = $element->get('PROPERTY_PRICE_VALUE');
}
SQL strings → Connection API:
// Before — direct SQL with concatenation
global $DB;
$id = intval($id); // the entire "protection"
$res = $DB->Query("SELECT * FROM b_custom_table WHERE ID = " . $id);
// After — parameterized query
$connection = \Bitrix\Main\Application::getConnection();
$result = $connection->query(
"SELECT * FROM my_custom_table WHERE ID = ?",
[$id]
);
// or via SqlHelper for complex cases
$helper = $connection->getSqlHelper();
$safeId = $helper->forSql((string)$id);
Global $APPLICATION → Context and Application:
// Before
global $APPLICATION;
$APPLICATION->SetTitle('My Page');
$APPLICATION->IncludeFile('/local/include/header.php');
// After — within component or controller context
$this->arResult['PAGE_TITLE'] = 'My Page';
// Inclusion via D7 mechanisms or Inertia/React approach
Refactoring Strategy: Incremental Phases
Rewriting the entire project at once is a bad idea. Even a small online store contains 50,000+ lines of code. The correct approach is layered refactoring with prioritization.
Phase 1. Audit. Inventory of problem areas:
- SQL queries with string concatenation (potential SQL injections)
- Global variables in logic (
global $DB,global $USER,global $APPLICATION) - Heavy queries via
CIBlockElement::GetListwithout limits - Duplicated logic code across different files
Phase 2. Create a service layer. New D7 API service classes for each area of responsibility. Old code calls the new services.
Phase 3. Gradual replacement. Module by module and functional block by block. After each block is replaced — regression testing.
Phase 4. Remove obsolete code. Only after the new path has been battle-tested.
Infoblock Transition to D7 Classes
Starting from Bitrix version 20.x, auto-generated classes for infoblocks are available via Bitrix\Iblock\Elements:
// Enable in the infoblock settings — "API code" flag
// A class is generated with namespace Bitrix\Iblock\Elements
$result = \Bitrix\Iblock\Elements\ElementCatalogTable::getList([
'select' => ['ID', 'NAME', 'IBLOCK_SECTION_ID'],
'filter' => ['=ACTIVE' => 'Y'],
]);
This is not a full replacement for CIBlockElement — for write operations (add/update), the old API is still often used, as D7 wrappers for infoblock writes do not cover all scenarios.
Typical Pitfalls During Refactoring
Losing events. If the old code called CIBlockElement::Update(), event handlers were attached to it (OnAfterIBlockElementUpdate). When switching to D7 methods, the ORM events are different. You need to check which handlers are bound to the old events.
Differences between GetList and getList results. CIBlockElement::GetList returns infoblock properties in a specific format with _VALUE, _ENUM_ID suffixes. ORM methods return a different structure. Code that processes results needs to be adapted.
Transactions. The old API did not always wrap operations in transactions. D7 provides explicit transactions:
$connection = \Bitrix\Main\Application::getConnection();
$connection->startTransaction();
try {
// multiple operations
$connection->commitTransaction();
} catch (\Throwable $e) {
$connection->rollbackTransaction();
throw $e;
}
What to Refactor First
| Priority | What | Why |
|---|---|---|
| Critical | SQL with string concatenation | Security |
| High | Heavy GetList without limits | Performance |
| Medium | Business logic in components | Maintainability |
| Planned | Global variables for styling | Readability |
Timeline
| Project Scale | Refactoring Timeline |
|---|---|
| Small site (up to 30 files with logic) | 2–4 weeks |
| Medium online store (50–150 files) | 2–3 months |
| Large portal or marketplace | 4–8 months (incremental) |
Refactoring is technical debt you pay now to avoid paying three times as much in a year. Code on the old API does not become more reliable over time — with each Bitrix update, the risk of incompatibility grows.







