PHPの型チェックについて

採用はこちら

PHPの型チェックとは、変数や引数、戻り値、プロパティなどに入っている値が、期待した型であるかを確認することです。

PHPは動的型付けの言語です。

つまり、変数そのものに固定の型があるわけではなく、代入された値によって型が決まります。

$value = '100'; // string
$value = 100;   // int
$value = true;  // bool

そのためPHPでは、意図しない型の値が入り込むことがあります。

例えば、数値として扱いたい値が文字列だったり、配列を想定していたのに null が入っていたりするケースです。

こうしたミスを防ぐために、PHPでは型チェックや型宣言を適切に使うことが重要です。

PHPの型チェックは、主に次のような方法で行います。

方法目的
is_* 関数値の型を確認するis_int($value)
厳密比較値と型を比較する$value === 1
instanceofオブジェクトの型を確認する$user instanceof User
型宣言引数・戻り値・プロパティの型を指定するfunction foo(int $id): string
strict_typesスカラー型の暗黙変換を抑えるdeclare(strict_types=1);
静的解析実行前に型の不整合を検出するPHPStan / Psalm
目次

PHPで扱う主な型

PHPには、文字列、整数、真偽値、配列、オブジェクトなど、さまざまな型があります。

スカラー型

スカラー型とは、単一の値を表す基本的な型です。

内容
int整数1, 100, -5
float浮動小数点数1.5, 3.14
string文字列'hello', "100"
bool真偽値true, false
$id = 10;          // int
$price = 1200.5;   // float
$name = 'Taro';    // string
$isActive = true;  // bool

複合型

複合型は、複数の値や構造を持つ型です。

内容
array配列
objectオブジェクト
callable呼び出し可能な関数・メソッド
iterableforeach で反復可能な値
$numbers = [1, 2, 3]; // array
$user = new User();   // object

特殊な型

PHPには、特殊な意味を持つ型もあります。

内容
null値が存在しないことを表す
mixedどの型でもよい
void戻り値がない
never正常終了しない
function logMessage(string $message): void
{
    echo $message;
}

is_* 関数で型をチェックする

PHPで値の型を確認する基本的な方法が、is_* 系の関数です。

よく使う is_* 関数

代表的な関数は以下です。

関数判定内容
is_int()整数かどうか
is_float()小数かどうか
is_string()文字列かどうか
is_bool()真偽値かどうか
is_array()配列かどうか
is_object()オブジェクトかどうか
is_null()null かどうか
is_numeric()数値または数値文字列かどうか
is_callable()呼び出し可能かどうか
is_iterable()反復可能かどうか

is_int() の使用例

$value = 123;

if (is_int($value)) {
    echo '整数です';
}

is_int() は、値の型が整数かどうかを確認します。

注意したいのは、文字列の '123' は整数ではないという点です。

var_dump(is_int(123));   // true
var_dump(is_int('123')); // false

'123' は数値のように見えますが、型としては string です。

is_numeric() の使用例

is_numeric() は、値が数値、または数値として扱える文字列かどうかを判定します。

var_dump(is_numeric(123));     // true
var_dump(is_numeric('123'));   // true
var_dump(is_numeric('1.5'));   // true
var_dump(is_numeric('abc'));   // false

フォーム入力やURLパラメータは文字列として受け取ることが多いため、is_numeric() が使われることがあります。

$age = $_POST['age'] ?? null;

if (!is_numeric($age)) {
    throw new InvalidArgumentException('年齢は数値で入力してください');
}

$age = (int) $age;

ただし、is_numeric()'1.5'true になります。

IDのように正の整数だけを許可したい場合は、is_numeric() だけでは不十分です。

$id = '1.5';

if (is_numeric($id)) {
    // ここに入ってしまう
}

整数だけを許可したい場合は、正規表現を使う方が安全です。

$id = $_GET['id'] ?? '';

if (!preg_match('/^[1-9][0-9]*$/', $id)) {
    throw new InvalidArgumentException('IDは正の整数で指定してください');
}

