質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

解決済

4回答

4206閲覧

class内のconstは後から定義しても良いのはなぜか?

raccy

総合スコア21733

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

2グッド

5クリップ

投稿2017/04/27 10:39

編集2017/04/27 10:48

PHPのconstについて、

PHP

1<?php 2const Y = "hoge"; 3const X = Y; 4var_dump(X);

これはstring(4) "hoge"が表示されます。では、

PHP

1<?php 2const X = Y; 3const Y = "hoge"; 4var_dump(X);

とすると、Yを定義せずに使っているという警告と共にstring(1) "Y"が表示されます。ではでは、

PHP

1<?php 2class A { 3 const X = A::Y; 4 const Y = "hoge"; 5} 6var_dump(A::X);

とすると、string(4) "hoge"が表示され、定義前のA::Yを見に行けているようです。

どうしてclass外では後方のconstを参照することはできないのに、class内だとできるのでしょうか?そのとき、どのような順番で何を処理しているのでしょうか?

###他の言語からの考察

C++はそもそも前方に宣言がないと使えないので、宣言があることが前提になります。char *のようなポインタだと逆順でも同じ物になりますが、std::stringのようなものでは逆順だと結果が変わります。

C++

1#include <iostream> 2#include <string> 3class A 4{ 5public: 6 static const char *const XC; 7 static const char *const YC; 8 static const std::string XS; 9 static const std::string YS; 10}; 11 12const char *const A::XC = YC; 13const char *const A::YC = "hoge"; 14const std::string A::XS = YS; 15const std::string A::YS = "hoge"; 16 17int main() 18{ 19 std::cout << A::XC << std::endl; // hoge 20 std::cout << A::XS << std::endl; // (空) 21 return 0; 22}

Javaはコンパイル時にエラーになります。

Java

