PHPのクラス変数とメンバ変数について

採用はこちら

PHPでクラスを学ぶときに、「クラス変数」や「メンバ変数」という言葉が出てくることがあります。

ただし、PHPの公式な表現に近づけるなら、これらは次のように整理すると分かりやすいです。

一般的な呼び方PHPでの正確な呼び方値の持ち方アクセス方法
メンバ変数プロパティ / インスタンスプロパティオブジェクトごとに持つ$this->name / $user->name
クラス変数staticプロパティ / 静的プロパティクラスに紐づいて共有されるself::$count / User::$count

PHPでは、クラスの中に定義する変数を一般的にプロパティと呼びます。

そのため、厳密には「メンバ変数」というよりも「プロパティ」と呼ぶ方が正確です。

また、「クラス変数」という言葉もPHPの公式用語としてはやや曖昧です。

PHPでは、クラス自体に紐づく変数はstaticプロパティと呼ぶのが一般的です。

目次

メンバ変数とは

PHPでは「プロパティ」と呼ぶのが正確

メンバ変数とは、クラスの中に定義される変数のことです。

PHPでは、次のようにクラス内に変数を定義します。

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

この $name$age が、いわゆるメンバ変数です。

ただし、PHPではこれらをプロパティと呼びます。

$user = new User();
$user->name = 'Taro';
$user->age = 25;

echo $user->name; // Taro

このように、オブジェクトを作成してから、そのオブジェクトが持つデータとして扱います。

メンバ変数はオブジェクトごとに値を持つ

通常のプロパティは、オブジェクトごとに別々の値を持ちます。

class User
{
    public string $name;
}

$user1 = new User();
$user1->name = 'Taro';

$user2 = new User();
$user2->name = 'Hanako';

echo $user1->name; // Taro
echo $user2->name; // Hanako

$user1$user2 は、どちらも User クラスから作られたオブジェクトです。

しかし、それぞれ別のオブジェクトなので、$name の値も別々に管理されます。

イメージとしては、次のようになります。

Userクラス
 ├─ $user1 → name = Taro
 └─ $user2 → name = Hanako

つまり、通常のプロパティは「そのオブジェクトが持っている情報」を表します。

ユーザー名、年齢、メールアドレス、商品名、価格、記事タイトルなど、個別のオブジェクトごとに変わる値は、通常のプロパティとして定義します。

クラス変数とは

PHPでは「staticプロパティ」と呼ぶ

PHPで「クラス変数」と呼ばれるものは、多くの場合、staticプロパティのことを指します。

staticプロパティは、オブジェクトではなく、クラス自体に紐づく変数です。

class User
{
    public static int $count = 0;
}

この $count は、User オブジェクトごとに存在するのではなく、User クラスに1つだけ存在する値です。

外部からアクセスするときは、次のように書きます。

echo User::$count;

クラス内部からアクセスする場合は、次のように書きます。

self::$count

通常のプロパティでは -> を使いますが、staticプロパティでは :: を使います。

staticプロパティはインスタンス間で共有される

staticプロパティの大きな特徴は、すべてのインスタンスで値が共有されることです。

class User
{
    public static int $count = 0;

    public function __construct()
    {
        self::$count++;
    }
}

$user1 = new User();
$user2 = new User();
$user3 = new User();

echo User::$count; // 3

この例では、User オブジェクトを3つ作成しています。

コンストラクタが呼ばれるたびに self::$count++ が実行されるため、最終的に User::$count3 になります。

ここで重要なのは、$count$user1$user2$user3 それぞれに存在しているわけではないという点です。

Userクラス
 └─ static $count = 3

$user1
$user2
$user3

staticプロパティは、クラス全体で共有したい値を扱うときに使います。

メンバ変数とクラス変数の違い

値の持ち方が違う

メンバ変数とクラス変数の一番大きな違いは、値をどこに持つかです。

通常のメンバ変数、つまりインスタンスプロパティは、オブジェクトごとに値を持ちます。

一方、クラス変数に相当するstaticプロパティは、クラスに紐づいて1つの値を共有します。

