Nucleus(JP)フォーラム

NucleusCMS日本語版ユーザーのためのサポートフォーラムです。疑問が生じたらまずは記事検索をご利用ください。

ログインしていません。

#1 2012-08-23 17:29:47

mekyo
メンバー
登録日: 2005-11-22
投稿: 80

Re: call_user_func_arrayの挙動

call_user_funcでの挙動は変なので
  call_user_func_arrayを使います

※PHP5.3までの仕様
→ 内部的に(C言語)そのままcall_user_funcから関数へ渡る。
→ 関数定義が無視される(渡した値が反映)
   function  a(&v) { }
   function  a(&&v) { }
  の変数定義は無理され、渡した配列のアドレスそのまま代入されます。
※php5.4でこの問題は修正されています

オフライン

#2 2012-08-24 06:52:45

mekyo
メンバー
登録日: 2005-11-22
投稿: 80

Re: call_user_func_arrayの挙動

Index: nucleus/libs/MANAGER.php
===================================================================
--- nucleus/libs/MANAGER.php	(リビジョン 1212)
+++ nucleus/libs/MANAGER.php	(作業コピー)
@@ -425,8 +425,12 @@
                 // load class if needed
                 $this->_loadPlugin($listener);
                 // do notify (if method exists)
-                if (isset($this->plugins[$listener]) && method_exists($this->plugins[$listener], 'event_' . $eventName))
-                    call_user_func(array(&$this->plugins[$listener],'event_' . $eventName), &$data);
+                $event_funcname = 'event_' . $eventName;
+                if (isset($this->plugins[$listener]) && method_exists($this->plugins[$listener], $event_funcname))
+                  {
+                    $this->plugins[$listener]->$event_funcname($data);
+//                  call_user_func_array(array(&$this->plugins[$listener] , $event_funcname), array(&$data) );
+                  }
             }
         }
 

$event_funcname = 'event_' . $eventName;
$this->plugins[$listener]->$event_funcname($data);
に置き換えると、
関数定義通りの動作をしますので
call_user_func_arrayの変な挙動に振り回されなくて済みます。

call_user_func(関数名 , &$data );
call_user_func_array(関数名 , array(&$data) );
は、同じ動作をします。
※「呼び出し時の参照渡し」が廃止されたので
   引数が参照変数の関数を呼び出す場合は
   call_user_func_arrayを使う必要があります。
   call_user_funcは、値渡しの関数です。参照渡しの受け取りはできません。

call_user_func_array(関数名 , array(&$data) );
を実行した場合、
PHP(5.2,5.3) と PHP5.4 とで 挙動が違います
PHP5.4 は、呼び出し先の関数定義通りの動作をします

PHP5.3までは、下記の結果から、
参照アドレスで変数を渡すと
Nucleus v3.64 では、全部参照渡しになっていることがわかります。
PHP5.4では、不具合修正されているみたいで、
きちんと 「値渡し変数」と「参照渡し変数」が関数定義通りの動作をします。


$func($a) : すべて正常に動作
       php 5.2 , 5.3 , 5.4 ;
func_by_val:   -
func_by_ref: modified

call_user_func_array($func , array(&$a))
       php (5.2,5.3)   5.4
func_by_val: modified     -
func_by_ref: modified   modified

call_user_func($func , &$a)
       php (5.2,5.3)
func_by_val: modified  ← 意図しない結果
func_by_ref: modified

-------------------------------------

echo PHP_VERSION."\n";

echo "step1: \$func(\$a)\n";
echo step1();
echo "\n";

echo "step2: call_user_func_array(\$func , array(0=>&\$a))\n";
echo step2();
echo "\n";

  function func_by_val($v)  { $v = 'modified'; return true; }
  function func_by_ref(&$v) { $v = 'modified'; return true; }

  function step1()
  {
     $s = '';
     foreach(array('func_by_val','func_by_ref') as $func)
       { $a = '-'; $func($a); $s .= $func.': '.$a."\n"; }
     return $s;
  }

  function step2()
  {
     $s = '';
      foreach(array('func_by_val','func_by_ref') as $func)
      {
        $a = '-';
        try
        {
           $suscess = call_user_func_array($func , array(0 => &$a) );
        }
        catch (Exception $e)
        { $s .=   "\n".$e->getMessage()."\n"; }
        $s .=  $func.': '. ($suscess? $a : 'failed ')."\n";
      }
     return $s;
  }

