PHP TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given の原因と解決方法【nullを返す関数の見抜き方】

count(): Argument #1 ($value) must be of type Countable|array, null givenとは?ざっくりと3行で
  • count(): Argument #1 ($value) must be of type Countable|array, null givenは、nullをcount()に渡したときにPHP8以降で発生するTypeErrorだ。
  • 実務では未定義の配列キーへのアクセスや、DB検索・API取得が0件のときに関数がnullを返してしまうケースが大半を占める。
  • count()に渡す前にis_array()でチェックするか、null合体演算子(?? [])で空配列にフォールバックすれば直る。
PHP count()のTypeError(null given)が発生し原因究明から修正までを描いた4コマ漫画
①エンジニアがタグ一覧画面で謎の500エラーに固まる。②ログにcount(): null givenの文字を発見する。③未定義キーが原因と判明しnull合体演算子で修正する。④配列は必ずnullチェックする習慣を心に誓う。

4コマの主人公が直面したのは、タグ一覧画面が突然500エラーで真っ赤になるという、多くの開発者が一度は経験する場面です。ログを確認するとcount()のTypeError(null given)という一文が残っており、原因は$product['options']のような未定義の配列キーへのアクセスでした。PHPは存在しないキーを読み取ると警告付きでnullを返すため、その値をそのままcount()に渡した瞬間にTypeErrorとしてスクリプトが停止します。

この現象を放置すると、管理画面の一部機能だけが本番環境でだけ落ちるという厄介な障害につながります。ローカルの開発環境がPHP7系のままだと警告止まりで再現せず、本番のPHP8環境で初めて発覚するケースも珍しくありません。3コマ目のように?? []で空配列にフォールバックする一行を挟むだけで、処理は継続しつつ意図しない挙動も防げます。

4コマ目の教訓どおり、配列を返すはずの値には常にnullである可能性を疑う習慣が欠かせません。次の章から、具体的な原因パターンと再発防止のコツを順に見ていきます。

count(): Argument #1 ($value) must be of type Countable|array, null givenの基本情報

PHPのcount(): Argument #1 ($value) must be of type Countable|array, null givenは、count()関数の第一引数にnullを渡した際にPHP8以降で発生するTypeErrorです。配列を返すはずの変数が実際にはnullだったことが直接の引き金になります。

項目内容
エラーメッセージPHP Fatal error: Uncaught TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given in /path/to/file.php:12
発生する言語・環境PHP 8.0以上(8.1・8.2・8.3・8.4を含む)。PHP 7.2〜7.4では同じ条件でもWarningとして処理が継続する
エラーの意味count()の第1引数(内部パラメータ名$value)には配列またはCountableインターフェースを実装したオブジェクトしか渡せないが、実際にはnullが渡されている
主な原因未定義の配列キーへのアクセス、DB検索・API取得結果が0件、自作関数の早期returnなどにより、配列であるべき変数がnullになっている
解決の基本方針count()に渡す直前でvar_dump()やgettype()を使い、その変数がどこでnullになったかを特定したうえでnull合体演算子や型宣言で防ぐ

なぜcount(): Argument #1 ($value) must be of type Countable|array, null givenが発生するのか?

PHP7.4以前は同じコードでも処理が止まらずWarningで済んでいたため、PHP8への移行時に突然このエラーが表面化し、それまで気づかれていなかった潜在バグが一気に顕在化するケースが多く報告されています。

count(): Argument #1 ($value) must be of type Countable|array, null givenの原因は、nullがどこで生まれるかによって大きく3つに分類できます。配列を返すはずの箇所がいつnullにすり替わったのかを追跡すると、修正すべき箇所が見えてきます。以下では実際の開発現場で頻出する3つの発生源を順に見ていきます。

未定義の配列キーにアクセスしてしまうケース

連想配列から値を取り出す際に、そのキーが存在するかどうかを確認せずに読み取ると、PHPは警告付きでnullを返します。この値をそのままcount()に渡すのが最も多いパターンです。

<?php
// PHP 8.3
$product = ['id' => 1, 'name' => 'ノートPC'];

// 'options' キーは存在しないため、$optionsはnullになる
$options = $product['options'];

// nullをcount()に渡すとTypeErrorになる
echo count($options);