$id = (int) $id;

gettype()get_debug_type() で型を確認する

値の型名を取得したい場合は、gettype()get_debug_type() を使います。

gettype() の使用例

$value = 123;

echo gettype($value); // integer

gettype() は、値の型を文字列で返します。

var_dump(gettype('hello')); // string
var_dump(gettype(123));     // integer
var_dump(gettype(true));    // boolean
var_dump(gettype([]));      // array

get_debug_type() の使用例

get_debug_type() は、デバッグ向けにより分かりやすい型名を返します。

$value = new DateTime();

echo get_debug_type($value); // DateTime

オブジェクトの場合、クラス名が返るため、エラーメッセージやデバッグ出力で便利です。

条件分岐には is_* 関数や instanceof を使う

実務では、型によって処理を分けたい場合、gettype() の戻り値を文字列比較するよりも、is_* 関数を使う方が明確です。

避けたい例です。

if (gettype($value) === 'integer') {
    // 動作はするが、意図がやや分かりにくい
}

分かりやすい例です。

if (is_int($value)) {
    // 整数の場合の処理
}

オブジェクトの場合は instanceof を使います。

if ($value instanceof User) {
    // Userクラスのインスタンスの場合の処理
}

===== の違い

PHPの型チェックで特に重要なのが、===== の違いです。

== は緩い比較

== は、値が等しいかを比較します。必要に応じてPHPが型変換を行ってから比較します。

var_dump(1 == '1'); // true

この場合、整数の 1 と文字列の '1' は型が違います。

しかし、== では型変換が行われるため、同じ値として扱われます。

=== は厳密比較

=== は、値だけでなく型も同じかどうかを比較します。

var_dump(1 === '1'); // false

整数の 1 と文字列の '1' は、値としては近いですが型が違います。

そのため、=== では false になります。

実務では === を基本にする

実務では、基本的に ===!== を使うのがおすすめです。

$status = 'published';

if ($status === 'published') {
    echo '公開済みです';
}
$count = 0;

if ($count !== 0) {
    echo '0ではありません';
}

== を使うと、0'0'falsenull、空文字などが絡んだときに、意図しない判定になることがあります。

var_dump(false == 0);   // true
var_dump(false == '');  // true
var_dump(null == '');   // true

特にフォーム入力やAPIレスポンスを扱う場合は、型まで含めて比較する === を使う方が安全です。

instanceof でオブジェクトの型をチェックする

クラスやインターフェースの型を確認する場合は、instanceof を使います。

クラスを判定する

class User {}

$user = new User();

if ($user instanceof User) {
    echo 'Userクラスのインスタンスです';
}

instanceof を使うと、指定したクラスのインスタンスかどうかを判定できます。

インターフェースを判定する

instanceof は、インターフェースに対しても使えます。

interface Exportable
{
    public function export(): array;
}

class CsvExporter implements Exportable
{
    public function export(): array
    {
        return [];
    }
}

$exporter = new CsvExporter();

if ($exporter instanceof Exportable) {
    $exporter->export();
}

実務では、具象クラスよりもインターフェースで判定した方が柔軟な設計になります。

if ($logger instanceof LoggerInterface) {
    $logger->info('message');
}

型宣言で引数や戻り値をチェックする

PHPでは、関数やメソッドの引数、戻り値、クラスプロパティなどに型を宣言できます。

引数の型宣言

function double(int $number): int
{
    return $number * 2;
}

echo double(10); // 20

この例では、double() の引数 $numberint を指定しています。

つまり、この関数は整数を受け取ることを前提にしています。

double([]);

配列のように、明らかに int として扱えない値を渡すと TypeError になります。

戻り値の型宣言

戻り値にも型を宣言できます。

function getUserName(): string
{
    return 'Taro';
}

この関数は、戻り値として string を返すことを宣言しています。

$name = getUserName();

戻り値の型が明確になると、関数を使う側も安心して処理を書けます。

型宣言は常に完全厳密ではない

