PHP
16
どのような問題がありますか?

この記事は最終更新日から5年以上が経過しています。

投稿日

更新日

PHPにはローカルな定数がない?

再代入不可なローカル変数は作れないのか、というお話です。
最近オブジェクト定数を知り、オブジェクト定数を使いながらつらつら書いていたのですが、一つのメソッドにしか使わない定数が出てきました。
いったいどうすればいいんだ。

概要

定数をメソッド内のスコープで使用できない。

定数

superglobalsと同様に定数のスコープはグローバルです。 つまり、スコープによらずスクリプトの中ではどこでも定数に アクセスできます。スコープの詳細についてはマニュアルの 変数のスコープ をご覧ください。

定数は手動ではdefine()あるいはconstで定義できます。

define("CONSTANT", "Hello world.");
const CONSTANT = 'Hello World';

定数->構文

定数は、定義することができ、変数のスコープ規則に関係なく、あら ゆる場所からアクセス可能です。

オブジェクト定数

オブジェクト定数はクラス内でconstを使い宣言できます。
定数constはPHP5.3から使えるようになり、初出はオブジェクト用のようです。

値が変更できない定数をクラス内に定義することができます。

class MyClass
{
    const CONSTANT = 'constant value';
    function showConstant() {
        echo  self::CONSTANT . "\n";
    }
}

個人的にはオブジェクト定数は馴染み薄く、クラス定数といった方がしっくりきます。

オブジェクト定数定数とそんなに違いがない

記事を書いている途中まで、オブジェクト定数はクラス内でのみ使えると勘違いしていました。繰り返しますが、勘違いです。
オブジェクト定数のページでもクラス宣言後に

class MyClass
{
    const CONSTANT = 'constant value';
    function showConstant() {
        echo  self::CONSTANT . "\n";
    }
}
echo MyClass::CONSTANT . "\n";

とグローバルから(インスタンスも必要なく!)オブジェクト定数を呼び出せます。

詳しくはPHPのオブジェクト指向の基礎編。クラス定数について。がわかりやすいです。

見たとおり、クラス定数はクラスのインスタンスを生成せずに、直接参照できます。理屈的には静的メンバと同様です。ていうか、クラス定数も静的メンバです。
静的フィールドとの違いは値の変更が出来るか出来ないかだけのものです。

で、必ずpublicということは、結局クラス定数も通常の定数と同じくグローバルという事になります。

じゃあどう使い分けるのか、の解説もあります。

final修飾子、ないの?

たしかJavaではfinalで変更不可にできたと思います(うろ覚え)
PHPにはないのでしょうか

あったよ、final修飾子が!

finalキーワード

PHP 5 ではキーワード final が導入され、 final を前に付けて定義されたメソッドは子クラスから上書きできません。 クラス自体がfinalと定義された場合には、このクラスを拡張することはできません。

変数は?

注意: プロパティを final として宣言することはできません。 final として宣言できるのはクラスとメソッドのみです。

プロパティは(publicになりますが)const 使えばいいんですが、変数は?

どうすればいいの?

現在の知識ではベストな案がない。
代替、というか普通の使い方が3点。

1 メソッドの中で定数を定義する

メリット

  • 値の再代入が無い
  • 定数が再利用される可能性が低そう
    • 定数がメソッド内で宣言しても常にグローバルという知識が無ければ再利用されない
    • 定数がメソッド内で宣言しても常にグローバルという知識があれば、察してそっとしておいてくれる(希望)

デメリット

  • 定数の再利用可能性が全領域
  • 一般的な名前をつけにくい

2 オブジェクト定数を使う

メリット

  • 値の再代入が無い
  • そのクラスで使用する定数とわかりやすい
  • クラス::定数名となるので、一般的な名前をつけやすい

デメリット

  • 定数の再利用可能性が全領域
  • 定数の再利用に躊躇がなさそう
    自分なら「オブジェクト定数にしているならどこでつかってもいいよね?」って思いそう

3 普通の変数を使う

メリット

  • スコープがメソッド内
  • 大文字で宣言すれば定数として扱うというローカル規約が多少は認知されている(と思う)

デメリット

  • 値が再代入される可能性がある
    = 常に値を意識しなければならない
    (多分、変数を見たら値が変化していないかが一瞬でもよぎると思います)
  • 定数であることを周知し難い(変数大文字・コメントなど)

結論 : 普通に変数を使うと思います

結局のところ

  1. 値を変更されたくない
  2. 他の箇所で値を自由に使われたくない

の両立ができないことが問題なのですが、1に関しては
「俯瞰して再代入が無いことがわかるほどメソッドを簡潔に作る」
ことができれば無問題だと思います。

仮にメソッドが自分の手を離れて、誰かが途中で値を変える変更をしたとしても、それはそれで一瞬で目につくようになるはずですし。1
全体に注意を払うよりもメソッド内にのみ注意を払うほうが楽なのは当然です。

でも、値が変わらないことをはっきり表現できないのはちょっとムズムズします2


悩むべき問題じゃない
とか
実はあるよ
とかあると小躍りします

余談 : 定数は不変という幻想をぶち殺す

ありえないだろうと思いながらも「php 定数 削除」で検索したら、ありました。

runkit_constant_remove

runkit_constant_remove — 定義済みの定数を削除する

メソッドの頭で定数宣言、終わりでrunkit_constant_removeとすれば、一応ローカルな定数は実現出来そうです…
が、定数を自由に削除・再定義できる関数を組み込むことの方が混乱を招きそうです…

追記

PHP7.1からオブジェクト定数にアクセス修飾子が追加される予定のようです。
* PHP7.1の新機能 - Qiita


  1. なぜ定数扱いの値を変更するのかに関してはわかりませんが 

  2. そんなことを言い出したら型の不変も指定したくなってJavaとかにしなさいってなるでしょうが 

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
khsk
週一ぐらいでなにか書いていました。いろいろお休み中。

コメント

runkit_constant_removeは名前の通りrunkitがインストールされている前提の関数であり、これがインストールされていなければ動作しません。

またrunkitは「通常あり得ない動作を可能にさせるため」に使われるツールだったりするようです。

0
どのような問題がありますか?
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
新人プログラマ応援 - みんなで新人を育てよう!
~
データに関する記事を書こう!
~
16
どのような問題がありますか?
ユーザー登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる
ユーザー登録ログイン
ストックするカテゴリー