phalcon/cphalcon · error · Phalcon\Mvc\Model\Exceptions\RowIsImmutable
Row is an immutable ArrayAccess object
Error message
Row is an immutable ArrayAccess object
What it means
Row objects returned by Phalcon ORM queries for partial selects are immutable snapshots. offsetSet() exists only to satisfy the ArrayAccess interface and unconditionally throws RowIsImmutable, because mutating a hydrated result would desynchronize it from the data source and break identity tracking.
Source
Thrown at phalcon/Mvc/Model/Row.zep:70
*/
public function offsetGet(mixed index) -> mixed
{
if !property_exists(this, index) {
throw new IndexNotInRow();
}
return this->{index};
}
/**
* Rows cannot be changed. It has only been implemented to meet the definition of the ArrayAccess interface
*
* @param string|int offsetSet
* @param ModelInterface value
*/
public function offsetSet(mixed offset, mixed value) -> void
{
throw new RowIsImmutable();
}
/**
* Rows cannot be changed. It has only been implemented to meet the definition of the ArrayAccess interface
*
* @param string|int offset
*/
public function offsetUnset(mixed offset) -> void
{
throw new RowIsImmutable();
}
/**
* Reads an attribute value by its name
*
*```php
* echo $invoice->readAttribute("inv_title");
*```View on GitHub (pinned to b7419de9cd)
Solutions
- Convert the Row to a plain array first, then mutate the array: $data = (array) $row (or get_object_vars($row) / iterator_to_array($row)), then $data['extra'] = ...
- Compute derived values directly in the query using SQL expressions and aliases so no mutation is needed
- If you always want arrays, use a result set hydration strategy that returns arrays instead of Row objects
- Keep the Row read-only and carry computed values in separate variables passed alongside it
Example fix
// before $row = $query->getSingleResult(); $row['discounted'] = true; // throws RowIsImmutable // after: copy to an array, then extend it $data = (array) $row; $data['discounted'] = true; return $data;
Defensive patterns
Strategy: fallback
Validate before calling
// Before any mutation attempt: branch on the result type
if ($result instanceof \Phalcon\Mvc\Model\Row) {
$data = (array) $result; // mutable copy
} else {
$data = (array) $result;
}
$data['extra'] = 'value'; Type guard
function isImmutableRow(mixed $value): bool
{
return $value instanceof \Phalcon\Mvc\Model\Row;
} Try / catch
try {
$row['key'] = $value;
} catch (\Phalcon\Mvc\Model\Exceptions\RowIsImmutable $e) {
$data = (array) $row;
$data['key'] = $value; // mutate the copy instead
} Prevention
- Treat every Phalcon ORM result as read-only at the boundary; copy to array before decorating
- Push computed values into the query (SQL aliases) when possible
- Encode the rule in code review: no assignment or unset on query results
When it happens
Trigger: Executing $row['key'] = $value on any Phalcon\Mvc\Model\Row instance; trying to attach computed fields to a result row before passing it to a view or serializer; attempting to reuse a fetched Row as a template by overwriting its fields.
Common situations: Controllers that fetch partial rows and then decorate them with extra keys for the view; API layers that want to append metadata to results; developers used to hydrating to plain arrays assuming Rows behave the same.
Related errors
- The index does not exist in the row
- Class '{className}' is not an ADR Action.
- A null key is not allowed; bag elements must be written with
- Identity column '{identityField}' isn't part of the column m
- Identity column '{identityField}' isn't part of the table co
AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21).
Data as JSON: /api/errors/8e2778c220305834.
Report an issue: GitHub.