注意点として、PHPの型宣言は、常に完全に厳密というわけではありません。

strict_types=1 を指定していない場合、intfloatstringbool などのスカラー型では、PHPが変換できる値を自動変換することがあります。

例えば、次のようなコードです。

function add(int $a, int $b): int
{
    return $a + $b;
}

echo add('1', '2');

strict_types=1 がない場合、文字列の '1''2' が整数に変換され、処理が通る場合があります。

一方、厳密にチェックしたい場合は、ファイルの先頭に declare(strict_types=1); を書きます。

<?php
declare(strict_types=1);

function add(int $a, int $b): int
{
    return $a + $b;
}

echo add('1', '2'); // TypeError

strict_types=1 とは

strict_types=1 は、PHPのスカラー型チェックを厳密にするための宣言です。

strict_types=1 の書き方

ファイルの先頭に、次のように書きます。

<?php
declare(strict_types=1);

基本的には、<?php の直後に書くのが一般的です。

<?php
declare(strict_types=1);

function calculateTotal(int $price, int $quantity): int
{
    return $price * $quantity;
}

strict_types=1 がない場合

strict_types=1 がない場合、スカラー型ではPHPが可能な範囲で自動変換を行います。

function calculateTotal(int $price, int $quantity): int
{
    return $price * $quantity;
}

echo calculateTotal('1000', '2'); // 2000 になる場合がある

この挙動は便利に見えますが、意図しない値が通ってしまう原因にもなります。

strict_types=1 がある場合

strict_types=1 を指定すると、型宣言と異なるスカラー型の値を渡した場合に TypeError になります。

<?php
declare(strict_types=1);

function calculateTotal(int $price, int $quantity): int
{
    return $price * $quantity;
}

echo calculateTotal('1000', '2'); // TypeError

文字列の '1000' は、数値として扱えそうに見えます。

しかし型としては string なので、int としては受け付けられません。

strict_types は呼び出し元ファイルに影響する

strict_types で特に重要なのは、関数を定義したファイルではなく、関数を呼び出したファイル側の設定が影響する点です。

例えば、関数定義ファイルが次のようになっているとします。

// functions.php
<?php
declare(strict_types=1);

function add(int $a, int $b): int
{
    return $a + $b;
}

呼び出し元ファイルに strict_types=1 がない場合です。

// index.php
<?php

require 'functions.php';

echo add('1', '2');

この場合、呼び出し元である index.phpstrict_types=1 がないため、文字列が整数に変換されて通る場合があります。

一方、呼び出し元に strict_types=1 がある場合です。

// index.php
<?php
declare(strict_types=1);

require 'functions.php';

echo add('1', '2'); // TypeError

このように、厳密な型チェックを徹底したい場合は、関数定義ファイルだけでなく、呼び出し元のファイルにも declare(strict_types=1); を書く必要があります。

プロパティの型宣言

PHPでは、クラスのプロパティにも型を宣言できます。

プロパティ型の基本

class User
{
    public int $id;
    public string $name;
}

このクラスでは、$idint$namestring として宣言されています。

$user = new User();
$user->id = 1;
$user->name = 'Taro';

コンストラクタと組み合わせる

実務では、コンストラクタで値を受け取り、型付きプロパティに代入する書き方がよく使われます。

class User
{
    public int $id;
    public string $name;