比較項目メンバ変数 / インスタンスプロパティクラス変数 / staticプロパティ
PHPでの呼び方プロパティstaticプロパティ
宣言方法public string $name;public static int $count;
所属先オブジェクトクラス
値の持ち方インスタンスごとに別々クラス全体で共有
クラス内でのアクセス$this->nameself::$count
クラス外からのアクセス$user->nameUser::$count
主な用途名前、年齢、価格など個別データ件数、共通設定、共有状態など

アクセス方法が違う

通常のプロパティにアクセスするときは、-> を使います。

$user->name;

クラス内部からアクセスするときは、$this-> を使います。

$this->name;

一方、staticプロパティにアクセスするときは、:: を使います。

User::$count;

クラス内部からアクセスするときは、self:: を使うことが多いです。

self::$count;

この違いは非常に重要です。

通常のプロパティに :: でアクセスしたり、staticプロパティに -> でアクセスしたりすると、エラーや意図しない挙動の原因になります。

$this->self::$ の違い

$this-> は現在のオブジェクトを指す

$this は、現在のオブジェクト自身を表します。

class User
{
    public string $name;

    public function sayHello(): void
    {
        echo 'こんにちは、' . $this->name . 'さん';
    }
}

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

出力結果は次のとおりです。

こんにちは、Taroさん

この場合の $this->name は、「現在の User オブジェクトが持っている $name」という意味です。

通常のプロパティは、オブジェクトごとに値を持ちます。

そのため、クラス内からアクセスするときは $this-> を使います。

self::$ は現在のクラスを指す

self は、現在のクラス自身を表します。

class User
{
    public static int $count = 0;

    public static function showCount(): void
    {
        echo self::$count;
    }
}

User::$count = 5;
User::showCount(); // 5

この場合の self::$count は、「User クラスが持っているstaticプロパティ $count」という意味です。

staticプロパティはオブジェクトに紐づいていないため、$this->count のようにはアクセスしません。

実用例で見るメンバ変数とクラス変数

ユーザー名はメンバ変数、ユーザー数はクラス変数

次の例では、通常のプロパティとstaticプロパティを両方使っています。

class User
{
    private string $name;

    private static int $count = 0;

    public function __construct(string $name)
    {
        $this->name = $name;
        self::$count++;
    }

    public function introduce(): void
    {
        echo "私は{$this->name}です。" . PHP_EOL;
    }

    public static function showCount(): void
    {
        echo "ユーザー数は" . self::$count . "人です。" . PHP_EOL;
    }
}

$user1 = new User('Taro');
$user2 = new User('Hanako');

$user1->introduce(); // 私はTaroです。
$user2->introduce(); // 私はHanakoです。

User::showCount(); // ユーザー数は2人です。

このコードでは、$name は通常のプロパティです。

private string $name;

名前はユーザーごとに異なるため、オブジェクトごとに持たせる必要があります。

一方、$count はstaticプロパティです。

private static int $count = 0;

ユーザー全体の数を管理したいため、クラス全体で共有する値として定義しています。

商品クラスで考える例

商品を表すクラスでも、同じ考え方ができます。

class Product
{
    private string $name;
    private int $price;

    private static int $totalCount = 0;

    public function __construct(string $name, int $price)
    {
        $this->name = $name;
        $this->price = $price;
        self::$totalCount++;
    }

    public function getInfo(): string
    {
        return "{$this->name}:{$this->price}円";
    }

    public static function getTotalCount(): int
    {
        return self::$totalCount;
    }
}

$product1 = new Product('りんご', 100);
$product2 = new Product('みかん', 150);

echo $product1->getInfo(); // りんご:100円
echo $product2->getInfo(); // みかん:150円

echo Product::getTotalCount(); // 2

この例では、商品名と価格は商品ごとに違います。

そのため、$name$price は通常のプロパティにします。

一方、作成された商品の合計数は、商品オブジェクトごとではなく、Product クラス全体で管理したい値です。

そのため、$totalCount はstaticプロパティにしています。

