PHPの変数の型宣言について

採用はこちら

PHPは、もともと動的型付け言語です。

動的型付けとは、変数を使うときにあらかじめ型を固定せず、代入された値によって実行時に型が判断される仕組みです。

たとえば、PHPでは次のように同じ変数へ異なる型の値を代入できます。

$value = 'こんにちは';
$value = 100;
$value = true;

このように柔軟に書けるのがPHPの特徴です。

一方で、現在のPHPでは、関数の引数、戻り値、クラスのプロパティ、クラス定数などに型宣言を付けられます。

型宣言を使うと、

function greet(string $name): void
{
    echo "こんにちは、{$name}さん";
}

のように、「この引数には文字列を渡す」「この関数は値を返さない」といったルールをコード上で明確にできます。

ただし、最初に重要な注意点があります。

PHPでは、通常のローカル変数に対して、次のような型宣言はできません。

string $name = 'Taro'; // NG
int $age = 20;         // NG

PHPで型宣言できるのは、主に以下の場所です。

// 関数の引数
function hello(string $name): void
{
    echo $name;
}

// 関数の戻り値
function getAge(): int
{
    return 20;
}

// クラスのプロパティ
class User
{
    public string $name;
    public int $age;
}

// クラス定数 PHP 8.3以降
class Status
{
    public const string ACTIVE = 'active';
}

つまり、PHPの型宣言は「すべての変数に型を付ける仕組み」ではなく、関数の引数・戻り値・クラスプロパティ・クラス定数などに型を指定する仕組みだと理解するとよいでしょう。

目次

PHPの型宣言とは

PHPの型宣言とは、関数やメソッド、プロパティなどに対して、受け取る値や返す値の型を指定する機能です。

たとえば、次のコードを見てください。

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

echo double(10); // 20

この関数では、引数 $numint、戻り値にも int を指定しています。

function double(int $num): int

この部分は、次のような意味です。

int $num

これは「引数 $num は整数」という意味です。

: int

これは「戻り値は整数」という意味です。

型宣言を付けることで、この関数は「整数を受け取り、整数を返す関数」だと一目で分かります。

型宣言を使うメリット

PHPは型宣言なしでも動作します。

しかし、実務でコードを書くなら、型宣言を使うメリットは非常に大きいです。

バグに早く気づける

型宣言がない場合、本来想定していない値が渡されても、コードを実行するまで問題に気づきにくいことがあります。

function calculateTax($price)
{
    return $price * 0.1;
}

echo calculateTax('abc');

この関数では、本来 $price に数値を渡したいはずです。

しかし、型宣言がないため、文字列や配列など、意図しない値が渡される可能性があります。

型宣言を付けると、関数が期待する値を明確にできます。

function calculateTax(int $price): float
{
    return $price * 0.1;
}

これにより、コードを読む人も「この関数には整数の価格を渡すのだ」と分かります。

また、指定した型と合わない値が渡された場合には、エラーによって問題に気づきやすくなります。

ただし、ここで注意したいのが strict_types です。

PHPでは、strict_types を有効にしていない場合、intfloatstringbool などのスカラー型では暗黙の型変換が行われることがあります。

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

function showString(string $value): void
{
    var_dump($value);
}

showString(123);

strict_types を有効にしていない場合、123 が文字列 "123" に変換されることがあります。

より厳密に型をチェックしたい場合は、PHPファイルの先頭に次のように書きます。

<?php

declare(strict_types=1);

すると、スカラー型の暗黙変換が抑制され、型の不一致に気づきやすくなります。

コードの意図が読みやすくなる

型宣言がないコードは、関数の中身を読まないと何を渡せばよいか分かりにくくなります。

function createUser($name, $age, $isAdmin)
{
    // ...
}

このコードだけを見ると、$name$age$isAdmin にどのような値を渡すべきか分かりません。

型宣言を付けると、意図が明確になります。

function createUser(string $name, int $age, bool $isAdmin): User
{
    // ...
}

このコードを見れば、次のことがすぐに分かります。

  • $name は文字列
  • $age は整数
  • $isAdmin は真偽値
  • 戻り値は User オブジェクト

コードの読みやすさは、個人開発でもチーム開発でも重要です。