--------------------
現時点でのまとめ
●コードをほとんど触らないで済ませる
Nucleus v3.64 の簡易PHP5.4対策としては、
call_user_func_array(array(&$this->plugins[$listener] , $event_funcname), array(&$data) );
を選択する必要があります。
PHP5.4でNucleus v3.64 が正常に動作しない場合、
PHPをダウングレードすることで修正前のcall_user_funcと同じ挙動に戻すことができるからです。

●徹底的にコードを修正する場合は、
$this->plugins[$listener]->$event_funcname($data);
を選択するほうが複数のPHPのバージョン間で関数定義どおりの正しい動作に導くことができます。
(※ call_user_func_arrayには、バグが複数あるため)

call_user_func_array関数の動作の違いは、
・PHPコアレベルの実装上の問題である
・Cソースコードみると この問題は、かなり根が深い
・nucleusの問題ではない
ということで
これで調査を終了します。

オフライン

#3 2012-08-25 21:06:46

Mocchi
メンバー
登録日: 2006-11-19
投稿: 438

Re: call_user_func_arrayの挙動

以下を参照して下さい。
リファレンスの説明 @ php.net

裏付けをすることなく、自分の環境で発生した事象や自分の生半可な知識に基づいた仮説を主張することは、自他ともに利にならないことの方が多いのではないかと思いました。

オフライン

#4 2012-08-27 10:59:11

mekyo
メンバー
登録日: 2005-11-22
投稿: 80

Re: call_user_func_arrayの挙動

Mocchi さんの発言:

以下を参照して下さい。
リファレンスの説明 @ php.net

裏付けをすることなく、自分の環境で発生した事象や自分の生半可な知識に基づいた仮説を主張することは、自他ともに利にならないことの方が多いのではないかと思いました。

あなたは、リファレンスに関して勘違いしていませんか?

廃止されたのは、
関数の個々の引数の変数トップレベルでの問題で
要素ではないことを理解する必要があります。

-------------------------------------------------

PHP5.4から廃止された構文は、
「呼び出し時の参照渡し」 です

・「呼び出し時の引数の参照渡し」は
call_user_func      ('関数名' , &$parameter1 );
call_user_func_array('関数名' , &$param_array );
であり

・「呼び出し時の引数の値渡し」
call_user_func      ('関数名' , $parameter1 );
call_user_func_array('関数名' , array(&$parameter1) );
call_user_func_array('関数名' , $param_array );
は、PHP5.4.xにおいても 有効な構文です。

リファレンスの基本操作
・「リファレンスの代入」
・「リファレンス渡し」
・「リファレンスを返す」

array(子要素)は、
配列型という値であって、リファレンスではありません。
子要素がなんであろうと関係ありません。

※ array()は関数ではなく 配列を初期化するための言語構造です
ということを理解しておく必要があります。
(PHP Manual日本語版のarrayの項目「注意」にも書かれています)

array(&$parameter1)は、「関数」ではなく「配列の初期化式」であり
array(0 => &$parameter1)と等価です。
すなわち、要素0への「リファレンスの代入」であって、
「リファレンス渡し」ではないことも理解する必要があります。
しつこいですが、array()は通常の関数ではありません。
しかしながら、
array( &$parameter1 )という構文が
今後、廃止されない保証はありませんので
気持ちが悪いなら
array( 0 => &$parameter1 )とするといいでしょう。

そして、おさらいする必要があるのは、配列と代入です。
リファレンス演算子 & のついていない子要素への代入は、
子要素の型によっては、データアドレスが新規に作成され、
そこに値が複製されます。代入に使った変数と
異なったデータアドレスを持つ変数になることがあることを知っておく必要があります。

