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

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

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

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

Q&A

解決済

2回答

686閲覧

phpの遅延静的束縛、static::の指すものについて

moriman

総合スコア615

PHP

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

0グッド

1クリップ

投稿2019/01/13 18:19

php

1<?php 2class A { 3 private function foo() { 4 echo "success!\n"; 5 } 6 public function test() { 7 $this->foo(); 8 static::foo(); 9 } 10} 11 12class B extends A { 13 /* foo() が B にコピーされるので、メソッドのスコープは A のままとなり、 14 * コールは成功します */ 15} 16 17class C extends A { 18 private function foo() { 19 /* もとのメソッドが置き換えられるので、新しいメソッドのスコープは C となります */ 20 } 21} 22 23$b = new B(); 24$b->test(); 25$c = new C(); 26$c->test(); //fails 27?>``` 28```php 29//結果 30success! 31success! 32success! 33 34 35Fatal error: Call to private method C::foo() from context 'A' in /tmp/test.php on line 9

php.netの遅延静的束縛のページにあるコードなのですが、なぜこの結果なるのかわかりません。

より正確に言うと、遅延静的束縛は直近の "非転送コール" のクラス名を保存します。 静的メソッドの場合、これは明示的に指定されたクラス (通常は :: 演算子の左側に書かれたもの) となります。静的メソッド以外の場合は、そのオブジェクトのクラスとなります。

php.netの解説に上記のようにあるのですが、「そのオブジェクトのクラス」が何を指すのかわかりません。
上記の例でstaticはクラスAを指す、ということでしょうか?

それと
static::foo();
の部分は静的でないメソッドを静的に呼び出していると思うのですが、
http://php.net/manual/ja/language.oop5.static.php
↑の注意書きに

警告
PHP 7 では、static でないメソッドを静的にコールすることが非推奨になりました。 E_DEPRECATED レベルの警告が発生します。 将来的にはサポートされなくなる見込みです。
とあります。

static::foo();
はこの警告に当てはまるコード、という理解で良いですよね?

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

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

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

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

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

guest

回答2

0

php.netの解説に上記のようにあるのですが、「そのオブジェクトのクラス」が何を指すのかわかりません。

上記の例でstaticはクラスAを指す、ということでしょうか?

いえ、継承された後のクラスを指しています。だから、Cのほうではprivate C::fooAから呼び出そうとして失敗しています。

static::foo();

の部分は静的でないメソッドを静的に呼び出していると思うのですが

いえ、static::parent::による呼び出しはインスタンスメソッドを呼ぶこともできるもので、警告は無関係です。

投稿2019/01/13 22:19

編集2019/01/13 22:24
maisumakun

総合スコア145121

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

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

mpyw

2019/01/14 03:11

警告は、クラス名を ""外部から"" 直接指定する場合の(self:: static:: parent:: を使わない)呼び出しに関するものですね A::test() ↑こういう書き方を、外部のスコープから行った場合に発生します。もしこのメソッドが $this->test() でも呼び出せるスコープの場合には警告は発生しません。
moriman

2019/01/14 05:02

御回答頂きましてありがとうございます。 ①$b->test();------> $this->foo(); ②$b->test();------> static::foo(); ③$c->test();------> $this->foo(); ④$c->test();------> static::foo(); とした時に、 ① まず$bのtest()を実行しようとするが、Bクラスにはtest()はオーバーライドされてないので、継承元のAクラスのtest()を実行する。$thisは$bを指すので$bのfoo()を実行しようとするが、$b(Bクラス)でfoo()はオーバーライドされてないので継承元の$aのfoo()が実行される。これは理解できます。 ②のstaticは、頂いた回答によると「継承された後のクラス」つまりBクラスということですよね。 つまりB::foo();ということですが、Bクラスではfoo()はオーバーライドされてないので継承元の$aのfoo()が実行される。ということでしょうか。 ③の$thisは$cを指し、$cのfoo()を実行しようとする。Aクラス内からCクラスのprivateメソッドにアクセスできないのでエラー???? ④ Cクラスにはtest()メソッドはオーバーライドされてないのでAクラスのtest()メソッドを実行する。test()メソッド内のstaticは頂いた回答によるとCクラスを指す。なのでCクラスのfoo()メソッドを実行しようとするが、Cクラスのfooメソッドはprivateなので、Aクラス内からCクラスのprivateメソッドにはアクセスできない。だからエラー発生。 ①は問題ないです。②④については頂いた回答から推測すると上記のような考え方になったのですが、これで正しいなら理解できます。問題ないでしょうか? ③は現時点では上記のような認識なのですが、実行結果は違うのでよくわかりません。$thisは呼び出したインスタンスを指すので今回のサンプルコードでは$cを指すのではないのでしょうか? あと理解力が乏しくて申し訳ないのですが、mpywさんの説明で「もしこのメソッドが $this->test() でも呼び出せるスコープの場合には」の部分がどうしても具体的にイメージしずらいです。上記サンプルコードで説明するとどういう場合にあたりますでしょうか?
mpyw