staticを付けるかどうかの判断基準

オブジェクトごとに違う値なら通常のプロパティにする

次のような値は、通常のプロパティとして定義するのが自然です。

ユーザー名
メールアドレス
年齢
商品名
価格
記事タイトル
記事本文
公開状態
注文番号

これらは、オブジェクトごとに値が異なるからです。

class Article
{
    private string $title;
    private string $body;
    private bool $published = false;
}

記事タイトルや本文、公開状態は記事ごとに変わります。

そのため、通常のプロパティとして定義します。

クラス全体で共有したい値ならstaticプロパティにする

次のような値は、staticプロパティとして定義することがあります。

インスタンス生成数
クラス共通の設定値
共通カウンタ
全体で共有したい状態

たとえば、インスタンスが何個作られたかを数える場合は、staticプロパティが使えます。

class Counter
{
    private static int $count = 0;

    public static function increment(): void
    {
        self::$count++;
    }

    public static function getCount(): int
    {
        return self::$count;
    }
}

Counter::increment();
Counter::increment();

echo Counter::getCount(); // 2

ただし、staticプロパティはクラス全体で状態を共有するため、使いすぎるとコードの見通しが悪くなる場合があります。

実務では、本当にクラス全体で共有すべき値なのかを考えてから使うことが大切です。

staticメソッドとstaticプロパティ

staticメソッドはインスタンスを作らずに呼び出せる

staticプロパティと一緒に使われることが多いのが、staticメソッドです。

staticメソッドは、オブジェクトを作らずにクラスから直接呼び出せます。

class MathUtil
{
    public static function double(int $number): int
    {
        return $number * 2;
    }
}

echo MathUtil::double(10); // 20

このように、new MathUtil() と書かなくても、MathUtil::double() で呼び出せます。

共通の計算処理や変換処理など、特定のオブジェクトの状態に依存しない処理に使われることがあります。

staticメソッドの中では$thisは使えない

staticメソッドは、特定のオブジェクトに紐づいていません。

そのため、staticメソッドの中では $this を使えません。

class User
{
    private string $name;

    public static function showName(): void
    {
        echo $this->name; // エラー
    }
}

$this は「現在のオブジェクト」を指すものです。

しかし、staticメソッドはクラスに対して呼び出されるため、「現在のオブジェクト」が存在しません。

staticメソッド内でstaticプロパティにアクセスする場合は、次のように書きます。

class User
{
    private static string $defaultName = 'Guest';

    public static function showDefaultName(): void
    {
        echo self::$defaultName;
    }
}

アクセス修飾子の基本

publicprotectedprivateの違い

プロパティには、アクセス範囲を指定できます。

class User
{
    public string $name;
    protected int $age;
    private string $password;
}

主な違いは次のとおりです。

修飾子アクセスできる場所
publicクラスの外部からもアクセスできる
protected自分のクラスと子クラスからアクセスできる
private自分のクラスの中からだけアクセスできる

public にすると外部から自由に値を読み書きできます。

class User
{
    public string $name;
}

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

echo $user->name;

一方、private にするとクラスの外から直接アクセスできません。

class User
{
    private string $name;

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

    public function getName(): string
    {
        return $this->name;
    }
}

$user = new User('Taro');

echo $user->getName(); // Taro

この場合、次のような直接アクセスはできません。

echo $user->name; // エラー

実務ではprivateで値を守ることが多い

実務では、プロパティを何でも public にするのではなく、private にしてメソッド経由で扱うことがよくあります。

理由は、不正な値が入るのを防ぎやすくなるからです。

たとえば、年齢を管理するクラスがあるとします。

class User
{
    public int $age;
}

$user = new User();
$user->age = -100;

public だと、外部から不正な値を自由に代入できてしまいます。

そこで、次のように private にして、値を設定するときにチェックします。

class User
{
    private int $age;

    public function setAge(int $age): void
    {
        if ($age < 0) {
            throw new InvalidArgumentException('年齢は0以上である必要があります。');
        }

        $this->age = $age;
    }

