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

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

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

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

Q&A

解決済

4回答

1112閲覧

PHPで代入すると結果がかわる事象について

origa3

総合スコア22

PHP

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

0グッド

2クリップ

投稿2023/10/04 20:32

編集2023/10/04 20:35

質問の経緯

PHP の empty() でわけのわからない仕様を見つけました。
下記を実行してみてください。

実行:https://3v4l.org/ZeZFk

PHP

1<?php 2class Car 3{ 4 protected array $tags; 5 6 public function __get(string $prop): mixed 7 { 8 if (property_exists($this, $prop)) { 9 return $this->{$prop}; 10 } 11 } 12 13 public function __set(string $prop, mixed $val): void 14 { 15 if (property_exists($this, $prop)) { 16 $this->$prop = $val; 17 } 18 } 19} 20 21class CarMapper 22{ 23 public function test(Car $car): void 24 { 25 echo 'そのままだとtrueになる'.PHP_EOL; 26 var_dump(empty($car->tags)); // bool(true) 27 28 echo '代入するとfalseになる'.PHP_EOL; 29 $tags = $car->tags; 30 var_dump(empty($tags)); // bool(false) 31 } 32} 33 34$car = new Car(); 35$car->tags = ['tag1']; 36$carMapper = new CarMapper(); 37$carMapper->test($car);

なんと、「そのままだとtrueになる」のに、「代入するとfalseになる」のです。わけがわかりません…。

調べたこと

一応マニュアルにはこのような記載がございましたが、別に __isset() なんて宣言していませんのでこの記載は関係ないと思いますし。(つまり __isset() が宣言されていなければ empty() は平常運転するだろうと解釈できますし。)

オブジェクトのアクセス不能なプロパティに対して empty() を使用した場合は、もしオーバーロードメソッド __isset() が宣言されていればそれをコールします。
https://www.php.net/manual/ja/function.empty.php

それにもし「> アクセス不能なプロパティに対して empty() を使用」していることが何か関係しているとしても、そもそも最初の var_dump() は「アクセス不能なプロパティを __get() で取得した結果に empty() を使用」していると捉えるべきだと思うのです。

つまりこうではなくて、

PHP

1empty(アクセス不能なプロパティ)

こう捉えるべきだと思うのです。

PHP

1empty(アクセス不能なプロパティを __get() で取得した結果)

なぜならこの outer() は、inner() の結果を利用しているためです。empty() だって中の結果を利用してよ!と思います。なんで中の結果を作る前にアクセスの不能性を判定するのでしょうか。わけがわかりません…。

PHP

1echo outer(inner()); 2function inner(){return 'inner';} 3function outer($str){return 'outer'.$str;}

質問事項

改めまして質問は2つです。

➀いったいどういうロジックで「そのままだとtrueになる」のに、「代入するとfalseになる」という事象になるのでしょうか?
➁そしてどのようにして「$car->tags のような値(配列のプロパティ)が空配列であること」を確認すべきなのでしょうか?

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

※尚、empty() だけでなく isset() も同様の事象でした。

補足

質問の➁について、ひとまず Car に以下のような __isset() を置いて解決できました。

PHP

1 public function __isset(string $prop): bool 2 { 3 return property_exists($this, $prop); 4 }

ならば改めて質問になりますが

➂この __isset() はすべてのクラスで絶対に置いておくべきだと思ったのですが、あって困ることはございますか?

もし「オレは empty() や isset() は絶対に通さないのだ!」というなら不要でしょうけれど、あって困ることがなければ備えて置いておくべきだと思いました。(もちろんこれを置かず、➁への回答としてより良いものあれば引き続き➁も募集したいです。)

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

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

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

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

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

melian

2023/10/05 01:13 編集

※ 削除
yambejp

2023/10/05 09:09

質問に追記されるとどこで回答していいかわからなくなります > __isset() はすべてのクラスで絶対に置いておくべきだと思ったのですが、あって困ることはございますか? オーバーロードを利用するなら__issetで整合性を担保してください
guest

回答4

0

__set()/__get()はアクセス不能または存在しないプロパティへデータを書き込む/読み込むので、protectedまたはprivate宣言されたプロパティは値は参照できてもissetではfalse、emptyではtrueを返します

PHP

1class Car{ 2 public $t1; 3 private $t2; 4 protected $t3; 5 function __construct(){ 6 $this->t1=1; 7 $this->t2=2; 8 $this->t3=3; 9 } 10 public function __get($prop){ 11 return $this->{$prop}; 12 } 13 public function __set($prop,$val){ 14 $this->$prop = $val; 15 } 16} 17$car = new Car(); 18var_dump([isset($car->t1),isset($car->t2),isset($car->t3),isset($car->t4)]); 19// true,false,false,false

普通に評価したいならマジックメソッドではない普通のゲッタで処理することです

PHP

1class Car{ 2 private $t2; 3 function __construct(){ 4 $this->t2=2; 5 } 6 public function __get($prop){ 7 return $this->{$prop}; 8 } 9 public function get($prop){ 10 return $this->{$prop}; 11 } 12} 13$car = new Car(); 14var_dump([$car->get("t2"),$car->t2]); //int(2),int2(2) 15var_dump([empty($car->get("t2")),empty($car->t2)]); // false,true

投稿2023/10/05 00:52

編集2023/10/05 01:07
yambejp

総合スコア117673

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

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

origa3

2023/10/08 21:36

丁寧なサンプルコードをありがとうございます。オーバーロードの場合は __isset() を用意し、普通の場合は専用のゲッタを用意する。という感じですね。
guest

0

PHPマニュアルにもノートが付いていますし、3v4lを少し書き換えて検証してみたのですが、emptyでは__get呼ばないようです。

投稿2023/10/05 00:47

maisumakun

総合スコア146544

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

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

maisumakun

2023/10/05 00:48

> ➁そしてどのようにして「$car->tags のような値(配列のプロパティ)が空配列であること」を確認すべきなのでしょうか? $car->tags == [] // PHPで配列は値型なので、空配列同士を==で比較できる
origa3

2023/10/08 21:25

emptyでは__getを呼ばないとは…。まさかでした。ノートとサンプルコードまでありがとうございます。理解が捗りました。 適切な比較式もありがとうございます。そのようにさせて頂きます。
guest

0

ベストアンサー

empty()は「変数」をパラメータに取るとされています。

$car->tagsは「変数」ではないので、変数をパラメータとして与えた場合に得られる結果が得られないのはしかたがないです。

なぜか実行時エラーになったり警告などが出たりしないのはちょっと納得いきません。ただドキュメントでは「変数が存在しない場合でも警告を発しません」と説明されています。たしかに$car->tagsという「変数」は「存在」しない (なぜなら変数ではないから) ので、一応筋は通っているような……。だめでしょうか。

投稿2023/10/06 08:40

ikedas

総合スコア4443

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

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

origa3

2023/10/08 21:31

なるほど。変数か否かで決まるのだと言われれば納得できます。マニュアルに対する適切な読解だと思いました。 私が最も納得のいかなかった outer() と inner() との違いについて言及されたご回答で、大変助かりました。ありがとうございます。
guest

0

アクセス不能なプロパティに対して empty() を実行しようとした場合には、__get() ではなく __isset() が呼ばれます。

PHP: Overloading - Manual

__isset() is triggered by calling isset() or empty() on inaccessible (protected or private) or non-existing properties.

なので、

Car に以下のような __isset() を置いて〜

は適切な対応と言えます。

投稿2023/10/05 01:13

編集2023/10/05 01:15
melian

総合スコア21118

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

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

origa3

2023/10/08 21:26

なるほど。isset() で良かったのですね。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.31%

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

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

質問する

関連した質問