特にWeb制作やWordPress開発では、あとから自分や他の人がコードを修正することが多いため、型宣言によって意図を明確にしておくメリットは大きいです。

エディタやIDEの補完が効きやすくなる

型宣言があると、VS CodeやPhpStormなどのエディタがコードを理解しやすくなります。

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

function sendMail(User $user): void
{
    $user->getEmail();
}

User $user と書いておけば、エディタは $userUser クラスのインスタンスだと判断できます。

そのため、getEmail() などのメソッド補完が効きやすくなります。

型宣言がない場合、エディタは $user の正体を判断しにくくなります。

function sendMail($user): void
{
    $user->getEmail();
}

このようなコードでは、補完や静的解析の精度が下がりやすくなります。

チーム開発で安全になる

型宣言は、チーム開発でも役立ちます。

たとえば、ある関数を別の開発者が使うとき、型宣言があれば「どのような値を渡せばよいか」が分かりやすくなります。

function publishArticle(int $articleId, bool $notify): void
{
    // 記事公開処理
}

この関数は、記事IDとして整数、通知するかどうかとして真偽値を受け取ることが分かります。

型宣言がない場合は、関数名や中身を読んで判断する必要があります。

function publishArticle($articleId, $notify)
{
    // 記事公開処理
}

規模の大きいプロジェクトほど、型宣言の有無によって保守性に差が出ます。

PHPで使える主な型

PHPの型宣言では、さまざまな型を指定できます。

よく使う型は以下です。

意味
int整数1, 100, -5
float小数1.5, 3.14
string文字列'hello'
bool真偽値true, false
array配列['a', 'b']
objectオブジェクトnew stdClass()
callable呼び出し可能な関数・メソッドfunction () {}
iterableforeach で回せる値配列、Traversable
mixedどんな型でも可PHP 8.0以降
void戻り値なし戻り値専用
never正常終了しないPHP 8.1以降、戻り値専用
クラス名特定クラスのインスタンスUser
インターフェース名特定インターフェースの実装LoggerInterface
self現在のクラスクラス内で使用
parent親クラス継承時に使用
static遅延静的束縛の型戻り値などで使用

なお、型宣言では、基本的に次のような正式な型名を使います。

int
float
string
bool

次のような別名は、型宣言としては使わないようにしましょう。

integer
double
boolean

たとえば、真偽値を指定したい場合は boolean ではなく bool を使います。

function isPublished(bool $flag): void
{
    // ...
}

引数の型宣言

最も基本的なのが、関数やメソッドの引数に型を付ける書き方です。

function greet(string $name): void
{
    echo "こんにちは、{$name}さん";
}

greet('田中');

string $name と書くことで、$name には文字列を渡すことを明示しています。

複数の引数にも型宣言できます。

function createProduct(string $name, int $price, bool $isPublished): array
{
    return [
        'name' => $name,
        'price' => $price,
        'is_published' => $isPublished,
    ];
}

この関数では、商品名、価格、公開状態の型が明確です。

createProduct('Tシャツ', 3000, true);

このように、関数を呼び出す側も迷いにくくなります。

戻り値の型宣言

関数の戻り値にも型を指定できます。

戻り値の型は、引数リストの後に : 型名 の形で書きます。

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

この関数は、整数の価格と数量を受け取り、整数の合計金額を返します。

文字列を返す場合は次のように書きます。

function getFullName(string $firstName, string $lastName): string
{
    return $firstName . ' ' . $lastName;
}

配列を返す場合は次のように書きます。

function getUserData(): array
{
    return [
        'name' => 'Taro',
        'age' => 25,
    ];
}

戻り値の型宣言を付けることで、関数の利用者は「この関数が何を返すのか」を予測しやすくなります。

void型

void は「戻り値がない」ことを表す型です。

function logMessage(string $message): void
{
    echo $message;
}

void を指定した関数では、値を返してはいけません。

次のように、値を返さない return; は使えます。

function save(): void
{
    // 保存処理
    return;
}

しかし、値を返すことはできません。

function save(): void
{
    return true; // NG
}

void は、データを返す必要がない処理に使います。

たとえば、ログ出力、メール送信、データ保存、画面出力などです。

function sendNotification(string $message): void
{
    // 通知処理
}