連想配列$productにはoptionsキーが定義されていないため、$product['options']はWarningを伴ってnullを返します。未定義キーへのアクセスがそのまま配列扱いされてしまうことが、直後のcount()呼び出しでTypeErrorに直結します。

<?php
// null合体演算子で未定義キーの場合は空配列にフォールバックする
$options = $product['options'] ?? [];

echo count($options); // 0

json_decode()が壊れたJSONでnullを返すケース

外部APIのレスポンスをjson_decode()でパースする処理でも、この落とし穴に遭遇しやすくなっています。フォーマットが不正なJSONを渡すとjson_decode()はnullを返します。

<?php
// 末尾に不要なカンマがあり、JSONとして壊れているレスポンス
$response = '{"tags": ["php", "error"],}';

$data = json_decode($response, true);

// json_decode()が失敗すると$dataはnullになる
$tags = $data['tags'];

echo count($tags);

末尾カンマの混入によりjson_decode()は失敗し、$dataにはnullが代入されます。null値へのキーアクセスもまたWarningを伴ってnullを返すため、$tagsもnullとなり、count()の引数チェックに引っかかります。

<?php
$data = json_decode($response, true);

// デコード失敗時はnullになるため、配列でなければ空配列に置き換える
if (!is_array($data)) {
    $data = [];
}

$tags = $data['tags'] ?? [];

echo count($tags); // 0

自作関数が早期returnでnullを返してしまうケース

チーム内で共有している関数の設計ミスが原因になることもあります。該当データがない場合に空配列ではなくnullを返す実装は、呼び出し側でTypeErrorを誘発します。

<?php
function getActiveUserIds(array $users): ?array
{
    $activeUsers = array_filter($users, fn($user) => $user['active']);

    // 該当者がいない場合に空配列ではなくnullを返してしまっている
    if (count($activeUsers) === 0) {
        return null;
    }

    return array_keys($activeUsers);
}

$users = [
    ['id' => 1, 'active' => false],
];

$activeIds = getActiveUserIds($users);

echo count($activeIds);

戻り値の型が?arrayと曖昧に宣言されているうえ、該当者0件の分岐で意図的にnullを返す実装になっています。呼び出し側は戻り値が常に配列だと信じてcount()に渡すため、この設計自体がバグの温床になります。

<?php
function getActiveUserIds(array $users): array
{
    $activeUsers = array_filter($users, fn($user) => $user['active']);

    // 戻り値の型をarrayに固定し、該当者なしでも空配列を返すよう統一する
    return array_keys($activeUsers);
}

$activeIds = getActiveUserIds($users);

echo count($activeIds); // 0
ITKAGYO運営者デプロイ太郎のアイコン画像

【デプロイ太郎のコメント】このパターン、特に未定義の配列キーが原因のケースは現場でも本当によく見かけます。落ち着いてnullがどこで生まれたか一つずつ確認していきましょう。

count(): Argument #1 ($value) must be of type Countable|array, null givenは環境によって表示が変わる?

count(): Argument #1 ($value) must be of type Countable|array, null givenは、PHPのバージョンによって挙動そのものが異なります。ローカルと本番でPHPバージョンが揃っていないと、開発中は再現しないのに本番だけ落ちるという事故につながるため、事前に把握しておく価値があります。

実行環境表示されるエラーメッセージ
PHP 7.2〜7.4Warning: count(): Parameter must be an array or an object that implements Countable in /path/to/file.php on line 12
PHP 8.0以降(8.4を含む)PHP Fatal error: Uncaught TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given in /path/to/file.php:12

Laravelやワードプレスのカスタムフィールドでcount(): Argument #1 ($value) must be of type Countable|array, null givenが出るのはなぜ?

Laravelでの発生パターン

LaravelのRequestオブジェクトを扱う処理では、フォームのチェックボックスが1つも選択されなかった場合にリクエスト自体にキーが含まれないことがあります。Illuminate\Http\Request::input()は、指定したキーが存在せず第2引数のデフォルト値も省略した場合にnullを返す仕様のため、そのままcount()に渡すとTypeErrorになります。

<?php
public function store(Request $request)
{
    // チェックボックス未選択時、'tags' キー自体がリクエストに含まれない
    $tags = $request->input('tags');

    if (count($tags) === 0) {
        return back()->withErrors(['tags' => 'タグを選択してください']);
    }
    // ...
}

// 修正: 第2引数にデフォルト値の空配列を渡す
$tags = $request->input('tags', []);

