agentleFS
AnyOrgNameAnyOrgName/php-skills/skills/bitrix/d7-orm-queries/SKILL.md

d7-orm-queries

Skill4 starsChanged 4 months agoMITView on GitHub

Query 1C-Bitrix data with the D7 ORM DataManager API — getList with filter/select/order/runtime, references and joins, transactions, fetch vs fetchAll — and choose it over the legacy CIBlockElement API for new code. Use when the user is fetching rows through the D7 ORM — a DataManager `getList()` with `filter`, `select`, `order`, or `limit` — or building a new query over a Bitrix entity or highload block; the user is deciding between the D7 ORM (`ElementTable::getList`) and the legacy `CIBlockElement::GetList`, or migrating a query from the legacy API to the ORM; the user needs to join related entities in the ORM — a declared `Reference` field, a `runtime` field with `ReferenceField`/`ExpressionField`, or selecting columns across the join; the user needs several ORM `add`/`update`/`delete` operations to succeed or fail atomically — starting a DB transaction and committing or rolling back around DataManager writes. Keywords: bitrix, orm, datamanager, getlist, query, filter, runtime, reference, transaction,…

The file

We split this skill into the rules it gives an agent

12 rules. About 0 are common, 6 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Do not use this skill for a non-Bitrix ORM (Doctrine, Eloquent, plain PDO).

    line 90
  2. Distinctive:

    Do not use this skill for raw DDL, migrations, or schema surgery (CREATE/ALTER TABLE).

    line 92
  3. Very specific:

    Verify: The module is loaded, so ElementTable resolves without a class-not-found.

    line 139
  4. Very specific:

    Call EntityTable::getList with an explicit select, an order, and a filter whose keys are code literals and whose values hold the validated …

    line 140
  5. Distinctive:

    Verify: The filter binds input as a parameter and selects only the named columns.

    line 142
  6. Distinctive:

    Verify: Rows are read once.

    line 145
  7. Distinctive:

    Verify: A transaction is open on the current connection.

    line 161
  8. Very specific:

    Run the add/update/delete calls, checking isSuccess() on each Result and throwing when one fails so control reaches the catch block.

    line 162
  9. Distinctive:

    Verify: A failed write raises before any commit runs.

    line 164
  10. Very specific:

    Call $conn->commitTransaction().

    line 165
  11. Distinctive:

    Verify: A mid-sequence failure leaves the database in its pre-transaction state.

    line 167
  12. Very specific:

    When several ORM writes must be atomic, get the connection with Application::getConnection(), call startTransaction(), and commit on …

    line 180

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md
<!-- generated: do not edit — source: knowledge/atoms/bitrix/d7-orm-queries/ --><!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 --><!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->

Bitrix D7 ORM Queries

When to use

  • The user is fetching rows through the D7 ORM — a DataManager getList() with filter, select, order, or limit — or building a new query over a Bitrix entity or highload block.
  • The user is deciding between the D7 ORM (ElementTable::getList) and the legacy CIBlockElement::GetList, or migrating a query from the legacy API to the ORM.
  • The user needs to join related entities in the ORM — a declared Reference field, a runtime field with ReferenceField/ExpressionField, or selecting columns across the join.
  • The user needs several ORM add/update/delete operations to succeed or fail atomically — starting a DB transaction and committing or rolling back around DataManager writes.

When NOT to use

  • Do not use this skill for a non-Bitrix ORM (Doctrine, Eloquent, plain PDO); the DataManager getList/filter syntax and \Bitrix\Main\ORM are Bitrix-specific.
  • Do not use this skill for raw DDL, migrations, or schema surgery (CREATE/ALTER TABLE); it covers reading and writing rows through the ORM, not managing the underlying database schema.

Core guidance

  • Keep filter keys static; put user input only in values — Build filter keys (field names and operators like '>=DATE_CREATE') from literals in your code, and place untrusted input only in filter values, which the ORM parameterises.
  • Prefer the D7 ORM over legacy element queries for new code — In new code query through the D7 ORM (ElementTable::getList / a DataManager) rather than CIBlockElement::GetList; keep the legacy call only when patching existing legacy code where a rewrite is out of scope.

Decision Engine

Walk this section before choosing an implementation path.

Are you writing a new query, or editing an existing legacy CIBlockElement query?

├── Editing legacy query: → legacy — keep CIBlockElement::GetList and make a minimal local edit; do not rewrite to the ORM opportunistically.

└── New query: Does the query need to join a related entity that has no declared Reference field?

    ├── No ad hoc join needed: → orm — use DataManager getList with explicit select/filter/order and declared Reference fields for joins.

    └── Ad hoc join needed: → runtime — declare a runtime ReferenceField (or ExpressionField) inside this getList to express the one-off join.

Full engine: references/decision-engine.md — Decision Tree, Priority Matrix, Decision Table.

Workflows

Workflow: Safe Getlist