never型

never は、PHP 8.1以降で使える戻り値専用の型です。

never は、「この関数は正常に呼び出し元へ戻らない」ことを表します。

たとえば、必ず例外を投げる関数です。

function throwError(string $message): never
{
    throw new RuntimeException($message);
}

また、処理を終了する関数にも使えます。

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

never を指定した関数では、return; もできません。

function stop(): never
{
    return; // NG
}

never は、一般的なWeb制作では頻繁に使う型ではありません。

ただし、フレームワークやライブラリ、共通処理を設計するときには役立つ場面があります。

クラス名を型として指定する

PHPでは、独自に定義したクラス名を型として指定できます。

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

function sendWelcomeMail(User $user): void
{
    echo "{$user->email} にメールを送信します";
}

この場合、sendWelcomeMail() には User クラスのインスタンスを渡す必要があります。

$user = new User('Taro', 'taro@example.com');

sendWelcomeMail($user); // OK
sendWelcomeMail('Taro'); // NG

クラス名を型として指定すると、関数がどのようなオブジェクトを必要としているか明確になります。

Webアプリケーションでは、ユーザー、記事、商品、注文などをクラスで表現することがあります。

class Article
{
    public function __construct(
        public string $title,
        public string $body
    ) {}
}

function publishArticle(Article $article): void
{
    // 記事公開処理
}

このように書くと、publishArticle()Article オブジェクトを受け取る関数だと分かります。

インターフェースを型として指定する

クラス名だけでなく、インターフェースも型として指定できます。

interface MailerInterface
{
    public function send(string $to, string $subject, string $body): void;
}

class SmtpMailer implements MailerInterface
{
    public function send(string $to, string $subject, string $body): void
    {
        // SMTP送信処理
    }
}

function notifyUser(MailerInterface $mailer): void
{
    $mailer->send(
        'user@example.com',
        'お知らせ',
        '本文です'
    );
}

この場合、notifyUser()MailerInterface を実装したクラスであれば受け取れます。

インターフェースを型にすると、具体的なクラスに依存しにくくなります。

たとえば、あとからメール送信方法をSMTPからAPI送信に変更したい場合でも、同じインターフェースを実装していれば差し替えやすくなります。

class ApiMailer implements MailerInterface
{
    public function send(string $to, string $subject, string $body): void
    {
        // API経由でメール送信
    }
}

実務では、具体的なクラスよりもインターフェースに依存する設計が有効な場面があります。

nullable型

nullable型は、「指定した型、または null を許可する」書き方です。

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

    return null;
}

この ?string は、次の意味です。

string または null

つまり、文字列が返ることもあれば、null が返ることもあります。

引数にも使えます。

function greet(?string $name): void
{
    if ($name === null) {
        echo 'ゲストさん、こんにちは';
        return;
    }

    echo "{$name}さん、こんにちは";
}

?string を使うと、「この値はnullになる可能性がある」と明確にできます。

これは実務でとても重要です。

たとえば、データベースからユーザーを取得する関数では、ユーザーが見つからない場合があります。

function findUserById(int $id): ?User
{
    if ($id === 1) {
        return new User('Taro', 'taro@example.com');
    }

    return null;
}

このように ?User と書いておけば、呼び出し側は null チェックが必要だと分かります。

$user = findUserById(10);

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

echo $user->name;

Union型

Union型は、「複数の型のどれか」を指定する書き方です。

PHP 8.0以降で使えます。

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

この場合、$id には int または string を渡せます。

formatId(100);     // OK
formatId('A100');  // OK
formatId(true);    // NG

戻り値にもUnion型を使えます。

function findUser(int $id): User|false
{
    if ($id === 1) {
        return new User('Taro', 'taro@example.com');
    }

    return false;
}

この例では、ユーザーが見つかれば User、見つからなければ false を返します。

ただし、現代的な設計では、User|false よりも ?User の方が分かりやすい場合もあります。

function findUser(int $id): ?User
{
    if ($id === 1) {
        return new User('Taro', 'taro@example.com');
    }

    return null;
}

false を返す設計は、PHPやWordPressの古い関数ではよく見られます。

一方で、自分で新しく設計するコードでは、見つからない場合に null を返す方が扱いやすいことも多いです。

