self::と$this->の使い分け
解決済
回答 3
投稿
- 評価
- クリップ 14
- VIEW 25K+
外部のパートナー企業から納めて頂いたコードで、疑問があります。
前提
- PHP5
- ZendFramework1
- 普段はCakePHPを使っている :-)
MVCのコントローラにおいて、各メソッドで共通して呼び出すprivate関数の呼び出し方が、self::
で書かれていました。
class TestController extends Zend_Controller_Action {
public function FooAction()
{
//パートナーさんが書かれたコード
$ex = self::commonExample();
}
public function BarAction()
{
//私の普段の書き方
$ex = $this->commonExample();
}
private function commonExample()
{
/* メソッドで概ね共通する処理 */
}
}
commonExampleは、コンボボックスの初期値のセットとかです。本当にメソッドごとに共通した部分をサブルーチンにしているだけです。function init()
を使わないのは、全てのメソッドで呼び出すわけではないからですが、その点は今回の関係性がありません。
self::
は、呼び出された同じクラスの中にあるものを指すという理解をしているのですが、コントローラではstaticで呼び出されるわけでもなく、継承するわけでもないので、私は$this->
で呼び出していました。
発生した疑問
今回においてはどちらでも同じ挙動をしますので、どちらが間違っているわけでもないと思いますが、メソッドを追加するにあたって、同じコードの中で、self::
と$this->
が混在しているのは気持ちが悪く、かといって無理解のままself::
に合わせるのも納得がいきません。
正直、どっちでもいい話だと思いますが、作法としてはどうなのかが気になって、質問させて頂きました。
私は、明確な必要性がない限りは、$this->
でよいと思っていました。コントローラなので継承されることはないのですが、仮にサブルーチンがprivateではなくpublicのfunctionだったとして、継承を考えたとしても、明確な必要性がなく、擬似変数$this->
を避ける理由があるようには認識していません。
staticで呼び出されるような場合には、擬似変数は使えませんが、コントローラがstaticで呼び出されることを考慮する必要はないと思いました。しかしその上で、例えば使い分けするよりサブルーチンの呼び出しはself::
で統一するというのが一般的な作法、もしくは私の知識不足で非常識な疑問を抱いているのであるならば、今後はそのように是正させて頂こうとは思っています。
- サブルーチンの呼び出しは
self::
で行うのが一般的かどうか - 私が認識不足している
self::
を使うべき理由がないかどうか
この2点を論点にご教示頂けますと幸いです。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+8
まずはまとめというか私の見解から
- self::xxxのメソッドは、static宣言されているのですか?されてないなら単純に作った人のミスです。
- 今更質問するということは、クラスが2種類の働き方をする設計を意識したこと無いのでは?だから現在の設計はクラスはインスタンス化して使う前提で設計されているはずなので、$this->で統一してもいいのでは?
- 2種類の働き方をするクラスは人によっては「気持ち悪い。別にクラス2つ作ればいいじゃん」と感じるようです。今までどおり$this->で統一するやり方も十分ありです。
下記に根拠、2種類の働き方をするクラスの紹介をします。
※この2種類の働き方をするクラスって正式名称みたいなものはあるのですかね…?
正直、どっちでもいい話だと思いますが、作法としてはどうなのかが気になって、質問させて頂きました。
前提としてはクラスに紐づくメソッドか、インスタンスに紐づくメソッドかで決まります。
下記のようなDBとそのモデルがあったとして、例を含めて説明します。
DBのテーブル「users」とそのユーザーが管理画面で操作するのに「projects」というテーブルを用意します。
- テーブル「users」にアクセスする「model/user.php」と「class User」
- テーブル「projects」にアクセスする「model/project.php」と「class Project」
「削除済みのユーザー一覧」が欲しいという要望があったので、usersテーブルはdeletedというカラムを持ち、deletedに日付が入っているレコードは論理削除されたという設計にしました。
また、Userモデルの実装でUser::find_deleted_users
というメソッドを用意し、
select * from users where deleted is not null;
というSQLが走り、帰ってきたデータをUsersのインスタンスの配列で返す設計にしました。
削除されたユーザーが作ったプロジェクトも論理削除したいですよね?
Userのインスタンス用のメソッドにprojectsを作成して、Project::find_by_user_id($id)
というメソッドを用意して、そのインスタンスが帰るように設計しました。
// 削除されたユーザーに紐づくプロジェクトの配列を取得する
$target_projects = [];
foreach (User::find_deleted_users() as $user) {
foreach ($user->projects as $project) {
$target_projects[] = $project;
}
}
以上の設計では、UserやProjedctというクラスが2つの役割を持っている事がわかります。
このケースでは$thisとselfは相互不干渉であることがわかります。
クラスの基礎では スタティックではないメソッドのスタティック呼び出し全般が非推奨とあります。
これは質問者さんのような「上記の前提を業務に置き換えた時、どっちが$this->でどっちがselfかもうわかんね!」となった人が多いのでしょう
PHP7.0は良いガイドラインで、PHP5.6以下でも上記の設計に従った方が良いでしょう。
上記の例に当てはめると、下記のようにメソッドを定義する時点で明文化して相互不干渉ということを宣言する必要があります。
class User {
public $user;
function __construct ($user) {
$this->user = $user;
}
function projects() {
return Project::find_by_user_id();
}
static function find_deleted_users() {
$sql = "select * from users where deleted is not null;";
// DBに接続
return $users;
}
}
追記:インスタンス化されたオブジェクトがself::xxx
を使う意味について解説してなかったですね…
この辺は完全に設計者の好みになることをまず前提として書いておきます。
数学的な意味での関数はある変数に依存して決まる値あるいはその対応を表す式の事であった。と説明されています。
クラス定数なんかもPHPでは宣言出来ますが、staticのメソッドはその関数版として扱う事も考えられます。
この辺は関数型プログラミングの話になってきますので端折りますが、
staticメソッドは≒参照透過性のメソッドという宣言なわけで、
単体テストのコードによる自動テストが非常にしやすく、正常な動作を保証しやすい事がメリットとして挙げられます。
とりあえず、出来ればそのself::を使った方と一度話してみてください。
(説明が要領得なかったり、staticのないメソッドをselfで叩いているなら論外ですが)
この考えが気に入ればstaticなメソッドを増やせないかを考えて出来るだけselfで統一していっても良いですし、
別に気に入らなければ今まで通り$this->を使っていっても良いかと思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
意味を理解して使いましょう、としかいえません。
参考:
PHP: クラスの基礎 - Manual
PHPで「self::」と「$this」の違いを理解する。
sto's Blog: php 「$this」 , 「self::」について
余談ですが、この件を調べていて、こういうトピックもあったのでご参考まで。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
つい先日同じ質問を持ってました。
ここの表が理解しやすかったです。
PHPオブジェクト指向入門(前半):: 静的プロパティ / 静的メソッド / クラス定数
合わせて前後を読むと理解が深まるかと。
お作法的な選択もありそうですが、そちらは私では回答できないので、他の方から回答があるとイイのですが。。。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.19%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2017/01/17 14:09
残念ながらそれに相当します。今回でいえば『単純に作った人のミスです』ね。すっきりしました。
ただ私の根本的な認識間違いは、self::xxxが、静的コール「のみ」を意味し、staticのないメソッドで使用するのが誤りであることを理解していなかったことです。
私自身はself::xxxは「感覚的に」静的コールでしか使いませんが、自分の中でこの点が正しく(根拠をもって)理解できておらず、またエラーになっていなかったために、「そんな書き方もあるのか」と驚き(結果としては無かった)、「もしかしてそれが普通なのか?」という誤解(結果としては単なるミス)を抑えられず、質問に至りました。
要は、staticで無い限りはスコープ定義演算子を使って呼び出すことはないということですよね。
> 今更質問するということは、クラスが2種類の働き方をする設計を意識したこと無いのでは?
原理は理解しているつもりでしたが、仰る通り、フレームワークに則るだけで、静的メソッドを使う設計を意識したことはほとんどありません(静的コールはフレームワークでそうするから使い、応用する範囲)。なので、スコープ演算子についても浅い理解だったのだと思います。
感覚的には静的メソッドの意義は分かったように思います。今回の件でいえば
・self::を$this->にするか、functionにstatic宣言を付けるかは『好み』であろうと思いますが、コントローラはインスタンス化を前提としているので、前者でよい(好み)。
・静的メソッドはテストがしやすいので、スコープ演算子を使って静的コールする方法も取り込むのがよい。(もちろんsingletonパターンの使い方としてもですが)
といった理解になりました。
私が知りたかったこと、私が間違って理解していることが、エビデンスを含め、明確にして頂きました。
的を射た回答ありがとうございました。
2017/01/17 15:25
> staticで無い限りはスコープ定義演算子を使って呼び出すことはない
古いバージョンではこれでも良かった書き方ですから、はいだめーということはなさそうですね。
PHPの公式サイトは良くメンテされていますので、
作業担当者さんが古い書き方のまま覚えてしまい、このアナウンスをまだ見てなかっただけかと思います。
古いバージョンのPHPで皆が好き勝手にコーディングしていて分かりづらかったので、公式がstaticのみ::でアクセスしようねと正式にアナウンスしたと予想しています。
今後は仮にPHP5.6以下を使っている現場でもアナウンスを見習ってこの書き方を取り入れていきましょう!
(しっかし、PHPの公式サイトは情報量が多すぎて分かりづらい…)
> クラスが2種類の働き方をする設計
間違って理解しているという程でもないように感じました。
別にぜーんぶインスタンス化してしまう設計もキレイに表現出来ますよね。
対応しているフレームワークも少ないですし、書きにくいですし、インスタンス化して使う人の方が一般的なんじゃないかなぁと思います。