1class A { 2 public static final String X = Y; // エラー: 前方参照が不正です 3 public static final String Y = "hoge"; 4 public static void main(String[] args) { 5 System.out.println(X); 6 } 7}

Rubyは未定義であるとして同じくエラーになります。

Ruby

1class A 2 X = A::Y # uninitialized constant A::Y (NameError) 3 Y = "hoge" 4end 5p A::X

Haskellはオブジェクト指向におけるクラスに相当する物はないですが、通常の定数(そもそも全てが定数ですが)についても、遅延評価なので、順番とかはなんも関係無く動作できます。

Haskell

1x = y 2y = "hoge" 3main = print x -- "hoge"

C++がそういう動作になる理由はちょっと複雑になるので除きますが、JavaやRubyはclass内の定数であっても、定義されていない定数は使えません。定数の定義処理を順番にしていくと考えれば妥当な仕様だと思います。しかし、PHPだと、順番に定義していくと考えることはできません。となると、A::Y部分をHaskellのように遅延評価しているとか、そんな感じなのでしょうか?

###参考にした質問

PHP - 定数の定義より前にその定数を使う関数の定義を書いても大丈夫なのか(73837)|teratail
上の質問については、関数内の式の評価が呼び出し時だから問題ないという物です。では、constの右辺の評価も呼び出し時だから…と考えると、Xの右辺が評価されるときにYの右辺が評価済みというのもおかしな話ですし、class外だとうまくいかない理由も付けられなくなります。


定数式のバグ - Qiitaを読んで、ふと気になりました。なお、この現象は記事に書いているバグと関係はありますが、バグそのもののことではありません。

namimon, ikuwow👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

umyuさんがコンパイルするんだよと言う情報とJunSuzukiJapanさんの推測、及び、PHP のクラス定数のちょっと奇妙な話 - ngの日記を参考にオペコードなどを調べてみました。

const_test.php

PHP

1<?php 2function f0() { 3 echo A::X; 4} 5function g0() { 6 echo Y; 7} 8class A { 9 const X = 'ax'; 10} 11const Y = 'y'; 12function f1() { 13 echo A::X; 14} 15function g1() { 16 echo Y; 17}

と言うコードをオペコードを出してみたところ下記のようになっていました。

L1-18 {main}() const_test.php - 0x10d665540 + 7 ops L2 #0 NOP L5 #1 NOP L8 #2 NOP L11 #3 DECLARE_CONST "Y" "y" L12 #4 NOP L15 #5 NOP L18 #6 RETURN 1 function name: f0 L2-4 f0() const_test.php - 0x110477060 + 3 ops L3 #0 FETCH_CLASS_CONSTANT "A" "X" ~0 L3 #1 ECHO ~0 L4 #2 RETURN null function name: g0 L5-7 g0() const_test.php - 0x103c77120 + 3 ops L6 #0 FETCH_CONSTANT "Y" ~0 L6 #1 ECHO ~0 L7 #2 RETURN null function name: f1 L12-14 f1() const_test.php - 0x10dc720c0 + 2 ops L13 #0 ECHO "ax" L14 #1 RETURN null function name: g1 L15-17 g1()const_test.php - 0x103a772a0 + 3 ops L16 #0 FETCH_CONSTANT "Y" ~0 L16 #1 ECHO ~0 L17 #2 RETURN null

注目すべきはf0f1の違いとf1g1の違いです。f1のみ定数をFETCH_CLASS_CONSTANT等で取りに行かずに固定された(計算済みの)文字列になっています。つまり、

  • Compilationによってオペコードが終わった時点でクラス定数については確定済みにされる。対して、トップレベルの定数はDECLARE_CONSTによって動的に定義される。
  • 読みに行くクラス定数が既に現れている場合は、FETCH_CLASS_CONSTANTを使わずにその定数の値に置き換わる。

といえるかと思います。このことからクラス定数は静的、トップレベル定数は動的と言えるのかも知れません。しかし、今問題になっているのは後から定義の定数を使っている場合です。その場合についてもオペコードを見てみました。

PHP

1<?php 2function f0() { 3 echo A::X; 4} 5function g0() { 6 echo A::Y; 7} 8class A { 9 const X = A::Y; 10 const Y = 'ay'; 11} 12function f1() { 13 echo A::X; 14} 15function g1() { 16 echo A::Y; 17}
L1-18 {main}() const_test2.php - 0x10c8780c0 + 6 ops L2 #0 NOP L5 #1 NOP L8 #2 NOP L12 #3 NOP L15 #4 NOP L18 #5 RETURN 1 function name: f0 L2-4 f0() const_test2.php - 0x103477060 + 3 ops L3 #0 FETCH_CLASS_CONSTANT "A" "X" ~0 L3 #1 ECHO ~0 L4 #2 RETURN null function name: g0 L5-7 g0() const_test2.php - 0x102877120 + 3 ops L6 #0 FETCH_CLASS_CONSTANT "A" "Y" ~0 L6 #1 ECHO ~0 L7 #2 RETURN null function name: f1 L12-14 f1() const_test2.php - 0x107277240 + 3 ops L13 #0 FETCH_CLASS_CONSTANT "A" "X" ~0 L13 #1 ECHO ~0 L14 #2 RETURN null function name: g1 L15-17 g1() const_test2.php - 0x10be72100 + 2 ops L16 #0 ECHO "ay" L17 #1 RETURN null

A::Yは確定した値に置き換わっていますが、A::Xは未定義前と同じくFETCH_CLASS_CONSTANTを使用しています。あとはA::XFETCH_CLASS_CONSTANTを使うように定義されていれば、この動作に説明が付くでしょう。しかし、ここからはオペコードからはわかりません。オペコードにしているコンパイルの処理を見なければならないようです。

ということで、クラスに定数を追加しているところをソースコードから確認しました。

  • zend_declare_class_constant_ex()<zen_API.c >にて、zend_class_entryのconstants_table(HashTableという型のハッシュテーブル)にzend_hash_add_ptr()で追加することでクラスの定義が行われます。
  • zend_compile_class_const_decl()<zend_complie.c>にて、astから名前のastと値のastを取得し、値のastをzend_const_expr_to_zval()に投げた結果のzvalをzend_declare_class_constant_ex()に投げています。
  • zend_const_expr_to_zval()<zend_compile.c>にて、zend_compile_const_expr()の処理があり、クラス定数ならさらにzend_compile_const_expr_class_const()が呼ばれまてastがzvalを取れる形に変換されます。
  • zend_compile_const_expr_class_const()にて、クラス定数がまた未定義の時は、astをクラス名::定数名を見に行くzvalに変換します。

PHPにそこまで詳しくないので間違っているかも知れませんが、上のような動きのようです。ということで、ソースコードを見る限り、JunSuzukiJapanさんの推測が正しいと思われます。ベストアンサーはJunSuzukiJapanさんに上げたいと思います。

投稿2017/05/03 02:46

編集2017/05/03 13:17
raccy

総合スコア21733

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

PHPの内部はよく知りませんが、他の言語などから推察してみます。

<?php const Y = "hoge"; const X = Y; var_dump(X);

例えば、上記のコードの場合、PHPインタプリタは

・定数Yを定義し、その値を"hoge"とする。
・定数Xを定義し、その値を「定数Yの値("hoge")」にする。
・Xの値を表示

という動作をします。

では、問題の

<?php class A { const X = A::Y; const Y = "hoge"; } var_dump(A::X);

を考えてみます。

細かいところを省くと、上記のコードは

・クラスAを定義する。
・A::Xの値を表示

という動作をしています。

「クラスAを定義する」というところを、もう少しくわしく見ていきましょう。

クラスAを定義するとは

1。クラスAを作成(する準備をする)
2。クラスAに定数Xを定義し、その値をA::Yとする。
3。クラスAに定数Yを定義し、その値を"hoge"とする。

てなところでしょう。

問題は2の「クラスAに定数Xを定義し、その値をA::Yとする」ですが、
言語を実装する場合、2種類の方法が考えられます。

・クラス定義時にA::Yを計算してしまう。
・クラス定義時には「A::Yを計算した値である」と定義だけしておき、
はじめて参照された時に計算し、その後の変更はさせない。

PHPは後者の方法で実装されているのだと思われます。

なぜPHPが後者の方法で実装されているかは、実装した人たちに聞くなり、PHPの開発者メーリングリスト(あるかどうかはしらない)などを調べるなりしないとわからないです。

(個人が趣味で作った言語なら「そういう風に実装してみたかったから」などの理由だったりしますが、PHPだとどうでしょうね…)

投稿2017/05/02 05:22

JunSuzukiJapan

総合スコア308

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

raccy

2017/05/02 11:36

A::XはA::Yの値ではなく参照を持っていると言うことでしょうか?参照扱いだったら、後から定義されても問題ないとは言えそうです(普通の変数であれば、`$x = &$y;$y = "hoge";var_dump($x);`って書けますし)。 なにか、言語仕様としてそういう風になっているというドキュメントがあれば一番良いのですが。
guest

0

質問に対する本質的な回答ではないため参考情報として提示します。
ご存知だったら申し訳ないのですー。

PHP 7のRFCAbstract syntax treeに以下の記述がありました。

Implementation
Overview
The process for converting a PHP file into opcodes now consists of three phases:

Lexing: the generation of a token stream from the source code
Parsing: the generation of an abstract syntax tree from the token stream.
Compilation: the generation of op arrays from the abstract syntax tree.

投稿2017/04/28 20:27

umyu

総合スコア5846

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

PHPの内部の処理はC言語(C++ではありません)で動いています。

PHPをC言語に変換した場合、素のconstは当然上から順番に処理されるので未定義のものを使用するとエラーが発生します。

しかしclassの場合はC言語にはclassが存在しませんのでPHPのclassは解析処理を通して構造体(struct)に変換する必要があります。
その際にクラス内の変数やconstは適切に参照され問題なく動作しているのでしょう。

投稿2017/04/28 09:03

編集2017/04/28 09:10
shiroyuki

総合スコア169

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

raccy

2017/04/28 09:19

PHPはC言語に変換して動いているということでしょうか?私自身、PHPにはあまり詳しくなったとはいえ、それなりに知っていたと思ったのですが、C言語に変換しているというのは知らなかったので驚きの新事実です。
raccy

2017/05/01 09:36

PHPのclassがCのstructに変換されているとのことですが、具体的にconstはどのように変換されているのでしょうか?また、たとえstructに変換しても、C言語は事前に宣言されていない変数は使用できないと思うのですが、どうしてclassだと適切に参照されるのでしょうか?
shiroyuki

2017/05/02 06:33 編集

わかりやすくC言語に変換と言ってしまいましたが、PHPの実行ファイルがやっていることはPHPの構文が含まれる文字列を構文解析器が読み取っているだけです。 構文解析器が全てメモリ上で処理している可能性もありますが、もし単純にC言語に書き換えていただけだとしても、構造体は事前に型枠の定義の宣言が必要になります。 構造体の宣言前に構造体の中身を使用することはできませんので、classを構造体という枠組みで扱う時点でメンバが定義されていないということはないのです。 これ以上となるとPHPの仕様レベルの話になりますので、PHPはオープンソースなので実際のコードを見てもらったほうが言葉で説明するよりも確実で間違いないかと思われます。 蛇足ではありますが、PHPに限ったことではありませんがプログラミング言語というのは基本的にその言語を開発した言語が存在します。子の言語は親の言語で可能なことしかできません。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問