    public function getAge(): int
    {
        return $this->age;
    }
}

このようにすると、年齢にマイナス値が入るのを防げます。

ただし、DTOやLaravelのモデルなど、用途によっては public プロパティを使う設計もあります。

重要なのは、値を自由に変更させてよいかどうかを考えて、適切なアクセス修飾子を選ぶことです。

self::static::の違い

self::は定義されているクラスを指す

self:: は、そのコードが書かれているクラスを指します。

class ParentClass
{
    protected static string $name = 'Parent';

    public static function showSelf(): void
    {
        echo self::$name;
    }
}

この場合、self::$nameParentClass$name を指します。

static::は実際に呼び出されたクラスを指す

static:: は、実際に呼び出されたクラスを指します。

継承が絡むと、self::static:: の違いが分かりやすくなります。

class ParentClass
{
    protected static string $name = 'Parent';

    public static function showSelf(): void
    {
        echo self::$name;
    }

    public static function showStatic(): void
    {
        echo static::$name;
    }
}

class ChildClass extends ParentClass
{
    protected static string $name = 'Child';
}

ChildClass::showSelf();   // Parent
ChildClass::showStatic(); // Child

self::$name は、メソッドが定義されている ParentClass$name を参照します。

一方、static::$name は、実際に呼び出された ChildClass$name を参照します。

この仕組みを遅延静的束縛と呼びます。

初心者のうちは、まず self:: を理解できれば問題ありません。

継承を使った設計をするようになったら、static:: も覚えるとよいです。

型付きプロパティの基本

PHP 7.4以降ではプロパティに型を書ける

PHP 7.4以降では、プロパティに型を指定できます。

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

型を指定すると、意図しない型の値が入るのを防ぎやすくなります。

$user = new User();
$user->age = 'abc'; // TypeErrorになる

実務では、型付きプロパティを使うことが一般的です。

コードの意図が分かりやすくなり、バグも見つけやすくなります。

初期化していない型付きプロパティには注意

型付きプロパティに初期値を設定せず、コンストラクタでも値を入れない場合、そのプロパティは未初期化状態になります。

class User
{
    public string $name;
}

$user = new User();

echo $user->name; // エラー

このようなエラーを防ぐには、初期値を設定するか、コンストラクタで値を入れます。

class User
{
    private string $name;

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

または、初期値を持たせることもできます。

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

PHP 8のコンストラクタプロパティ昇格

コンストラクタでプロパティを簡潔に定義できる

PHP 8以降では、コンストラクタの引数からプロパティを定義できます。

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

    public function introduce(): string
    {
        return "私は{$this->name}です。{$this->age}歳です。";
    }
}

これは、次のコードとほぼ同じ意味です。