オブジェクトなどの例外を除けば
プログラミング言語ほとんどに共通事項
関数の引数の定義

    「値渡し」:データは、複製され、関数外の渡した変数とは独立。
    「参照渡し」:関数外の渡した変数と同じアドレスを指し、
    変更を加えれば、関数外の変数も変更される。

このことが理解できれば、call_user_func_arrayにおいて
引数を参照で受け取る関数を呼び出す際は、
array($parameter1) ではなく array(&$parameter1) を使う必要があることは理解できると思います。
また、値受け取りの関数を呼び出す際は、(~5.3まで)
array(&$parameter1) ではなく array($parameter1) を使ったほうが安全であることは理解できると思います。
※ php5.2,5.3は、配列をそのまま、呼び出し先の関数に丸投げするため(値渡し、参照渡しの定義を無視しているため)
※ php5.4では、テストコードを実行すればわかりますが、適切に処理されているようです。

-----------

・call_user_func      ('関数名' , &$parameter1 )
call_user_func_array('関数名' , array( &$parameter1 ) )
call_user_func_array('関数名' , array( 0 => &$parameter1 ) )
は、書き方が違うだけで 同一の内容である。

・array()は言語構造であり、関数ではない
廃止された「呼び出し時の参照渡し」 とは関係がない。

call_user_func_arrayの第二引数は、
関数に渡った後は、
関数なのでそのあとのことは、文法構造ではなく
内部処理される問題であることも理解する必要があります

以上が理解できない場合は、
下記の方法をとる方が安全です。

・固定のパラメーターを渡す場合は、
  $func = '関数名';
  $func( param1,... );
  $Class->$func( param1,... );
  の形式を使った方が間違いが無いし、無駄に悩まなくていいです。

オフライン

#5 2012-08-27 11:03:05

mekyo
メンバー
登録日: 2005-11-22
投稿: 80

Re: call_user_func_arrayの挙動

ついでに

PHPの変数の実体は、zval構造体変数へのポインタです。
内部 → zval変数を参照(zval変数へのポインタ)

zval変数は、_zval_struct構造体であり
データ本体
データの型
参照数
参照かどうかのフラグ
で構成されています。

PHPの内部では、
・普通に変数に代入すれば、
zval変数へのポインタ変数を作成します。
新規に、_zval_struct構造体の変数が作られ、そこに値と型が複製され
その構造体変数へのポインタをPHP変数に設定します
  (一部の型は下記の動作になります)
zval変数へのポインタを設定して返します。
・リファレンス演算子を使って代入すれば、
zval変数へのポインタ変数を作成します。
  参照カウンタを+1して、
同じ、zval変数へのポインタを設定して返します。
・unsetしたら参照カウンタを-1して、
他で利用されていなければ、データを消去します。
  ZVAL_NULLを設定します。
という動作だったと思います。

間違っているかもしれませんけど
昔 拡張モジュールを作ったことがあるので確かこのような動作だったと思います

PHP5.3までの
call_user_func、call_user_func_arrayのように
PHPの言語構造と実装がかみ合っていないことが多々ありますので
そのあたりも踏まえていないと、PHPの「リファレンスの操作」への理解は、難しいと思います

オフライン

#6 2016-08-11 10:07:17

ピヨピヨbird
メンバー
登録日: 2015-04-05
投稿: 78

Re: call_user_func_arrayの挙動

このトピックで指摘されていた
notify関数の「call_user_func」を誤使用している問題点
で、
Nucleusのnotify関数内でcall_user_funcが誤使用(変数の複製をリファレンスと誤認)
されたままであったため
php7.0.9(21 Jul 2016)からphpの不具合が修正され、Nucleusが正常な動作ができなくなってしまいました。

2012年にこのトピックで提案されていた修正案2個
・call_user_func_array を使う方法
・可変変数 を使う方法
どちらも php7.0.9で正常な動作を確認しました。

「可変変数」 を使って関数として呼び出す方法
を採用し 2016/8/10 にリポジトリにコミットされました。

関連トピック: PHP7.0.9での不具合報告

オフライン

Board footer