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

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

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

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

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

Q&A

解決済

1回答

432閲覧

php で trait を使う価値がある状況を知りたい

munekun

総合スコア105

PHP

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

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

1グッド

1クリップ

投稿2025/03/23 03:14

編集2025/03/23 03:18

知りたいこと

php学習中ですが、trait を使う価値がある状況が想像できないので、どなたか教えていただけませんでしょうか。

納得いかないポイント

納得いかないポイントは次の [STEP 3] においてでして・・

[STEP 1] traitlog() を実装する
[STEP 2] class 内で use する
[STEP 3] class 内で $this->log() を実行できるようになる

・・・いやこれじゃ log()class が持っているように誤認するので分かりにくい!という「メソッド迷子状態」になる点が納得いきません。不便ではないでしょうか?

そしてこの点は以下 コード比較のように、そもそも trait でなく class を使えば解消できると考えており、そのために trait を使う価値がある状況が想像できないのです。

コード比較

以下に trait バージョンclass バージョン を比較し、いかに後者が優れており、trait が無用の長物で月夜に提灯で蛇足であるかを示します。(もちろん浅慮な考えでしょうから、アドバイス頂けますと幸いです。)

trait バージョン

先述のように $this->log() のように実行され、使う価値があるとは思えません。これでは // log を UserTest が持っているように誤認する ので、メソッド迷子状態になるだけだと思うのです。

php