if (count($tags) === 0) {
    return back()->withErrors(['tags' => 'タグを選択してください']);
}
Requestから配列を取得するときは、必ず第2引数に空配列のデフォルト値を指定する習慣をつけると安全です。

ワードプレス(ACF)での発生パターン

Advanced Custom Fields(ACF)のリピーターフィールドやギャラリーフィールドは、投稿に1件もデータが登録されていない場合、get_field()が空配列ではなくnullやfalseを返す仕様になっています。テーマファイルでcount()にそのまま渡すと、投稿を新規作成した直後などにTypeErrorが発生します。

<?php
$repeater = get_field('gallery');

// リピーターに1件も登録されていないとget_field()はnullを返す
echo count($repeater);

// 修正: 配列でなければ空配列として扱う
$repeater = is_array($repeater) ? $repeater : [];

echo count($repeater); // 0
ACFのリピーター・ギャラリーフィールドは未入力時にnullやfalseを返すことがあるため、is_array()での防御が定番の対処法です。

count(): Argument #1 ($value) must be of type Countable|array, …と表示される他のパターンは?

bool given と表示される場合

PDOStatement::fetch()は、該当する行がすでに無くなると配列ではなくfalseを返します。この戻り値をそのままcount()に渡すと、null givenではなくbool givenのTypeErrorになります。

<?php
$stmt = $pdo->query('SELECT * FROM tags WHERE post_id = 999');
$row = $stmt->fetch(PDO::FETCH_ASSOC); // 該当行がなければfalseを返す

echo count($row); // TypeError: bool given

// 修正: 配列かどうかを確認してから数える
echo is_array($row) ? count($row) : 0;

string given と表示される場合

カンマ区切りの文字列をexplode()で配列に変換し忘れると、文字列のままcount()に渡すことになり、string givenのTypeErrorが発生します。

<?php
$csvLine = "php,error,typeerror";

// explode(',', $csvLine) をし忘れて文字列のまま渡している
echo count($csvLine); // TypeError: string given

// 修正: 明示的に配列へ変換してから数える
$tags = explode(',', $csvLine);

echo count($tags); // 3

count(): Argument #1 ($value) must be of type Countable|array, null givenはどうデバッグすればいい?

count(): Argument #1 ($value) must be of type Countable|array, null givenをデバッグする際は、まずエラーメッセージ末尾のfile:lineを開き、count()を呼び出している行の直前にvar_dump()やgettype()を1行差し込むのが最短ルートです。値がnullになった時点までさかのぼって処理を追えば、原因箇所を特定できます。

<?php
// count()を呼ぶ前に型を確認するデバッグ用の一行
var_dump(gettype($options), $options);

echo count($options);
ITKAGYO運営者デプロイ太郎のアイコン画像

【デプロイ太郎のコメント】エラーメッセージのfile:lineをまず開いて、その直前の行にvar_dump()を1つ挟むだけでnullの発生源はすぐに絞り込めますよ。

count(): Argument #1 ($value) must be of type Countable|array, null givenを未然に防ぐには?

count(): Argument #1 ($value) must be of type Countable|array, null givenを未然に防ぐには、PHPStanやPsalmといった静的解析ツールを導入し、関数の戻り値が?arrayのようにnull許容になっていないかをコードレビューの段階で洗い出すのが効果的です。あわせて、自作関数の戻り値を常にarray型に固定し、該当データがない場合でも空配列を返す設計を徹底しておきましょう。