    public function __construct(int $id, string $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
}

PHP 8以降では、コンストラクタプロパティ昇格を使って、より簡潔に書けます。

class User
{
    public function __construct(
        public int $id,
        public string $name,
    ) {}
}

プロパティ代入時の注意点

型付きプロパティに、明らかに合わない値を代入すると TypeError になります。

$user = new User(1, 'Taro');

$user->id = 'abc'; // TypeError

ただし、strict_types=1 がない弱い型付けモードでは、スカラー型の値が自動変換される場合があります。

class User
{
    public int $id;
}

$user = new User();
$user->id = '123';

var_dump($user->id); // int(123) になる場合がある

文字列の '123'int に変換できるため、通る場合があります。

このような暗黙変換を避けたい場合は、代入を行うファイルにも declare(strict_types=1); を書くのが安全です。

Nullable型で null を許可する

値が存在しない可能性がある場合は、Nullable型を使います。

?型 の書き方

function findUserName(int $id): ?string
{
    if ($id === 1) {
        return 'Taro';
    }

    return null;
}

?string は、string または null を返すという意味です。

つまり、次の書き方と同じ意味です。

function findUserName(int $id): string|null
{
    return null;
}

引数で null を許可する

引数にもNullable型を使えます。

function greet(?string $name): string
{
    if ($name === null) {
        return 'Hello, guest';
    }

    return 'Hello, ' . $name;
}

この関数は、文字列または null を受け取れます。

echo greet('Taro');
echo greet(null);

null を許可するかは設計として考える

Nullable型は便利ですが、安易に使いすぎると、関数内で null チェックが増えます。

function sendMail(?string $email): void
{
    if ($email === null) {
        return;
    }

    // メール送信処理
}

本当に null を許可すべきなのか、呼び出し前に必須チェックすべきなのかを考えて使うことが大切です。

ユニオン型で複数の型を許可する

PHP 8.0以降では、ユニオン型を使えます。

ユニオン型の基本

ユニオン型は、「AまたはB」のように複数の型を許可する書き方です。

function formatId(int|string $id): string
{
    return (string) $id;
}

この関数は、int または string を受け取れます。

formatId(123);   // OK
formatId('abc'); // OK
formatId([]);    // TypeError

戻り値にユニオン型を使う

戻り値にもユニオン型を使えます。

function findUser(int $id): User|false
{
    // 見つかれば User、見つからなければ false
}

ただし、現在の設計では、false を返すよりも null を返す方が扱いやすいことが多いです。

function findUser(int $id): ?User
{
    // 見つかれば User、見つからなければ null
}

または、見つからないことを例外として扱う設計もあります。

function findUserOrFail(int $id): User
{
    $user = findUser($id);

    if ($user === null) {
        throw new RuntimeException('ユーザーが見つかりません');
    }

    return $user;
}

ユニオン型と暗黙変換に注意する

strict_types=1 がない場合、ユニオン型でもスカラー型の暗黙変換が起こることがあります。

function test(int|float|bool $value): void
{
    var_dump($value);
}

test('45');   // int(45) になる場合がある
test('45.0'); // float(45.0) になる場合がある
test('abc');  // bool(true) になる場合がある

このような挙動は直感と異なる場合があります。

そのため、外部入力をユニオン型の関数にそのまま渡すのではなく、先にバリデーションしてから渡すのが安全です。

Intersection型で複数条件を満たす型を指定する

PHP 8.1以降では、Intersection型も使えます。

Intersection型の基本

Intersection型は、「AかつB」を表します。

function process(Countable&Iterator $items): void
{
    echo count($items);

    foreach ($items as $item) {
        // 処理
    }
}

この関数は、Countable でもあり、Iterator でもある値だけを受け取ります。

ユニオン型との違い

ユニオン型は「AまたはB」です。

A|B

Intersection型は「AかつB」です。

A&B

使い分けると、次のようになります。

意味
`AB`
A&BAかつB

Intersection型は、インターフェースを組み合わせて厳密に条件を指定したい場合に便利です。

mixedvoidnever の使い方

PHPには、通常の型とは少し違う特別な型があります。

mixed

mixed は、どの型でも受け取れることを表します。

function dumpValue(mixed $value): void
{
    var_dump($value);
}

mixed は便利ですが、使いすぎると型安全性が下がります。

function process(mixed $value): void
{
    // $value が何なのか分かりにくい
}

外部入力、ログ出力、デバッグ関数など、どうしても型を絞れない場面に限定して使うのがおすすめです。

void

void は、戻り値がないことを表します。

function saveLog(string $message): void
{
    file_put_contents('app.log', $message, FILE_APPEND);
}

この関数は、何かを返すための関数ではなく、ログを書き込むための関数です。

never

never は、関数が正常に戻らないことを表します。

function abort(): never
{
    throw new RuntimeException('Aborted');
}

必ず例外を投げる関数や、exit する関数などに使います。

function redirect(string $url): never
{
    header('Location: ' . $url);
    exit;
}

array の型チェックの限界

PHPでは、配列に対して array 型を指定できます。

array 型の基本

function calculateTotal(array $prices): int
{
    return array_sum($prices);
}

この関数は、引数として配列を受け取ります。

calculateTotal([100, 200, 300]);

しかし、PHP標準の型宣言だけでは、配列の中身までは厳密に表現しにくいです。

配列の中身までは標準型だけで表現しにくい

例えば、次のような型はPHP標準の関数シグネチャでは直接書けません。

// このような書き方はPHP標準の型宣言としては使えない
function sum(array<int> $numbers): int {}
function sendEmails(User[] $users): void {}

つまり、array と書けても、「整数だけの配列」や「Userだけの配列」まではPHP本体だけで十分に表現できません。

PHPDocで配列の中身を補足する

配列の中身を表現したい場合は、PHPDocを使います。

/**
 * @param int[] $prices
 */
function calculateTotal(array $prices): int
{
    return array_sum($prices);
}

オブジェクト配列なら、次のように書きます。

/**
 * @param User[] $users
 */
function sendEmails(array $users): void
{
    foreach ($users as $user) {
        // Userとして扱う
    }
}

PHPDocはPHP本体による実行時チェックではありません。

しかし、PHPStanやPsalmなどの静的解析ツールと組み合わせることで、配列の中身の型チェックに役立ちます。

DTOやコレクションクラスを使う

配列の構造が複雑な場合は、配列のまま渡すよりもDTOや専用クラスにする方が安全です。

final class RegisterUserInput
{
    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}
}
function register(RegisterUserInput $input): void
{
    echo $input->name;
}