2019/01/14 07:37

>> あと理解力が乏しくて申し訳ないのですが、mpywさんの説明で「もしこのメソッドが $this->test() でも呼び出せるスコープの場合には」の部分がどうしても具体的にイメージしずらいです。 呼び出す場所が test() メソッドが定義されているクラスのインスタンスのメソッドかどうか、というだけです。($this が書けるのはインスタンスメソッドだけです)
mpyw

2019/01/14 07:38

ちなみにそのスコープの場合には、__callStaticを呼ぼうとしても__callが呼ばれてしまうというトラップもあるので注意してください。 PHPのオブジェクトから、自身のアクセス不能メソッドを静的コンテキストで呼び出せない件(追記あり) - Qiita https://qiita.com/yamadar/items/f15805a28252699e423e 自分自身から__callStaticを呼び出したい - Qiita https://qiita.com/rana_kualu/items/d6e96d073fbb95692afe
guest

0

ベストアンサー

maisumakun さんの回答がほぼ全てだと思うのですが、「まだ回答を求めています」ということなので、もう少しかみ砕いて説明してみます。

php.netの解説に上記のようにあるのですが、「そのオブジェクトのクラス」が何を指すのかわかりません。
上記の例でstaticはクラスAを指す、ということでしょうか?

php

1$c = new C(); 2$c->test(); //fails

この場合、「そのオブジェクト」は $c です。
また、$cnew C() を代入されているので「そのオブジェクトのクラス」は C ということになります。

その上で……。
test メソッドは Aクラスにしか存在していません。
そのため、 $c->test(); を実行した場合、呼び出されるのは A クラスのメソッドです。

testメソッド内の挙動については下記のとおりになります。

php

1 public function test() { 2 /* testメソッド自体はAクラスのもの。 3 * Aクラスの「$this」、つまりAクラスのfooメソッドを呼び出す。 4 * ⇒問題なし(Aクラスのメソッドが、Aクラスのprivateメソッドを呼んでいるので) 5 */ 6 $this->foo(); 7 8 /* 前述のとおり、Cクラスのfooメソッドを呼ぼうとする。 9 * testメソッド自体はAクラスのもの。 10 * ⇒AクラスのtestメソッドからCクラスのprivateメソッドは呼べない 11 * ⇒エラーになる。 12 */ 13 static::foo(); 14 }

警告
PHP 7 では、static でないメソッドを静的にコールすることが非推奨になりました。 E_DEPRECATED レベルの警告が発生します。 将来的にはサポートされなくなる見込みです。

とあります。

static::foo();
はこの警告に当てはまるコード、という理解で良いですよね?

いいえ。

