yiisoft/yii2 · error · yii\base\InvalidArgumentException

$ownership must be an integer, string, array, or null.

Error message

$ownership must be an integer, string, array, or null.

What it means

changeOwnership() accepts $ownership only as int (uid), string ('user' or 'user:group'), array (indexed [user, group] or associative ['user' => ..., 'group' => ...]), or null; every other type falls into the final else branch and throws InvalidArgumentException. This is a pure type-contract violation — the filesystem was never touched.

Source

Thrown at framework/helpers/BaseFileHelper.php:998

            return;
        }

        $user = $group = null;
        if (!empty($ownership) || $ownership === 0 || $ownership === '0') {
            if (is_int($ownership)) {
                $user = $ownership;
            } elseif (is_string($ownership)) {
                $ownerParts = explode(':', $ownership);
                $user = $ownerParts[0];
                if (count($ownerParts) > 1) {
                    $group = $ownerParts[1];
                }
            } elseif (is_array($ownership)) {
                $ownershipIsIndexed = ArrayHelper::isIndexed($ownership);
                $user = ArrayHelper::getValue($ownership, $ownershipIsIndexed ? 0 : 'user');
                $group = ArrayHelper::getValue($ownership, $ownershipIsIndexed ? 1 : 'group');
            } else {
                throw new InvalidArgumentException('$ownership must be an integer, string, array, or null.');
            }
        }

        if ($mode !== null) {
            if (!is_int($mode)) {
                throw new InvalidArgumentException('$mode must be an integer or null.');
            }
            if (!chmod($path, $mode)) {
                throw new Exception('Unable to change mode of "' . $path . '" to "0' . decoct($mode) . '".');
            }
        }
        if ($user !== null && $user !== '') {
            if (is_numeric($user)) {
                $user = (int) $user;
            } elseif (!is_string($user)) {
                throw new InvalidArgumentException('The user part of $ownership must be an integer, string, or null.');
            }
            if (!chown($path, $user)) {

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Normalize to a supported scalar before calling: cast numeric uids to int or build a 'user:group' string yourself.
  2. Validate the value first: is_int || is_string || is_array || is_null.
  3. Fix the config source so ownership is a simple scalar like 'www-data:www-data'.

Example fix

// before
\yii\helpers\FileHelper::changeOwnership($file, $config->owner); // $config->owner is \stdClass

// after
$owner = is_array($config->owner)
    ? implode(':', [$config->owner['user'] ?? '', $config->owner['group'] ?? ''])
    : (string) $config->owner;
\yii\helpers\FileHelper::changeOwnership($file, $owner);
Defensive patterns

Strategy: type-guard

Validate before calling

$ok = $ownership === null || is_int($ownership) || is_string($ownership) || is_array($ownership);
if (!$ok) {
    throw new \InvalidArgumentException('$ownership must be int, string, array, or null');
}

Type guard

/** @param mixed $ownership */
function isOwnershipValue($ownership): bool
{
    return $ownership === null
        || is_int($ownership)
        || is_string($ownership)
        || is_array($ownership);
}

Prevention

When it happens

Trigger: changeOwnership($path, 1000.5) with a float uid; passing a stdClass/stdClass-like config object; a boolean sneaking in because a config lookup defaulted to false; a float produced by arithmetic on an uid before the call.

Common situations: Ownership values coming from JSON/YAML config (nested objects instead of scalars) forwarded unchecked; dynamic values where a null-coalescing default yields a bool; values extracted from an API payload with unknown shape.

Related errors


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