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

  1. 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'] = ...
  2. Compute derived values directly in the query using SQL expressions and aliases so no mutation is needed
  3. If you always want arrays, use a result set hydration strategy that returns arrays instead of Row objects
  4. 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

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


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/8e2778c220305834. Report an issue: GitHub.