配列の場合はキー名の打ち間違いに気づきにくいですが、DTOにするとプロパティや型が明確になります。

キャストと型チェックの違い

型チェックとキャストは似ていますが、役割が違います。

型チェックは型を確認する

型チェックは、値が期待した型かどうかを確認する処理です。

$value = '123';

if (is_string($value)) {
    echo '文字列です';
}

この処理では、$value の型を確認しているだけです。

キャストは型を変換する

キャストは、値を別の型に変換する処理です。

$value = '123';

$intValue = (int) $value;

var_dump($intValue); // int(123)

キャストでは、値の型が変わります。

$value = '123';
$value = (int) $value;

キャストは意図しない変換に注意する

キャストは便利ですが、意図しない変換が起こることがあります。

var_dump((int) '123abc'); // int(123)
var_dump((int) 'abc');    // int(0)
var_dump((bool) 'false'); // bool(true)

特に注意したいのが、文字列の 'false' です。

var_dump((bool) 'false'); // true

'false' は空文字ではないため、真偽値に変換すると true になります。

文字列の真偽値を安全に扱いたい場合は、filter_var() を使う方法があります。

$value = 'false';

$bool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);

var_dump($bool); // false

型チェックとバリデーションの違い

型チェックとバリデーションは混同されやすいですが、目的が違います。

型チェックは型を見る

型チェックは、値がどの型かを確認します。

$email = 'abc';

var_dump(is_string($email)); // true

この例では、$email が文字列かどうかだけを確認しています。

バリデーションは値の妥当性を見る

バリデーションは、値がルールに合っているかを確認します。

$email = 'abc';

var_dump(filter_var($email, FILTER_VALIDATE_EMAIL)); // false

'abc' は文字列ですが、メールアドレスとしては不正です。

つまり、型チェックだけでは不十分なケースがあります。

チェック
型チェック文字列かどうか
フォーマットチェックメール形式かどうか
範囲チェック1以上かどうか
桁数チェック255文字以内かどうか
業務ルールチェック在庫があるかどうか