nullable型とUnion型の違い

次の2つは、ほぼ同じ意味です。

?string
string|null

どちらも「文字列またはnull」という意味です。

つまり、以下の2つは同じように考えられます。

function getName(): ?string
{
    return null;
}
function getName(): string|null
{
    return null;
}

ただし、複数の型と null を組み合わせる場合は、Union型で書きます。

function getValue(): int|string|null
{
    return null;
}

この場合、戻り値は intstringnull のいずれかです。

Intersection型

Intersection型は、「複数の型をすべて満たす」ことを表します。

PHP 8.1以降で使えます。

interface Loggable
{
    public function log(string $message): void;
}

interface SerializableData
{
    public function toArray(): array;
}

function exportData(Loggable&SerializableData $object): array
{
    $object->log('export');

    return $object->toArray();
}

Loggable&SerializableData は、次の意味です。

Loggable でもあり、SerializableData でもある

Union型の | は「または」です。

A|B

Intersection型の & は「かつ」です。

A&B

Intersection型は、通常のWeb制作では頻繁に使うものではありません。

しかし、オブジェクト指向設計やライブラリ開発では、複数の役割を持つオブジェクトを受け取りたいときに役立ちます。

DNF型

PHP 8.2以降では、DNF型も使えます。

DNFは、Disjunctive Normal Formの略です。

簡単に言うと、Union型とIntersection型を組み合わせる書き方です。

function handle((LoggerInterface&Countable)|ArrayObject $value): void
{
    // ...
}

これは、次の意味です。

LoggerInterface かつ Countable
または
ArrayObject

DNF型は、通常のWeb制作やWordPress開発で頻繁に使うものではありません。

まずは、以下の型を理解しておけば十分です。

string
int
float
bool
array
?string
?User
int|string
User|null

DNF型は、より複雑な型設計が必要になった段階で学べばよいでしょう。

mixed型

mixed は、「どんな型でも受け取る」ことを表す型です。

PHP 8.0以降で使えます。

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

mixed は、次のような値をすべて受け取れます。

debugValue(1);
debugValue('text');
debugValue(true);
debugValue(null);
debugValue([1, 2, 3]);
debugValue(new stdClass());

ただし、mixed を多用すると型宣言のメリットが薄くなります。

型宣言の目的は、「何を受け取るのか」「何を返すのか」を明確にすることです。

何でも受け取れる mixed ばかり使うと、その明確さが失われます。

実務では、本当にどんな値でも入り得る場所で使うのがおすすめです。

function saveOption(string $key, mixed $value): void
{
    // 設定値を保存
}

設定値、デバッグ用関数、汎用的なライブラリ関数などでは mixed が役立つことがあります。

array型

配列を受け取る場合は array を使います。

function renderList(array $items): string
{
    $html = '<ul>';

    foreach ($items as $item) {
        $html .= '<li>' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '</li>';
    }

    $html .= '</ul>';

    return $html;
}

ただし、PHPの型宣言だけでは、配列の中身の型までは指定できません。

たとえば、次のような書き方はPHP本体の型宣言としては使えません。

function renderNames(array<string> $names): string
{
    // NG
}
function renderNames(string[] $names): string
{
    // NG
}

PHPの型宣言で指定できるのは、あくまで「配列であること」です。

function renderNames(array $names): string
{
    return implode(', ', $names);
}

そのため、配列の中身の型を伝えたい場合は、PHPDocを併用することがあります。

/**
 * @param string[] $names
 */
function renderNames(array $names): string
{
    return implode(', ', $names);
}

または、次のように書くこともあります。

/**
 * @param array<int, string> $names
 */
function renderNames(array $names): string
{
    return implode(', ', $names);
}

ただし、PHPDocはPHP本体が実行時にチェックするものではありません。

PHPStan、Psalm、IDE、エディタなどが、静的解析や補完のために利用します。

実行時に配列の中身まで厳密に保証したい場合は、自分でチェック処理を書くか、配列ではなく専用のクラスを使う方法があります。

class TagList
{
    /**
     * @param string[] $tags
     */
    public function __construct(
        public array $tags
    ) {}
}

callable型

callable は、「呼び出し可能なもの」を表す型です。

function execute(callable $callback): void
{
    $callback();
}