class User
{
    private string $name;
    private int $age;

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

コンストラクタプロパティ昇格を使うと、プロパティ宣言と代入処理を短く書けます。

実務のPHP 8以降のコードでは、よく使われる書き方です。

staticプロパティを使うときの注意点

staticは便利だが使いすぎに注意する

staticプロパティは、クラス全体で値を共有できる便利な仕組みです。

しかし、使いすぎるとコードの見通しが悪くなることがあります。

たとえば、次のようなコードを考えます。

class AppConfig
{
    public static string $mode = 'production';
}

小さなコードでは便利ですが、大きなアプリケーションでは注意が必要です。

staticプロパティで状態を共有すると、次のような問題が起きやすくなります。

どこで値が変更されたか分かりにくい
テストしにくい
依存関係が見えにくい
状態が共有されてバグの原因になりやすい

特に、ログイン中のユーザー、リクエストごとの状態、一時的な計算結果などをstaticで持つと、意図しない副作用が起きることがあります。

staticプロパティは、本当にクラス全体で共有すべき値に限定して使うのが安全です。

固定値ならstaticプロパティよりクラス定数を使う

値を変更しないのであれば、staticプロパティよりもクラス定数を使う方が自然です。

class Tax
{
    public const RATE = 0.1;
}

echo Tax::RATE;

RATE のように変更しない値は、public static float $rate とするより、public const RATE とした方が意図が明確です。

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

種類変更できるか
通常のプロパティ変更できる$user->name
staticプロパティ変更できるUser::$count
クラス定数基本的に変更できないTax::RATE

固定値ならクラス定数、共有して変更する可能性がある値ならstaticプロパティ、という考え方で問題ありません。

よくあるミス

通常のプロパティに::でアクセスしてしまう

通常のプロパティは、クラス名から直接アクセスできません。

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

echo User::$name; // NG

$name はstaticプロパティではないため、この書き方は間違いです。

正しくは、オブジェクトを作ってからアクセスします。

$user = new User();
echo $user->name;

staticプロパティに->でアクセスしてしまう

staticプロパティは、オブジェクトから -> でアクセスするのではなく、:: を使ってアクセスします。

class User
{
    public static int $count = 0;
}

$user = new User();
echo $user->count; // NG

正しくは次のように書きます。

echo User::$count;

または、クラス内部からであれば次のように書きます。

self::$count;

プロパティ名に$を付け忘れる

PHPでは、プロパティを宣言するときにも $ が必要です。

class User
{
    public string name; // NG
}

正しくは、次のように書きます。

class User
{
    public string $name;
}

PHPでは変数名に $ を付けるため、プロパティ宣言でも $ を忘れないようにしましょう。

staticプロパティの$の位置を間違える

staticプロパティにアクセスするときは、次の形で書きます。

ClassName::$property;

間違いやすい例は次のとおりです。

ClassName::property;  // NG
ClassName::$property; // OK

$ はプロパティ名の前に付けます。

実務的なクラス設計の例

記事クラスでプロパティとstaticプロパティを使い分ける

最後に、より実務に近い例を見てみます。

class Article
{
    private string $title;
    private string $body;
    private bool $published = false;

    private static int $count = 0;

    public function __construct(string $title, string $body)
    {
        $this->title = $title;
        $this->body = $body;

        self::$count++;
    }

    public function publish(): void
    {
        $this->published = true;
    }

    public function isPublished(): bool
    {
        return $this->published;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public static function getCount(): int
    {
        return self::$count;
    }
}

使い方は次のとおりです。

$article1 = new Article('PHPの基礎', '本文です');
$article2 = new Article('Laravel入門', '本文です');

$article1->publish();

echo $article1->getTitle(); // PHPの基礎
var_dump($article1->isPublished()); // true

echo Article::getCount(); // 2

この例では、次のプロパティは記事ごとに異なります。

private string $title;
private string $body;
private bool $published = false;

そのため、通常のプロパティとして定義しています。

一方、作成された記事数はクラス全体で管理したい値です。

private static int $count = 0;

そのため、staticプロパティとして定義しています。

まとめ

メンバ変数とクラス変数の違いを理解しよう

PHPでは、クラスの中に定義する変数をプロパティと呼びます。

一般的に「メンバ変数」と呼ばれるものは、PHPでは通常のプロパティ、またはインスタンスプロパティと考えると分かりやすいです。

一方、「クラス変数」と呼ばれるものに近いのは、PHPではstaticプロパティです。

メンバ変数
= 通常のプロパティ
= オブジェクトごとに値を持つ

クラス変数
= staticプロパティ
= クラスに紐づいて値を共有する

通常のプロパティは、オブジェクトごとに異なる値を扱うときに使います。

$this->name;
$user->name;

staticプロパティは、クラス全体で共有したい値を扱うときに使います。

self::$count;
User::$count;

初心者のうちは、まず次の基準で考えるとよいです。

その値はオブジェクトごとに違うか?
→ はい:通常のプロパティにする

その値はクラス全体で共有したいか?
→ はい:staticプロパティにする

その値は変更しない固定値か?
→ はい:クラス定数にする

PHPでは「メンバ変数」「クラス変数」という言葉よりも、プロパティstaticプロパティクラス定数という言葉で整理すると、より正確に理解できます。

以上、PHPのクラス変数とメンバ変数についてでした。

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

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