Bitrix result and errors
Skill bxmaximum/bitrix-framework-skills/skills/bitrix-result-and-errors
Covers Bitrix unified operation results — Bitrix\Main\Result, Error, ErrorCollection, typed descendants (AddResult, UpdateResult, DeleteResult, EventResult), difference between Result and exceptions, returning errors from controllers via addError and combining service errors. Applied when designing service APIs and use cases, returning errors from module methods and controllers without throwing exceptions. Key terms — Result, Error, ErrorCollection, isSuccess, getErrors, AddResult, UpdateResult, addError.From its SKILL.md
npx -y skills add bxmaximum/bitrix-framework-skills --skill bitrix-result-and-errorsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 19 stars19 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
6.2 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it
Result and Errors in Bitrix
Baseline: main 23.0+.
Philosophy
- User Errors →
Result+Error. Validation, business rules, "entity not found," "insufficient permissions." - Program Errors → Exceptions. Connection failure, missing mandatory module, broken configuration.
- One method — one clear contract: either it always returns
Result(and doesn't throw "normal" exceptions), or it guaranteed returns a value and throws\Throwablein exceptional cases.
Bitrix\Main\Result
Base object:
$result = new \Bitrix\Main\Result();
if ($titleEmpty)
{
$result->addError(new \Bitrix\Main\Error('Title required', 'POST_TITLE_EMPTY'));
}
if (!$result->isSuccess()) { return $result; }
$result->setData(['id' => $id]);
return $result;
API:
isSuccess(): boolgetErrors(): Error[]getErrorMessages(): string[]getErrorCollection(): ErrorCollectionaddError(Error $e): selfaddErrors(array $errors): selfsetData(array $data): selfgetData(): array
Bitrix\Main\Error
new \Bitrix\Main\Error(
message: 'Title required',
code: 'POST_TITLE_EMPTY',
customData: ['field' => 'title'],
);
The error code should be stable and machine-readable — clients should rely on it, not the text.
ErrorCollection
$errors = new \Bitrix\Main\ErrorCollection();
$errors->setError(new \Bitrix\Main\Error('...', 'CODE_A'));
$errors->add([new \Bitrix\Main\Error('...', 'CODE_B')]);
foreach ($errors as $error) { /* ... */ }
$errors->getErrorByCode('CODE_A');
Controller already contains protected ErrorCollection $errorCollection — it is used by $this->addError(...) and $this->addErrors(...).
Typed Result for Service
Instead of setData(['post' => $post]), it's easier to inherit and provide normal getters:
<?php declare(strict_types=1);
namespace Vendor\Blog\Application\Result;
use Bitrix\Main\Result;
use Vendor\Blog\Domain\Model\Post;
final class CreatePostResult extends Result
{
private ?Post $post = null;
public function setPost(Post $post): self
{
$this->post = $post;
$this->setData(['post' => $post]);
return $this;
}
public function getPost(): ?Post
{
return $this->post;
}
}
Usage:
public function create(CreatePostRequest $request): CreatePostResult
{
$result = new CreatePostResult();
$validation = $this->validator->validate($request);
if (!$validation->isSuccess())
{
foreach ($validation->getErrors() as $error)
{
$result->addError(new \Bitrix\Main\Error($error->getMessage(), $error->getCode()));
}
return $result;
}
$post = $this->repository->save(Post::fromRequest($request));
return $result->setPost($post);
}
Specialized ORM Results
AddResult—getId().UpdateResult—getAffectedRowsCount(),getPrimary().DeleteResult—getAffectedRowsCount().
They inherit from Result and work with the same API. Example:
$add = PostTable::add(['TITLE' => 'Hi', 'AUTHOR_ID' => 1]);
if (!$add->isSuccess())
{
$this->logger->warning('Add post failed', ['errors' => $add->getErrorMessages()]);
return $add; // proxy upwards
}
$id = $add->getId();
EventResult
Handlers of \Bitrix\Main\Event return \Bitrix\Main\EventResult with type EventResult::SUCCESS, EventResult::ERROR, or EventResult::UNDEFINED (not a drop-in for service Result). Details: skill bitrix-events.
Attribute / ValidationService checks yield \Bitrix\Main\Validation\ValidationResult (also isSuccess() + errors) — skill bitrix-validation. Merge those errors into a service Result when crossing the module boundary.
Returning Errors in Controller
public function createAction(#[ValidationParameter] CreatePostRequest $request): array
{
$result = $this->postService->create($request);
if (!$result->isSuccess())
{
$this->addErrors($result->getErrors());
return [];
}
return ['id' => $result->getPost()->getId()];
}
The front-end will receive the following structure:
{
"status": "error",
"errors": [
{ "message": "Title required", "code": "POST_TITLE_EMPTY", "customData": { "field": "title" } }
]
}
Code Conventions
- Module prefix + entity + reason:
BLOG_POST_NOT_FOUND,BLOG_POST_TITLE_EMPTY. - Do not use
"1","ERROR",""— it doesn't allow the client to react. - Translated texts — via
Loc::getMessage('BLOG_ERROR_POST_TITLE_EMPTY'), code — via constant.
When to use Exceptions?
- Invalid system state: "module not installed," "database not responding," "service misconfigured."
- Developer contract violation:
InvalidArgumentException,LogicException,TypeError. - Inside domain operations where "error = bug." For example,
Money::divide()with zero.
Exceptions are caught at the module/controller boundary and turned into Result + log:
try
{
return $this->service->operation($dto);
}
catch (\Bitrix\Main\SystemException $e)
{
$this->logger->error('Operation failed', ['exception' => $e]);
$result = new \Bitrix\Main\Result();
$result->addError(new \Bitrix\Main\Error('Internal error', 'INTERNAL_ERROR'));
return $result;
}
Antipatterns
- Returning
bool/null/-1from a service instead ofResult. - Throwing
\Exception('Post not found')for a routine situation — that's not an exception. - Swallowing
catch (\Throwable $e) {}without logging. - Error in the form of an array
['error' => 'msg']— unify usingResult.
Controller JSON error format: {"status":"error","errors":[{"message":"...","code":"..."}]}. See skill bitrix-controllers.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.