Progress:
- [ ] Guard the query with `if (!\Bitrix\Main\Loader::includeModule('iblock')) { ..
- [ ] Call `EntityTable::getList` with an explicit `select`, an `order`, and a `filter`
whose keys are code literals and whose values hold the validated input
- [ ] Iterate a large result with `while ($row = $result->fetch())`, or call
`$result->fetchAll()` for a small bounded set
  1. Guard the query with if (!\Bitrix\Main\Loader::includeModule('iblock')) { ... } before referencing the entity's DataManager.
    • Verify: The module is loaded, so ElementTable resolves without a class-not-found.
  2. Call EntityTable::getList with an explicit select, an order, and a filter whose keys are code literals and whose values hold the validated input.
    • Verify: The filter binds input as a parameter and selects only the named columns.
  3. Iterate a large result with while ($row = $result->fetch()), or call $result->fetchAll() for a small bounded set.
    • Verify: Rows are read once; memory stays flat for large sets.

Workflow: Atomic Writes

Progress:
- [ ] Get the connection with `$conn = \Bitrix\Main\Application::getConnection();` and
call `$conn->startTransaction();` before the first write
- [ ] Run the `add`/`update`/`delete` calls, checking `isSuccess()` on each Result and
throwing when one fails so control reaches the catch block
- [ ] Call `$conn->commitTransaction();` after the last successful write, and
`$conn->rollbackTransaction();` in the catch block on any failure
  1. Get the connection with $conn = \Bitrix\Main\Application::getConnection(); and call $conn->startTransaction(); before the first write.
    • Verify: A transaction is open on the current connection.
  2. Run the add/update/delete calls, checking isSuccess() on each Result and throwing when one fails so control reaches the catch block.
    • Verify: A failed write raises before any commit runs.
  3. Call $conn->commitTransaction(); after the last successful write, and $conn->rollbackTransaction(); in the catch block on any failure.
    • Verify: A mid-sequence failure leaves the database in its pre-transaction state.

Best practices

  • Pass an explicit select listing only the fields you need, including any reference sub-fields (['ID', 'NAME', 'SECTION.NAME']); do not rely on the default select or fetch every column
  • Loop with while ($row = $result->fetch()) for large or streamed result sets; call $result->fetchAll() only for small, bounded sets you need entirely in memory at once
  • Model a relation you use repeatedly as a declared Reference field on the entity map; express a one-off join inside a single query with a runtime ReferenceField instead of adding permanent schema
  • When several ORM writes must be atomic, get the connection with Application::getConnection(), call startTransaction(), and commit on success or rollbackTransaction() inside a catch on failure

Anti-patterns

Anti-patternWhy it failsDo instead
Ignoring the write result objectA failed write returns a Result with errors rather than throwing, so ignoring it<br>silently drops data and hides validation failures.Check $result->isSuccess() and read $result->getErrorMessages(), handling or<br>surfacing failures before proceeding.
Interpolating user input into a filter keyOnly filter values are parameterised, so an interpolated key is a direct SQL injection vector.Whitelist the allowed field to a fixed set of literals and keep the untrusted<br>input in the filter value only.
Relying on the default select to load every columnLoading unused columns wastes IO and memory and makes the query's real<br>dependencies invisible.Pass an explicit select naming exactly the fields and reference sub-fields the<br>code uses.

See references/anti-patterns.md.

Debugging

  1. Stale reads from the ORM entity cache after a direct write — The ORM caches results and only invalidates them on writes through its own DataManager. Fix: Route writes through the entity's DataManager so its cache is cleared, or clean the entity's managed cache explicitly after an out-of-band change.
  2. fetchAll on an unbounded result exhausts memory — fetchAll materialises the whole result set at once instead of streaming it. Fix: Iterate with while ($row = $result->fetch()) for large sets, and always bound paged queries with limit/offset.
  3. N+1 queries by fetching relations in a loop — Each loop iteration round-trips to the database instead of joining once. Fix: Select the related columns through a Reference or runtime ReferenceField in the original getList so the relation loads in a single joined query.

Performance notes

  • fetchAll on an unbounded result exhausts memory — Calling fetchAll() on a query with no limit loads every matching row into an array and can exhaust PHP memory on a large table.
  • N+1 queries by fetching relations in a loop — Fetching a list and then calling a second getList per row to load its related entity issues one query per row (N+1) and dominates response time.

Production checklist

  • Every getList passes an explicit select naming only the required fields and reference sub-fields, not a bare or default select.
  • Large or unbounded results are streamed with fetch(); only small bounded sets use fetchAll(), and paged queries set limit/offset.
  • No untrusted input is interpolated into a filter key, field name, or operator; input flows only into parameterised filter values.
  • The module that owns the DataManager (for example iblock) is included with a checked Loader::includeModule before any of its ORM classes are referenced.
  • The result of every add/update/delete is checked with isSuccess() and errors are read from getErrorMessages() rather than assumed to have worked.
  • Any group of ORM writes that must be atomic runs inside a connection transaction with a commit on success and a rollback in the catch path.

Version notes

  • The D7 ORM supersedes CIBlockElement for new reads — The \Bitrix\Main\ORM DataManager API (getList with entity Query, references, and runtime fields) matured on the D7 platform (main ~16+); the legacy CIBlockElement::GetList remains for older code and iblock-specific properties.

Security

  • Never interpolate untrusted input into filter or order keys — Treat every value from the request as untrusted: never build a filter key, field name, operator, order column, or select alias from user input. The ORM parameterises filter values only; keys are compiled into SQL literally.

Testing

  • Activation and decision behavior are verified in eval/scenarios/bitrix/d7-orm-queries.yaml.

Additional resources

  • references/anti-patterns.md
  • references/core.md
  • references/decision-engine.md
  • references/examples.md
  • references/pitfalls.md
  • references/recipes.md
  • references/version-differences.md

Knowledge graph

Prerequisites: bitrix/d7-loader
Related: ai-rules/strategy-bitrix-d7-incremental, bitrix/cache-layers
Behavior constraints: ai-rules/strategy-legacy-safe-edits

<!-- graph-fragment: graph/fragments/bitrix/d7-orm-queries.yaml -->

Skill contract

Produces: orm.d7-query
Requires: bitrix.module-loaded
Risk: medium · Confidence: 0.9

<!-- contract-fragment: contracts/fragments/bitrix/d7-orm-queries.yaml -->

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.