PHPのマニュアル( http://php.net/manual/ja/language.oop5.static.php )を再読していただきたいのですが、上記制限が入るのは「static メソッド」です(同じ「static」というキーワードのため紛らわしいのですが、 static:: とは別物です。詳細は後述します)。

そして、staticメソッドというのは

class Hoge { public static function fuga() { } }

といった「メソッド宣言に"static"と書かれているもの」を指します。

非推奨になった呼び出し方、というのは

php

1class Hoge 2{ 3 public static function fuga() // staticメソッド 4 { 5 } 6 7 public function piyo() // staticではないメソッド 8 { 9 } 10}

というクラスがある時に、クラスオブジェクトを作らずに直接「staticではないメソッド」を呼ぶことです。

php

1$hoge = new Hoge(); 2$hoge->piyo(); // OK 3 4Hoge::piyo(); // 非推奨

今回の fooメソッド は、Aクラス・Cクラスともに private function foo() で宣言されている(=メソッド宣言にstaticと書かれていない)ので、これはstaticメソッドではありません。

static::foo() については、上記マニュアルの一番上に書かれている

ヒント
このページでは、static キーワードを使って静的なメソッドやプロパティを定義する方法を説明します。 static は、 静的な変数の定義 や 静的遅延束縛 にも使えます。これらの場合の static の使い方は、 それぞれのページを参照ください。

の「静的遅延束縛」に該当しています。

↓静的遅延束縛のマニュアル
http://php.net/manual/ja/language.oop5.late-static-bindings.php


以下、回答へのコメントを受けて追記します。

一つめの部分について、ご理解いただけたようで何よりでした。

以下、二つめの部分についての疑問に対する回答……に入る前に

この「静的にコールする」の定義はマニュアルなどで説明されていますでしょうか?もしソースがあれば教えて頂けますでしょうか。

を解消しておきましょう。

まず、

・static メソッド=静的メソッド(staticの日本語訳が"静的"なので)
・非static メソッド=静的ではないメソッド≒動的メソッド

です。

そして、この「静的メソッド」と「動的メソッド」というのは、PHP独自の概念ではなく、オブジェクト指向型言語全般(Java、C、等々)に一般的に存在する概念である、ということはご理解ください。

解説ページ(PHPのページではありませんが、わかりやすくまとまっていたのでご紹介です)
https://yryr.me/programming/csharp/beginner/object-model/object-model-static-dynamic-difference.html

そして、ここで「(メソッドを)静的にコールする(calling methods statically)」と書かれているのは、超意訳すると「静的(メソッドを、静的メソッドを呼び出すために用意された方法で)コールする」ということになるかと思います。

以上の内容を踏まえ、具体的なご質問への回答です。

「静的にコールする」=「クラスオブジェクト(インスタンス)を作らずに直接メソッドを呼ぶ」ということでしょうか?

はい、本質的なところはそのとおりです(上記解説ページ参照のこと)。

そして、通常は

「::(スコープ定義演算子)を用いてメソッドを呼び出すこと」を「静的にコールする」と認識していました。

という認識でも問題ないはずでした(スコープ演算子を用いてメソッドを呼び出す≒クラスオブジェクト(インスタンス)を作らずに直接メソッドを呼ぶために定義された書式がほとんどなので)。

が、「スコープ定義演算子を用いてメソッドを呼び出す([クラス名]::、self::、parent::、static::)」もののうち、「static::」のみ特殊な挙動をします(詳細は後述)。

それで考えると、質問のコードではCのインスタンスを作ってtest()を呼び出している→そのtest()で

static::foo()を呼び出している。

遅延静的束縛によりstaticはCクラスを指す、Cのインスタンスを作っているから、このstatic::foo()は

静的なコールではない、ということでしょうか?

結論が「静的なコールではない」というのは正しいのですが、その理由は、「Cのインスタンスを作っているから」ではありません。

遅延静的束縛のマニュアルに、下記の記載があります。

静的メソッドの場合、これは明示的に指定されたクラス (通常は :: 演算子の左側に書かれたもの) となります。
静的メソッド以外の場合は、そのオブジェクトのクラスとなります。

これを把握した上で、改めて、fooメソッドの宣言部分を見てみましょう。

private function foo()

メソッドの宣言に"static"が付いていません。
つまり、これは「静的メソッド以外」ということになります。

今回は、「静的メソッド以外から、static::を使ってfoo()メソッドが呼ばれた」ので「そのオブジェクトのクラス」のメソッドが動的に呼ばれたのです。

スコープ演算子を使ってメソッドを呼び出した時の動的・静的の違い   ・"static::"     ・呼出し先メソッドが静的でも動的でも使用できる     ・呼出し先メソッドの定義(静的メソッドか動的メソッドか)によって、静的なコールか動的なコールかが変わる   ・"[クラス名]::"、"self::"、"parent::"     ・呼出し先メソッドが静的な場合しか使用できない     ・必ず静的なコールになる(静的メソッドは静的なコールしかできないので)

投稿2019/01/28 05:10

編集2019/02/01 05:40
nak

総合スコア696

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

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

moriman

2019/01/31 17:05

nakさん解答を頂きましてありがとうございます。 まず一つ目の部分については そのため、 $c->test(); を実行した場合、呼び出されるのは A クラスのメソッドです の部分を正確に理解できていませんでした。頂いた解答の考え方で考えてみると納得できました。 次に二つ目の部分について。頂いた解答を読んで考えたのですが、 「静的にコールする」 の定義を私が理解していないのではないか、と思いました。解答を頂くまでの私の認識としては 「::(スコープ定義演算子)を用いてメソッドを呼び出すこと」を「静的にコールする」と認識していました。 「静的にコールする」=「クラスオブジェクト(インスタンス)を作らずに直接メソッドを呼ぶ」ということでしょうか? それで考えると、質問のコードではCのインスタンスを作ってtest()を呼び出している→そのtest()で static::foo()を呼び出している。 遅延静的束縛によりstaticはCクラスを指す、Cのインスタンスを作っているから、このstatic::foo()は 静的なコールではない、ということでしょうか? この「静的にコールする」の定義はマニュアルなどで説明されていますでしょうか?もしソースがあれば教えて頂けますでしょうか。
nak

2019/02/01 05:41

上記のご質問を受けて、回答を追記させていただきました。
moriman

2019/02/04 02:06

ありがとうございました。 もう少しここらへんを調べてみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問