1<?php 2 3trait TestLogger { 4 public array $logs = []; 5 6 public function log(string $message): void { 7 $this->logs[] = $message; 8 } 9} 10 11class UserTest { 12 use TestLogger; 13 14 public function run(): void { 15 $this->log("Test started"); // log を UserTest が持っているように誤認する 16 print_r($this->logs); 17 } 18} 19 20// 実行 21(new UserTest())->run();
class バージョン

一方でこちらは $this->log() でなく $this->testLogger->log() のように実行されますので、メソッド迷子にならないと思うのです。

php

1<?php 2 3class TestLogger { 4 public array $logs = []; 5 6 public function log(string $message): void { 7 $this->logs[] = $message; 8 } 9} 10 11class UserTest { 12 public TestLogger $testLogger; 13 14 public function __construct() { 15 $this->testLogger = new TestLogger(); 16 } 17 18 public function run(): void { 19 $this->testLogger->log("Test started"); // log を testLogger が持っているとすぐわかる 20 print_r($this->testLogger->logs); 21 } 22} 23 24// 実行 25(new UserTest())->run();

以上です。
お詳しい方、よろしくお願い致します。

utm.👍を押しています

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

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

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

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

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

utm.

2025/03/23 03:50 編集

前提としてトイレトはまじで全く使わないと思います。 仰るようにメンバ変数としてインスタンス化したり、依存性注入を使用したり、ベースクラスに処理をまとめるなどで代替がききますし、第1トレイトは型を持てないという... また、ご指摘のようにコードが致命的に分かりづらくなる。 例えばプライベートメソッドを定義したいが、そのメソッドを使い回しもしたいみたいな需要があれば生きるかも知れません。そんな時ないでしょうが。 ちなみに、あるプロジェクトでは public getFormatedXXX という感じで使われていました。 特定のメンバ変数に依存していて使いずらいし、全然使い回されてなかったので最初トレイトに実装があると気づくまでどうして動いているのか分かりませんでした。 確かにメソッド名を各クラスでコピペしなくていいメリットはあるかもしれませんけど、全然別の方法で抽象化した方が良いと思います。 ダックタイピングの考えに似てるかもしれないですね。 (ダックタイピングを指示する層がいるのも自分的には受け入れられないですが)
munekun

2025/03/23 07:42

> トイレトはまじで全く使わない > 全然別の方法で抽象化した方が良い utm. さんでも使うケースがないとなると、私なんて考える必要がなさそうです。 > コードが致命的に分かりづらくなる > 最初トレイトに実装があると気づくまでどうして動いているのか分かりませんでした。 ですよね、私もこれが怖いです。 > 例えばプライベートメソッドを定義したいが(略) なるほど、プライベートメソッドでも trait なら使いまわせるのは納得です。いつかなんかで使えそうかも!覚えておきたいと思います。 > ダックタイピング 初耳の言葉でした。そういえば暗にやっていることですけれど、できるだけ明示した方がいいですもんね。 ありがとうございます。 参考になりました。
tezcello

2025/03/23 11:55

> php学習中ですが、trait を使う価値がある状況が想像できない まだそういう場面に巡り合っていないだけって事でしょうね。 再利用したいと思うモノが継承関係にしか存在し得ない(←大方はそうだと思う)なら使う場面は無いでしょう。 > これじゃ log() を class が持っているように誤認する use ~ があるのに誤認するの? それだと、基底クラスにあるのか派生クラスにあるのかも正確に認識出来ていないのでは? > trait バージョン > trait が無用の長物 トレイトを利用するクラスが一つだけではメリットは全くないはずです。 継承関係(縦方向と仮定する)から外れるクラス間(=先に対して横方向)で再利用する為に用意された機能ですから。
utm.

2025/03/23 12:11

>基底クラスにあるのか派生クラスにあるのかも正確に認識出来ていないのでは? 根本的に型としての抽象化の意味が無いのであれば委譲を使うべきに思いますので、そこを叩いても仕方ない気がしますが。 関心の分離などの考えからいって質問者さんの指摘は真っ当だと感じますけれど
tezcello

2025/03/23 16:06

> 質問者さんの指摘は真っ当だと感じます 継承(多重のを含む)元にある諸々はそこ(継承元)にある事が認識出来ているのに、トレイトだと迷子になるってのは理解できないなぁ~って感じたのです。 僕にとっては、extends ~、 implements ~ と、use ~ には基本的に違いが無く引っ張ってくる元が違うだけ(インターフェイスはチョッと毛色が違うけどね)なので、トレイトだけ誤認するってのが... そもそもの発想がトレイトは継承寄りでしょうから、委譲の延長線上での考察は相性が悪いでしょうね。 水平方向への串刺しみたいなものですから、ひとつのクラスだけでは全くメリットが無いので、例示されたモノにはトレイトを使う意味は無いでしょう。
munekun

2025/03/23 22:13

tezcelloさん、ありがとうございます。 > 再利用したいと思うモノが継承関係にしか存在し得ない(←大方はそうだと思う)なら使う場面は無い 質問で挙げている問題点は「メソッド迷子」ですので、その点がボヤけてしまような横方向のコードでなく縦方向のコードを提示しました。 横方向でも「メソッド迷子」は生じますので。 ただしこの問題点は > use ~ があるのに誤認するの? > 継承(多重のを含む)元にある諸々はそこ(継承元)にある事が認識出来ているのに、トレイトだと迷子になるってのは理解できないな のようにお考えの方にとっては問題点にならないわけですね。 きっとお使いのphpStormの補完が優秀なのでしょうか?(例えば `log()` を持っているのは `$this` ではないことが明示されるからメソッド迷子になんてならないとか?) いずれにせよ、おかげ様で「trait の横方向が生むメリットが、メソッド迷子というデメリットを補ってあまりあるようなケースが知りたい」という質問だったのだと明確になった気がします。
munekun

2025/03/23 22:16

melianさん、たくさんのリンクありがとうございます。
utm.

2025/03/24 00:45 編集

tezcelloさん 継承やインタフェースは特定のメソッドがあることを強要しその型であることを明示するので、実際の型がなんであるかに関わらず型として使え(例えば関数の引数の型)ますので、誤った認識かと思います。 もちろん、ダックタイピングが好きだったりでコードエディタの補完やコンパイル時のチェック(※インタプリタでは無い言語)を使わない人には無関係な話です。 また、触れませんでしたが、ドックタイピングを許容するのであればtraitクラスを付け替える事で仕様変更やテストが簡易になるケースはあるかと思いますので、"トレイトを利用するクラスが一つだけならメリットは全くない"ということもない気もします。
tezcello

2025/03/24 12:22

> phpStormの補完が優秀なのでしょうか? はい。とても優秀で、もう PhpStorm の無い世界線には戻れません... > `log()` を持っているのは `$this` ではないことが明示されるからメソッド迷子になんてならない 継承だろうが、トレイトだろうが(型宣言をしたプロパティも)その場面で使われるメソッドがどこに記述されていようが、それを特定してコメントやコード補完もしてくれますし、ジャンプも可能なので迷子にはならないです。 > 実際の型がなんであるかに関わらず型として使え(例えば関数の引数の型)ますので トレイトを導入したクラスとして定義したのですから、「なんであるかに関わらず」とは違うのでは? 辻褄が合わなくなるような事をしてしまったのは設計が間違っているのであって、「トレイトを使うから」ではないと思います。
utm.

2025/03/24 22:30

tezcelloさん いいえ。そういう話ではなく継承とtraitの違いの話をしています
guest

回答1

0

ベストアンサー

色々コメントで書いたのですがベースクラスのメソッドをオーバーライドするという使い方で非常にメリットがあるのをすっかり忘れていました。

実際にLaravelで使用されている例ですが、
例えば以下のようなModelクラスの定義があるとします。
Modelクラスとは基本的にはデータベースのクエリーやコマンドをクエリビルダーと呼ばれるメソッドチェーンで書ける方法です。

以下の例ではSQLのDELETEコマンドとUPDATEコマンドを使います。
また、パターンとしてメソッドチェーン(ビルダーパターン)を使用します。

以下のクラスはtraitのuseをコメントアウトし未使用として説明します。

php

1use Illuminate\Database\Eloquent\SoftDeletes; 2 3class Content extends Model 4{ 5 // use SoftDeletes; 6 7 protected $dates = ['deleted_at']; 8 9 protected $fillable = [ 10 'body' 11 ]; 12}

例えば上記のクラス定義があり以下のメソッドを呼び出すと
sqlとしては
DELETE FROM Content WHERE ID = 1;
のような形になります。
(*各メソッドはBaseクラスで定義されています。)

php

1Route::get('/contents/softdelete', function() { 2 Content::find(1)->delete(); 3});

traitを使ってみると

php

1class Content extends Model 2{ 3 use SoftDeletes; 4 5 protected $dates = ['deleted_at']; 6 7 protected $fillable = [ 8 'body' 9 ]; 10}

以下のメソッドを呼び出すと
sqlとしては
UPDATE Content SET DELETED_AT = Now() WHERE ID = 1;
のような形になります。
(*物理削除から論理削除に内部処理が変更されました)

php

1Route::get('/contents/softdelete', function() { 2 Content::find(1)->delete(); 3});

これはModelのBaseクラスで定義されていたdeleteメソッドがtraitをuseすることでオーバーライドされたことで動作が変わったことを示しています。

インタフェースでしたら、特定の型であるかをifで分岐し、その型なら動作を変えるという関数が必要でしたが、traitを使うことで、
一部の処理を分離することができたというわけです。
traitがclassと別定義なのは、付け加えを容易にするための対策だとは思いますが、モジュール単位で考えるとBaseクラスに依存している可能性があるので、特定のクラスの内部処理の定義を分離していると考えるとわかりやすいかもしれません。

引用:
ソースコードをお借りしました。
https://qiita.com/shinya_aa/items/b7c6c068a90ec7bdcd73

蛇足
個人的に文法上気持ち悪いのは。モジュール単位で考えると特定のクラスに完全に依存しているインタフェースがそのクラスの依存関係を明示的に表せないのと似て、traitが単体で浮いている形になっているところですね。
まあ、モジュールを使用するにしても、使用側でいろいろしたいことがあるので、このような定義にせざるを得ないとは思いますが。

投稿2025/03/24 01:10

utm.

総合スコア673

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

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

munekun

2025/03/24 23:18 編集

たびたびありがとうございます。 なるほど、同じ `delete()` でも `trait` にある論理削除の処理でオーバーライドされて、そしてそういう処理変更が簡単にできるわけですね。Lalavel は未経験ですがとても丁寧なご説明でわかりました。 個人的にはそれでも `delete()` のメソッド迷子になりそうで不安が残りますw 以下のように `trait` の有無で削除方針を切り替えるよりも、 ``` class Content extends Model { __use SoftDeletes; ``` 以下のようにクラスの引数 `$type` に 'soft'|'hard' を指定して削除方針を切り替えることもできますし、メソッド迷子にもならないので使いやすいような気も・・(素人感覚なのかと思いますが) ``` class Content extends Model { __private Deletes $deletes; __public function __construct($type = 'soft') __{ ____$this->deletes = new Deletes($type); __} ``` クラスで `$type` を使うと「コードの行数が増えてしまう」し、「if分岐ができてしまう」という2点が、「メソッド迷子」以上のデメリットだろ。ということでしょうか? また `delete()` 以外の切り替えにおいて `Content::__construct()` の引数が増えて複雑になるだろ。ということでしょうか? そういわれると少し納得感もあります。何度か書いて身体で覚えないといけないかもしれません。この2点に注意してメリットの実感を心がけてみます。 いずれにせよ Lalavel でも使われている代表的なケースということで大変参考になりました。
utm.

2025/03/26 04:12

> 以下のようにクラスの引数 `$type` に 'soft'|'hard' を指定して削除方針を切り替えることもできますし、メソッド迷子にもならないので使いやすいような気も・・ いや、おっしゃる通りだと思います。 コードの利用性からいって自分はインタフェースなどを分岐の条件としてこだわっていましたが、フラグとなるプロパティを持つことでも問題なく動作しそうです。 コードとしてはスッキリならないので、traitを使った方がマシにも思えますがtraitの完璧な使い方が私には分からないので混乱する人は居そうです。 メソッドが迷子になる事に関しては、モジュールだと捉えれば内部実装を知る必要はないので、許容できる範囲であり、 コードをスッキリさせるという名目から、使用するに十分適している状態だと思います。 蛇足 なんとなくの空気感ですが、委譲の複雑さを軽減するために、色々な案があって迷走した結果になると思います。 Kotlinという言語には、委譲のための文法が存在しますが、分かりづらさを定量化できるのであれば、traitとトントンぐらいの分かりづらさはあると思います。 (勝手に空気感を感じ取っているだけなので、厳密に同じ思想から産まれたのかは不明)
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.32%

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

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

質問する

関連した質問