<?php
// 戻り値をarray型に固定し、nullを返す余地をなくす
function fetchTagsByPostId(PDO $pdo, int $postId): array
{
    $stmt = $pdo->prepare('SELECT tag FROM tags WHERE post_id = :id');
    $stmt->execute(['id' => $postId]);

    // fetchAll()は該当なしでも常に空配列を返す
    return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
戻り値の型をarrayで固定し、該当なしのときは空配列を返す設計にしておけば、count()のTypeErrorはそもそも起こりにくくなります。

strict_types宣言の有無でこのTypeErrorの発生条件は変わるのか?

count(): Argument #1 ($value) must be of type Countable|array, null givenは、ファイルの先頭にdeclare(strict_types=1)を宣言していてもいなくても、同じ条件で発生します。strict_typesはint・float・string・boolといったスカラー型の暗黙変換を制御する仕組みであり、配列やCountableへの変換ルールには関与しません。

nullは元々どちらのモードでも配列へ暗黙変換できない値のため、strict_types宣言の有無に関わらずcount()の内部的な型チェックはそのままエラーとして働きます。strict_typesを付けたから安全という誤解は禁物で、対策はあくまで呼び出し側でのnullチェックになります。

ITKAGYO運営者デプロイ太郎のアイコン画像

【デプロイ太郎のコメント】ここまで原因パターンを一通り押さえておけば、本番で似たエラーに出会っても慌てず対処できるはずです。焦らず一つずつ確認していきましょう。

よくある質問

Q
ローカルでは動くのに本番環境だけでこのTypeErrorが出るのはなぜですか?
A

多くの場合、ローカルと本番でPHPのバージョンが揃っていないことが原因です。ローカルがPHP7.4以前だとWarningで処理が続行してしまい、同じコードでもPHP8以降の本番環境では致命的なTypeErrorとして止まります。まずはphp -vでバージョンを比較しましょう。

Q
Laravelでこのエラーが起きた場合、どこを確認すればいいですか?
A

Requestオブジェクトからinput()やold()で値を取り出している箇所を優先的に確認してください。キーが存在しない場合にnullを返す仕様のメソッドが多いため、count()に渡す前に第2引数へデフォルト値の空配列を指定しているかどうかを見直すのが近道です。

Q
PHPStanやPsalmでこのエラーを事前に防げますか?
A

はい、防げます。関数やメソッドの戻り値が?arrayのようにnull許容型として宣言されている箇所に、静的解析ツールがcount()呼び出しとの型不一致を警告として検出してくれます。CIパイプラインに組み込んでおくと、デプロイ前にこの種の不具合を機械的に洗い出せます。

Q
TypeErrorをtry-catchで捕まえてユーザーにエラー画面を出さないようにできますか?
A

TypeErrorはErrorクラスを継承しているため、catch (\TypeError $e) で捕捉すること自体は可能です。ただし、これは応急処置にすぎません。根本原因のnullを放置したままだと同じ箇所で別の不具合を招きやすいため、恒久対応としてはnullを生まない設計に修正することをおすすめします。

Q
count(): Argument #1 ($value) must be of type Countable|array, null givenとPHP Warning: Undefined array keyの違いは何ですか?
A

Undefined array keyは未定義キーへのアクセスそのものに対する警告で、処理は止まらずにnullが返されるだけです。一方count()のTypeErrorは、そのnullを配列専用関数に渡した結果として発生する致命的なエラーで、キャッチしなければスクリプトが停止します。つまり前者は原因、後者はその結果として起きる別のエラーという関係です。

この記事と一緒に知っておきたいエラー解決

関連エラー この記事との関連
PHP Warning: Undefined array key の原因と解決方法 本記事のパターン1で扱った、nullが生まれる直接の入口となる警告を詳しく解説しています。
PHP foreach() argument must be of type array|object, null given の原因と解決方法 count()と同じくPHP8のnull厳格化によって発生する、配列操作関数のTypeErrorの兄弟パターンです。
PHP Fatal error: Uncaught Error: Call to a member function on null の原因と解決方法 配列ではなくオブジェクトを期待した箇所でnullを受け取ってしまう、同系統の設計ミスを扱っています。
PHP Fatal error: Uncaught TypeError: Argument 1 passed to … must be of type … given の原因と解決方法 count()以外の自作関数・組み込み関数でも起こる、PHP8の型宣言違反によるTypeError全般の解説です。
JavaScript Uncaught TypeError: Cannot read properties of null の原因と解決方法 言語は異なりますが、null由来のTypeErrorという構造が共通しており、比較しながら理解を深められます。

【出典】参考URL

https://www.php.net/manual/en/function.count.php:count()関数の仕様、PHP8.0.0でのTypeError化・PHP7.2.0でのWarning化の変更履歴の根拠
https://laravel.com/docs/12.x/requests:Illuminate\Http\Request::input()がキー未存在時にnullを返す仕様の根拠
https://github.com/AdvancedCustomFields/acf/issues/701:ACFのリピーターフィールドが未入力時にnull・falseを返す実際の挙動報告
https://github.com/matomo-org/matomo/issues/20323:本記事のエラーメッセージが実際のOSSプロジェクトで発生した事例

コメント

デプロイ太郎のSNSを見てみる!!