execute(function () {
    echo '実行されました';
});

callable には、無名関数だけでなく、文字列の関数名や配列形式のコールバックも渡せます。

function run(callable $callback): void
{
    $callback();
}

run('phpinfo');

オブジェクトのメソッドを渡すこともできます。

class Sample
{
    public function run(): void
    {
        echo 'run';
    }
}

$sample = new Sample();

run([$sample, 'run']);

ただし、callable はクラスプロパティの型としては使えません。

class Task
{
    public callable $callback; // NG
}

プロパティにコールバックを保持したい場合は、Closure を使う方法があります。

class Task
{
    public Closure $callback;
}

ただし、Closurecallable は完全に同じではありません。

callable は「呼び出し可能なもの」を広く受け取れますが、Closure は無名関数などの Closure オブジェクトを表します。

より設計を明確にしたい場合は、コールバックではなくインターフェースを定義する方法もあります。

interface TaskHandlerInterface
{
    public function handle(): void;
}

class Task
{
    public function __construct(
        public TaskHandlerInterface $handler
    ) {}
}

iterable型

iterable は、foreach で回せる値を表します。

function outputItems(iterable $items): void
{
    foreach ($items as $item) {
        echo $item;
    }
}

配列を渡せます。

outputItems(['a', 'b', 'c']);

Generator も渡せます。

function numbers(): Generator
{
    yield 1;
    yield 2;
    yield 3;
}

outputItems(numbers());

大量のデータを扱う場合、すべてを配列としてメモリに持つより、Generator を使って順番に処理した方が効率的なことがあります。

そのため、関数が配列に限定されず、foreach で回せればよい場合は、array ではなく iterable を指定すると柔軟です。

function renderOptions(iterable $options): string
{
    $html = '';

    foreach ($options as $value => $label) {
        $html .= sprintf(
            '<option value="%s">%s</option>',
            htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'),
            htmlspecialchars((string) $label, ENT_QUOTES, 'UTF-8')
        );
    }

    return $html;
}

object型

object は、「何らかのオブジェクト」を表します。

function dumpObject(object $object): void
{
    var_dump($object);
}

ただし、実務では object よりも、具体的なクラス名やインターフェース名を指定する方が安全です。

function send(User $user): void
{
    // よい
}
function send(object $user): void
{
    // 何のオブジェクトか分かりにくい
}

object は、どんなオブジェクトでも受け取れるため柔軟ですが、その分、関数の意図が曖昧になりやすいです。

基本的には、具体的な型を指定できるならそちらを優先しましょう。

false型、true型、null型

PHPでは、特定のリテラル型も使えます。

function alwaysTrue(): true
{
    return true;
}
function alwaysFalse(): false
{
    return false;
}
function alwaysNull(): null
{
    return null;
}

ただし、通常のアプリケーションコードでは多用しません。

よく見かけるのは、処理に失敗した場合に false を返すような設計です。

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

PHPやWordPressの既存関数では、失敗時に false を返すものが多くあります。

一方、自分で新しく関数を設計する場合は、見つからないことを表すなら null を使った方が分かりやすい場合もあります。

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

どちらを使うかは、プロジェクトの方針や既存コードとの整合性に合わせて決めるとよいでしょう。

クラスプロパティの型宣言

PHP 7.4以降では、クラスのプロパティにも型を付けられます。

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

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

この場合、$id には整数、$name には文字列、$email には文字列または null を代入できます。

$user = new User(1, 'Taro', 'taro@example.com');

型に合わない値を代入しようとするとエラーになります。

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

初期化されていないtyped propertyに注意

型付きプロパティでよくあるエラーが、未初期化のプロパティを読み取ろうとするケースです。

class User
{
    public string $name;
}

$user = new User();

echo $user->name; // NG

この場合、$name はまだ初期化されていません。

ここで注意したいのは、?string にしても、自動的に null が入るわけではないという点です。

class User
{
    public ?string $name;
}

$user = new User();

echo $user->name; // NG

?string は、「文字列またはnullを代入できる」という意味です。

「初期値がnullになる」という意味ではありません。

最初から null にしたい場合は、明示的に初期値を入れます。

class User
{
    public ?string $name = null;
}