実務では、型チェックとバリデーションを組み合わせて使います。

外部入力の型チェック

Webアプリでは、外部から入ってくる値をそのまま信用しないことが重要です。

$_GET$_POST は文字列として扱う

URLパラメータやフォーム入力は、基本的に文字列として受け取ることが多いです。

$id = $_GET['id'];

$user = findUser($id);

このコードでは、$id が本当に整数として扱える値か分かりません。

安全にするには、入口でチェックします。

$id = $_GET['id'] ?? '';

if (!preg_match('/^[1-9][0-9]*$/', $id)) {
    http_response_code(400);
    exit('Invalid id');
}

$user = findUser((int) $id);

関数側にも型を付けます。

function findUser(int $id): ?User
{
    // ユーザー検索処理
}

このように、外部入力は入口で検証し、内部では型宣言された値として扱うのが安全です。

JSON入力をチェックする

APIでJSONを受け取る場合は、json_decode() の結果が想定通りか確認します。

$json = file_get_contents('php://input');

$data = json_decode($json, true);

if (!is_array($data)) {
    throw new InvalidArgumentException('JSONの形式が不正です');
}

そのうえで、各項目をチェックします。

$name = $data['name'] ?? null;

if (!is_string($name) || $name === '') {
    throw new InvalidArgumentException('nameは必須です');
}

入力チェックを関数化すると、コードを整理しやすくなります。

function requireString(array $data, string $key): string
{
    if (!array_key_exists($key, $data)) {
        throw new InvalidArgumentException("{$key} は必須です");
    }

    if (!is_string($data[$key])) {
        throw new InvalidArgumentException("{$key} は文字列で指定してください");
    }

    return $data[$key];
}

使用例です。

$name = requireString($data, 'name');

empty() を型チェックとして使わない

PHPでは empty() がよく使われますが、型チェックとは別物です。

empty() の挙動

empty() は、値が空とみなされるかどうかを判定します。

empty('');   // true
empty('0');  // true
empty(0);    // true
empty(null); // true
empty([]);   // true

注意すべきは、empty('0')true になることです。

empty() による入力チェックの注意点

例えば、数量を入力してもらうフォームを考えます。

if (empty($_POST['quantity'])) {
    echo '数量を入力してください';
}

この場合、quantity'0' が入っていると、空と判定される可能性があります。

0 を有効な値として扱うなら、次のように明示的に判定する方が安全です。

$quantity = $_POST['quantity'] ?? null;

if ($quantity === null || $quantity === '') {
    echo '数量を入力してください';
}

empty() は便利ですが、型チェックや必須チェックの代わりとして安易に使うと、意図しないバグにつながります。

isset()array_key_exists() の違い

配列のキー確認では、isset()array_key_exists() の違いも重要です。

isset()null を false と判定する

$data = [
    'name' => null,
];

var_dump(isset($data['name'])); // false

isset() は、キーが存在していても値が null の場合は false を返します。

array_key_exists() はキーの存在を見る

$data = [
    'name' => null,
];

var_dump(array_key_exists('name', $data)); // true

array_key_exists() は、値が null でも、キーが存在すれば true を返します。

使い分け

使い分けは次のように考えると分かりやすいです。

やりたいこと使うもの
キーが存在し、値が null ではないことを確認したいisset()
値が null でもキーの存在を確認したいarray_key_exists()

入力データで「項目が送られているか」を正確に見たい場合は、array_key_exists() が向いています。

if (!array_key_exists('name', $data)) {
    throw new InvalidArgumentException('nameは必須です');
}

静的解析ツールで型チェックを強化する

PHP本体の型宣言だけでは、完全に防げないミスもあります。

そこで役立つのが静的解析ツールです。

静的解析ツールとは

静的解析ツールは、PHPコードを実行せずに解析し、型の不整合やバグになりそうな箇所を検出するツールです。

代表的なものには、PHPStanやPsalmがあります。

