yiisoft/yii2 · error · InvalidCallException

Setting read-only property: {class}::{name}

Error message

Setting read-only property: {class}::{name}

What it means

BaseObject::__set() throws InvalidCallException('Setting read-only property: C::p') when the name has a getter but no setter and is not a public field - the property is computed or derived and immutable from outside. Yii deliberately blocks the write instead of silently ignoring it, so the failing assignment is in your code or your config.

Source

Thrown at framework/base/BaseObject.php:162

    /**
     * Sets value of an object property.
     *
     * Do not call this method directly as it is a PHP magic method that
     * will be implicitly called when executing `$object->property = $value;`.
     * @param string $name the property name or the event name
     * @param mixed $value the property value
     * @throws UnknownPropertyException if the property is not defined
     * @throws InvalidCallException if the property is read-only
     * @see __get()
     */
    public function __set($name, $value)
    {
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
            $this->$setter($value);
        } elseif (method_exists($this, 'get' . $name)) {
            throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
        } else {
            throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
        }
    }

    /**
     * Checks if a property is set, i.e. defined and not null.
     *
     * Do not call this method directly as it is a PHP magic method that
     * will be implicitly called when executing `isset($object->property)`.
     *
     * Note that if the property is not defined, false will be returned.
     * @param string $name the property name or the event name
     * @return bool whether the named property is set (not null).
     * @see https://www.php.net/manual/en/function.isset.php
     */
    public function __isset($name)
    {

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Remove the assignment and set the underlying writable properties instead
  2. If mutation is legitimate, add a setter setX() alongside the existing getter
  3. In configuration, delete the read-only key or map it to the writable property it derives from

Example fix

// before
class Name extends \yii\base\BaseObject
{
    public $first;
    public $last;
    public function getFullName() { return $this->first . ' ' . $this->last; }
}
$n->fullName = 'Ada B'; // InvalidCallException

// after
$n->first = 'Ada';
$n->last = 'B';
Defensive patterns

Strategy: type-guard

Validate before calling

if ($object->canSetProperty($name)) {
    $object->$name = $value;
} else {
    throw new \LogicException("{$name} on " . get_class($object) . ' is read-only');
}

Type guard

function canWrite(\yii\base\BaseObject $object, string $name): bool
{
    return $object->canSetProperty($name);
}

Prevention

When it happens

Trigger: Assigning to a computed property ($n->fullName = 'A B' where getFullName() concatenates two stored fields); putting a read-only property key into a config array, because component configuration assigns every key through __set(); immutably-designed value objects used where mutable DTOs were expected.

Common situations: Config snippets copied from examples that target a different class version; value objects (money, date ranges) treated as writable; refactors that introduced a getter without removing old assignments.

Related errors


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/2ca4ebab64c468bc. Report an issue: GitHub.