または、コンストラクタで必ず代入します。

class User
{
    public string $name;

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

コンストラクタプロパティプロモーション

PHP 8.0以降では、コンストラクタの引数とプロパティ定義をまとめて書けます。

これを、コンストラクタプロパティプロモーションといいます。

class User
{
    public function __construct(
        public int $id,
        public string $name,
        public ?string $email = null
    ) {}
}

これは、次のようなコードを短く書いたものです。

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

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

DTOや値オブジェクトを書くときに便利です。

class ProductData
{
    public function __construct(
        public string $name,
        public int $price,
        public bool $isPublished
    ) {}
}

データをまとめて受け渡しするクラスでは、コードをかなり簡潔にできます。

readonlyプロパティ

PHP 8.1以降では、readonly プロパティも使えます。

class UserId
{
    public function __construct(
        public readonly int $value
    ) {}
}

readonly にすると、初期化後に値を変更できません。

$userId = new UserId(10);

echo $userId->value; // 10

$userId->value = 20; // NG

ID、作成日時、設定値など、後から変更されたくない値に使うと便利です。

class ArticleId
{
    public function __construct(
        public readonly int $value
    ) {}
}

値オブジェクトを作るときにもよく使われます。

クラス定数の型宣言

PHP 8.3以降では、クラス定数にも型を付けられます。

class Role
{
    public const string ADMIN = 'admin';
    public const string EDITOR = 'editor';
    public const int DEFAULT_LEVEL = 1;
}

従来は次のように型なしで書いていました。

class Role
{
    public const ADMIN = 'admin';
}

型を付けることで、クラス定数の意図が明確になります。

class Config
{
    public const string SITE_NAME = 'My Site';
    public const int ITEMS_PER_PAGE = 10;
    public const bool DEBUG_MODE = false;
}

ただし、クラス定数の型宣言では、何でも指定できるわけではありません。

voidnever は戻り値専用の型なので使えません。

また、callable もクラス定数の型としては使えません。

class Example
{
    public const void VALUE = null;      // NG
    public const never ERROR = null;     // NG
    public const callable CALLBACK = ''; // NG
}

PHP 8.3未満の環境では、クラス定数の型宣言は使えないため注意しましょう。

strict_typesについて

PHPの型宣言を理解するうえで、strict_types は非常に重要です。

PHPでは、strict_types を有効にしていない場合、スカラー型で暗黙の型変換が行われることがあります。

スカラー型とは、主に以下のような型です。

int
float
string
bool

たとえば、次のコードを見てください。

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

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

strict_types を有効にしていない場合、文字列 '1''2' が整数として扱われることがあります。

より厳密にしたい場合は、PHPファイルの先頭に次のように書きます。

<?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を書く場所

declare(strict_types=1); は、基本的にPHPファイルの先頭に書きます。

<?php

declare(strict_types=1);

// ここからコードを書く

実務では、新規開発や自社管理のコードであれば、各PHPファイルに入れると型の不一致に気づきやすくなります。

ただし、既存のWordPressテーマや古いプラグインを改修する場合は注意が必要です。

既存コードが暗黙の型変換に依存している場合、strict_types=1 を入れることでエラーが出る可能性があります。

そのため、既存プロジェクトでは、導入前に動作確認を行うのが安全です。

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

strict_types で特に重要なのは、関数が定義されているファイルではなく、関数を呼び出す側のファイルに影響するという点です。

たとえば、次のようなファイルがあるとします。

// functions.php
<?php

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

そして、別のファイルから呼び出します。

// index.php
<?php

declare(strict_types=1);

require_once 'functions.php';

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

この場合、add() が定義されている functions.phpstrict_types=1 があるかどうかではなく、呼び出し元の index.phpstrict_types=1 があるため、'1''2'int として受け入れられません。

逆に、呼び出し元に strict_types=1 がなければ、スカラー型の暗黙変換が行われることがあります。

strict_typesが主に関係する型

strict_types は、主に次のようなスカラー型の暗黙変換に関係します。

int
float
string
bool

たとえば、string を期待する関数に int を渡した場合です。

function showString(string $value): void
{
    var_dump($value);
}

showString(123);

strict_types が無効であれば、123"123" に変換されることがあります。

一方、クラス型や配列型は、そもそも文字列からオブジェクトへ自動変換されるようなものではありません。

function handleUser(User $user): void
{
}

handleUser('abc'); // strict_typesに関係なくNG

つまり、strict_types はすべての型チェックの挙動を変えるというより、主にスカラー型の暗黙変換を抑制するものだと理解するとよいです。

型宣言でよくあるエラー

nullを許可していないのにnullを渡す

function greet(string $name): void
{
    echo $name;
}

greet(null); // NG

null を許可したい場合は、nullable型にします。

function greet(?string $name): void
{
    echo $name ?? 'ゲスト';
}

戻り値の型と実際のreturnが違う

function getPrice(): int
{
    return '1000';
}

このようなコードは、strict_types の設定や状況によって問題になります。

安全にするなら、明示的に整数を返します。

function getPrice(): int
{
    return 1000;
}

また、外部入力から取得した値を返す場合は、必要に応じてキャストやバリデーションを行います。

function getPriceFromRequest(array $request): int
{
    return (int) $request['price'];
}

voidなのに値を返す

function save(): void
{
    return true; // NG
}

void の場合は、値を返しません。

function save(): void
{
    // 保存処理
    return;
}

または、return; 自体を書かなくても問題ありません。

function save(): void
{
    // 保存処理
}

型付きプロパティを初期化していない

class User
{
    public string $name;
}

$user = new User();

echo $user->name; // NG

型付きプロパティは、読み取る前に必ず値を入れる必要があります。

初期値を入れる方法です。

class User
{
    public string $name = '';
}

nullableにして初期値を null にする方法です。

class User
{
    public ?string $name = null;
}

コンストラクタで代入する方法です。

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

arrayの中身までは保証されない

function renderNames(array $names): string
{
    return implode(', ', $names);
}

この場合、array であることは保証されます。

しかし、配列の中身がすべて文字列であることまでは、PHPの型宣言だけでは保証されません。

renderNames(['Taro', 'Jiro']); // 想定通り
renderNames([1, true, null]);  // arrayとしては渡せる

配列の中身の型まで伝えたい場合は、PHPDocを使います。

/**
 * @param string[] $names
 */
function renderNames(array $names): string
{
    return implode(', ', $names);
}

より厳密にしたい場合は、配列ではなく専用クラスを使うことも検討しましょう。

WordPress開発での型宣言の使い方

WordPress開発でも、型宣言は役立ちます。

ただし、WordPressでは対応PHPバージョンや既存コードとの互換性に注意が必要です。

広く配布するテーマやプラグインでは、利用者のサーバー環境が古い可能性があります。

一方、自社運用のテーマや、対応PHPバージョンをPHP 8系に限定できる案件では、型宣言を積極的に使うメリットがあります。

たとえば、ショートコードの処理です。

function render_campaign_shortcode(array $atts): string
{
    $atts = shortcode_atts([
        'title' => 'キャンペーン',
        'url' => '',
    ], $atts);

    return sprintf(
        '<a href="%s">%s</a>',
        esc_url($atts['url']),
        esc_html($atts['title'])
    );
}

投稿IDを受け取る関数です。

function get_post_thumbnail_alt(int $postId): string
{
    $thumbnailId = get_post_thumbnail_id($postId);

    if (!$thumbnailId) {
        return '';
    }

    return (string) get_post_meta($thumbnailId, '_wp_attachment_image_alt', true);
}

WordPress関数は、false0、空文字、null などを返すものも多いため、戻り値の型設計には注意が必要です。

たとえば、WordPressの既存関数に合わせて false を許容することもあります。

function getArticle(int $id): Article|false
{
    // 見つからない場合はfalse
}

一方、自分で設計する関数なら、?Article の方が分かりやすい場合もあります。

function findArticle(int $id): ?Article
{
    // 見つからない場合はnull
}

PHPバージョン別に使える型宣言

PHPの型宣言は、バージョンによって使える機能が異なります。

PHPバージョン主な型宣言機能
PHP 7.0スカラー型宣言、戻り値の型宣言
PHP 7.1nullable型、void
PHP 7.4クラスプロパティの型宣言
PHP 8.0Union型、mixed、コンストラクタプロパティプロモーション
PHP 8.1never、Intersection型、readonly プロパティ
PHP 8.2DNF型、true 型、false 型や null 型の扱い拡張
PHP 8.3クラス定数の型宣言

実務では、使用しているPHPバージョンに合わせて使う機能を選ぶ必要があります。

たとえば、PHP 7.4ではプロパティ型は使えますが、Union型は使えません。

class User
{
    public string $name; // PHP 7.4以降
}
function formatId(int|string $id): string // PHP 8.0以降
{
    return (string) $id;
}

PHP 8.3未満では、クラス定数の型宣言は使えません。

class Status
{
    public const string ACTIVE = 'active'; // PHP 8.3以降
}

特にWordPress案件では、サーバーのPHPバージョンを確認してから型宣言を使うことが大切です。

実務でのおすすめ方針

PHPの型宣言は、以下のような方針で使うとバランスがよいです。

関数の引数と戻り値にはできるだけ型を付ける

function createSlug(string $title): string
{
    return strtolower(str_replace(' ', '-', trim($title)));
}

引数と戻り値に型を付けるだけでも、コードの読みやすさは大きく変わります。

戻り値がない関数にはvoidを付ける

function sendLog(string $message): void
{
    error_log($message);
}

void を付けることで、「この関数は値を返さない」と明確になります。

nullがあり得る場合はnullable型を使う

function findArticle(int $id): ?Article
{
    // ...
}

?Article と書けば、「Articleが返ることもあるし、見つからなければnullになることもある」と分かります。

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

/**
 * @param array<int, string> $tags
 */
function renderTags(array $tags): string
{
    return implode(', ', $tags);
}

PHP本体では配列の中身の型まで指定できないため、PHPDocを併用すると分かりやすくなります。

配列を多用しすぎず、必要に応じてクラスを使う

配列だけでデータを渡すと、中身の構造が分かりにくくなります。

function saveArticle(array $data): void
{
    // $data['title'] があるか分かりにくい
}

専用のクラスを使うと、データの形が明確になります。

class ArticleData
{
    public function __construct(
        public string $title,
        public string $body,
        public bool $isPublished
    ) {}
}

function saveArticle(ArticleData $data): void
{
    // ...
}

実務では、簡単な処理なら配列でも十分ですが、データ構造が複雑になる場合はクラス化を検討するとよいです。

初心者がまず覚えるべき型宣言

最初からすべての型を覚える必要はありません。

まずは、以下を押さえましょう。

string
int
float
bool
array
?string
?int
void
クラス名

次に、PHP 8以降を使うなら以下も覚えるとよいです。

int|string
User|null
mixed
readonly

さらに設計に慣れてきたら、以下を学ぶと理解が深まります。

interface
iterable
callable
never
Intersection型
DNF型

日常的なWeb制作やWordPress開発では、まずは stringintboolarray?型void、クラス名の型指定を使えるようになれば十分です。

まとめ

PHPは動的型付け言語ですが、現在のPHPでは型宣言を使うことで、安全で読みやすいコードを書けます。

ただし、PHPでは通常のローカル変数に次のような型宣言はできません。

string $name = 'Taro'; // NG
int $age = 20;         // NG

型宣言できる主な場所は、関数の引数、戻り値、クラスプロパティ、クラス定数です。

function sample(string $name): void
{
    echo $name;
}

このように書くと、引数の型と戻り値の有無が明確になります。

function findUser(int $id): ?User
{
    return null;
}

このように書くと、「Userが返る可能性もあるし、見つからなければnullが返る」という意味が明確になります。

また、strict_types を有効にすると、スカラー型の暗黙変換を抑制できます。

<?php

declare(strict_types=1);

新規開発では、型宣言と strict_types=1 を組み合わせることで、型の不一致に早く気づきやすくなります。

一方で、既存のWordPressテーマや古いコードでは、暗黙の型変換に依存している場合もあるため、導入時には注意が必要です。

PHPの型宣言は、コードを難しくするためのものではありません。

むしろ、関数やクラスの意図を分かりやすくし、バグを減らし、保守しやすくするための仕組みです。

まずは、関数の引数と戻り値に型を付けるところから始めるとよいでしょう。

以上、PHPの変数の型宣言についてでした。

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

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