function add(int $a, int $b): int
{
    return $a + $b;
}

add('hello', []);

このようなコードは、実行前に問題として検出できます。

PHPDocと組み合わせる

静的解析ツールは、PHPDocに書かれた型情報も読み取れます。

/**
 * @param int[] $numbers
 */
function sumNumbers(array $numbers): int
{
    return array_sum($numbers);
}

sumNumbers([1, 2, 'abc']);

PHP本体の型宣言では、array の中身までは十分に表現できません。

しかし、PHPDocと静的解析を組み合わせることで、「整数の配列に文字列が混ざっている」といった問題を検出できます。

実務では導入する価値が高い

PHPStanやPsalmを導入すると、次のような問題を早期に見つけやすくなります。

検出しやすい問題
引数の型違いintstring を渡している
戻り値の型違いUser を返すべき関数が null を返す
存在しないメソッド呼び出し$user->getEamil() のようなタイポ
配列キーの不足$data['name'] が存在しない可能性
PHPDocとの不一致int[] に文字列が混ざっている

型安全性を高めたい場合は、PHP本体の型宣言に加えて、静的解析ツールを使うのがおすすめです。

実務でおすすめの型チェック方針

PHPで安全に型を扱うには、単体のテクニックではなく、複数の方法を組み合わせることが重要です。

新規コードでは strict_types=1 を使う

新しくPHPコードを書く場合は、基本的にファイル先頭に declare(strict_types=1); を書く方針がおすすめです。

<?php
declare(strict_types=1);

これにより、スカラー型の暗黙変換を減らせます。

ただし、既存プロジェクトに一括で導入すると、これまで暗黙変換に依存していた箇所でエラーが出る可能性があります。

既存コードでは、段階的に導入するのが現実的です。

引数と戻り値に型を書く

関数やメソッドには、できるだけ引数と戻り値の型を書きます。

function getDisplayName(int $userId): string
{
    $user = findUser($userId);

    if ($user === null) {
        return 'Guest';
    }

    return $user->name;
}

型が明確になると、関数の使い方が分かりやすくなります。

プロパティにも型を書く

クラスのプロパティにも型を付けると、オブジェクトの状態が安定します。

final class Article
{
    public function __construct(
        public int $id,
        public string $title,
        public bool $isPublished,
    ) {}
}

どのプロパティにどの型の値が入るのかが明確になります。

比較には ===!== を使う

比較では、基本的に ===!== を使います。

if ($status === 'published') {
    // 公開済み
}
if ($count !== 0) {
    // 0ではない
}

== は型変換を伴うため、意図しない一致が起きる可能性があります。

外部入力は入口で検証する

$_GET$_POST、JSON、Cookie、外部APIレスポンスなどは、基本的に信用しないようにします。

$id = $_GET['id'] ?? '';

if (!preg_match('/^[1-9][0-9]*$/', $id)) {
    http_response_code(400);
    exit('Invalid id');
}

$id = (int) $id;

外部入力を入口で検証し、内部処理では型が保証された値として扱うのが理想です。

配列よりDTOを使う

複雑なデータを配列で渡すと、キーの打ち間違いや型の不一致に気づきにくくなります。

function register(array $user): void
{
    echo $user['name'];
}

より安全にするなら、DTOを使います。

final class RegisterUserInput
{
    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}
}
function register(RegisterUserInput $input): void
{
    echo $input->name;
}

DTOにすることで、データ構造と型が明確になります。

よくあるNG例と改善例

PHPの型チェックでは、よくある失敗パターンがあります。

ここでは、実務で注意したい例を紹介します。

== を使って比較している

NG例です。

if ($status == 0) {
    // 意図しない一致が起きる可能性
}

改善例です。

if ($status === 0) {
    // intの0だけを判定
}

empty() で必須チェックしている

NG例です。

if (empty($_POST['quantity'])) {
    echo '数量を入力してください';
}

quantity'0' の場合も空扱いされる可能性があります。

改善例です。

$quantity = $_POST['quantity'] ?? null;

if ($quantity === null || $quantity === '') {
    echo '数量を入力してください';
}

型宣言がない

NG例です。

function createUser($name, $age)
{
    // ユーザー作成処理
}

改善例です。

function createUser(string $name, int $age): User
{
    // ユーザー作成処理
}

何でも配列で渡している

NG例です。

function register(array $user): void
{
    echo $user['name'];
}

改善例です。

final class RegisterUserInput
{
    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}
}

function register(RegisterUserInput $input): void
{
    echo $input->name;
}

配列よりもDTOを使った方が、型や構造が明確になります。

PHPの型チェックで押さえるべきポイント

PHPの型チェックでは、次のポイントを押さえておくと実務で役立ちます。

型確認には is_* 関数を使う

値の型を確認したい場合は、is_int()is_string()is_array() などを使います。

if (is_string($name)) {
    echo $name;
}

オブジェクトの確認には instanceof を使う

クラスやインターフェースの型を確認する場合は、instanceof を使います。

if ($user instanceof User) {
    echo $user->name;
}

比較には === を使う

値と型の両方を比較したい場合は、=== を使います。

if ($id === 1) {
    echo 'IDは1です';
}

型宣言を積極的に使う

関数の引数、戻り値、プロパティには型を付けるのがおすすめです。

function findUser(int $id): ?User
{
    // ユーザー検索
}

厳密にしたいなら strict_types=1 を使う

スカラー型の暗黙変換を減らしたい場合は、ファイルの先頭に declare(strict_types=1); を書きます。

<?php
declare(strict_types=1);

外部入力は必ず検証する

外部から入ってくる値は、期待した型とは限りません。

$id = $_GET['id'] ?? '';

if (!preg_match('/^[1-9][0-9]*$/', $id)) {
    throw new InvalidArgumentException('IDが不正です');
}

静的解析ツールを導入する

PHPStanやPsalmを使うと、実行前に型の不整合を検出しやすくなります。

/**
 * @param int[] $numbers
 */
function sumNumbers(array $numbers): int
{
    return array_sum($numbers);
}

まとめ

PHPの型チェックは、バグを防ぎ、コードの安全性と読みやすさを高めるために重要です。

PHPは動的型付けの言語であり、文脈によって自動的に型変換されることがあります。

そのため、何も対策しないまま実装すると、文字列、数値、真偽値、null などが意図せず混ざり、予期しない動作につながることがあります。

基本的な型確認には、is_int()is_string()is_array() などの is_* 関数を使います。

オブジェクトの型確認には instanceof を使います。

比較では、型変換を伴う == ではなく、値と型の両方を比較する === を使うのが安全です。

関数やメソッドには、引数と戻り値の型を宣言しましょう。

クラスのプロパティにも型を付けることで、オブジェクトの状態を安定させやすくなります。

ただし、strict_types=1 がない場合、スカラー型では暗黙の型変換が起こることがあります。

より厳密に型を扱いたい場合は、ファイルの先頭に declare(strict_types=1); を書くのがおすすめです。

また、$_GET$_POST、JSON、Cookie、外部APIレスポンスなどの外部入力は、必ず入口で検証しましょう。

外部入力をバリデーションし、内部では型が保証された値として扱う設計にすると、コード全体が安定します。

実務では、次の方針を基本にするとよいです。

方針理由
declare(strict_types=1); を使う暗黙の型変換を減らせる
引数・戻り値に型を書く関数の使い方が明確になる
プロパティに型を書くオブジェクトの状態が安定する
=== / !== を使う意図しない型変換を防げる
外部入力を検証する不正な値の混入を防げる
PHPDocを使う配列の中身などを補足できる
PHPStanやPsalmを使う実行前に型の問題を検出できる

PHPの型チェックは、単にエラーを防ぐためだけのものではありません。

コードの意図を明確にし、保守しやすくし、チーム開発でも安心して変更できる状態を作るための重要な仕組みです。

以上、PHPの型チェックについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次