php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Request #34094 COM hook into PHP class requires class var
Submitted: 2005-08-12 11:49 UTC Modified: 2019-10-18 11:46 UTC
From: csaba at alum dot mit dot edu Assigned: cmb (profile)
Status: Assigned Package: COM related
PHP Version: Next Minor Version OS: Windows
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: csaba at alum dot mit dot edu
New email:
PHP Version: OS:

 

 [2005-08-12 11:49 UTC] csaba at alum dot mit dot edu
Description:
------------
This report seems related to http://bugs.php.net/bug.php?id=33386 but as far as I can tell a distinct phenomenon is occurring.  In this report, there is also only external access to the last function, (but in the VB analogue, that is OK here as opposed to there) but the access here is through a var of the class (as opposed to the class itself or a function of the class) and that seems wrong.

Let me summarize the VB analagous situation, the acutal situation, and then the distinction between the report above and this one.  

1.  Summary of the VB situation: In VB, documentation says and testing confirms that if you connect a DOM event handler directly to a class built with VB in an .OCX, then if you have marked one of the functions as default within the class (using Tools / Procedure Attributes / Advanced ... note that this means one event handler per class), that is the one that will be executed when the event occurs (note that this differs from the ScriptControl approach in the prior report where we expect access to all the class functions).  An example connection is made by: IE.Document.getElementById('elemId').onclick=classInstance
Note also that there is no facility for passing an argument to the class.


2.  Summary of actual situation:  With PHP in this report, we have to set a variable in the DOM to the class instance (OK to here) and then to access the class (here comes the bug:), ===> we must name a var in the class <=== (as opposed to the class instance itself or the function we want to access or for that matter (as in the earlier report) any function).  At least we are able to pass arguments.


3.  Comparison with prior report:  In both reports, we are only able to access (from an external COM object) the last function in the class, but in the earlier report that was not expected (in comparison to VB) whereas it is conceivable here.  In the earlier report, to access that last function any function of the class had to be named whereas here (and the reason for this report), a var (and not a function) of the class needs to be named.  Frankly, I would expect that other functions are accessible since some external agent (PHP? COM? javascript?) is discriminating based on what has been named.


Thanks for considering this,
Csaba Gabor from Vienna


Reproduce code:
---------------
The code below (watch for possible line wrap) manages to successfully hook a PHP class to event handlers within the IE DOM (as opposed to event handlers of the IE COM object).  See following section for issues.

<?
function popup ($text, $title="PHP popup",
                $timeout=4, $style=131120) {
  $oWSH = new COM("WScript.Shell");
  $oWSH->Popup($text, $timeout, $title, $style); }
function hookHandler ($wnd, $elemId, $hndlrName) {
  $wnd->ExecScript(
    "document.getElementById('$elemId')." .
       "onclick=function(){window.evtHandler." .
         "dummyVar('$hndlrName')}"); }

class evtHandler {
  function clickOK()    { popup ("OK was clicked"); }
  function clickBtn2()  { popup ("Test click"); }
  function keyPress($window) {
    if ($window->event->keyCode==27)  // ESC
      $window->doneP=true; }
  var $dummyVar;
  function __destruct() { popup ("destruction"); }
  function __construct() {
    if (func_num_args(0)) {
      popup("in event handler __construct: ");
      $argsRest = array_slice(func_get_args(),1);
      call_user_func_array (array(&$this,
                       "".func_get_arg(0)), $argsRest);
    } else popup("initialization __construct");
  }
}

$ie = new COM("InternetExplorer.Application");
$ie->visible = true;
$ie->Navigate2("about:blank");
$body ="<button id=btnOK " .
       "accesskey=O><u>O</u>K</button>&nbsp;" .
       "<button id=btn2 " .
       "accesskey=T><u>T</u>est me</button>&nbsp;" .
       "<button accesskey=c " .
       "onclick='window.doneP=true'>" .
       "<u>C</u>ancel</button>";
$ie->Document->body->innerHTML .= $body;

$window = $ie->Document->parentWindow;
$window->ExecScript ("window.doneP=false");
$window->ExecScript ("window.evtHandler=false");

$window->evtHandler = new evtHandler();
hookHandler ($window, 'btnOK', 'clickOK');
hookHandler ($window, 'btn2', 'clickBtn2');
$window->ExecScript("document.onkeypress=function(){" .
     "window.evtHandler.dummyVar('keyPress',window)}");

try { while (!$window->doneP) {
        com_message_pump(600);
        print "\n" . $ie->hwnd; }
      $ie->Quit(); }
catch (Exception $e) { popup ("IE has gone away"); }
popup("Done with php program");
?>

Expected result:
----------------
I expect to name the (non private) function within the class that I'd like to call for a DOM event handler, and have that function be executed.  If it turns out (because of COM restrictions et al) that an arbitrary function can't be called, then I would expect to have to name exactly the function that will be called and not an arbitrary variable (dummyVar) within the class.


Actual result:
--------------
1.  I need to refer to dummyVar on lines 10, 50
2.  All calls (attempt to) go to the final function defined, even if it's marked private (in this latter case, the call would fail)

I REALLY like being able to pass arguments to PHP event handlers (e.g. pressing the Escape key in this example), thanks for building it in.  I hope that is here to stay.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2005-08-12 20:01 UTC] wez@php.net
Can you clarify your sample script?
I've been staring at it for 5 minutes and can't figure it out.  I understand that you're trying to call into PHP objects from the browser, but can't tell quite what you're doing and what you expect it should be doing instead.


 [2005-08-12 21:55 UTC] csaba at alum dot mit dot edu
I'm sure most of what I'm going to write below is already clear to you, but better to err with too much info.  If it's still lacking, please let me know.  I've only tried this on Win XP Pro, SP 2 and it's to be run with CLI php.  the popup function is so that I get a MsgBox window to the foreground to help with debugging.  The calls to popup in the code below are there just to illustrate execution order.

The first block of code right after the class definition instantiates IE and stuffs the web page.  It is now set up, except for hooking in the event handlers.

The next block of 3 lines sets up variable space within IE ($window is the window object within IE's DOM).  Without it, IE doesn't like foreign apps setting values on its window.varName

The next block sets up the event handlers.  The first line where the class is created is so that IE has a local copy of this PHP object.  (In VB, I would assign an instance of the class that contains the event handler function to the event handler (eg. window.document.getElementById('domElemId').onclick=vbClassInstance  When that event is fired, the previously designated default function within the class (thus I'd need three classes for the three event handlers) would be called).  Trying the same type of assignment that VB uses did not work however.
Now the hooking up happens in the next three lines (the third one (escape key detecter) being an extended version for illustrative purposes of showing what happens with additional arguments).
The first one expands like so:
$window->ExecScript("document.getElementById('btnOK').onclick=function(){window.evtHandler.dummyVar('clickOK')}")
The point of that line is so that the string will execute as javascript local to the browser.  The reason I needed to do that was so I could assign an anonymous function (the function(){...}) to the event handler, and the reason I needed to that was so that window.evtHandler didn't get referenced till an event fired, else it would error or try to execute code.  Thus, I asked IE to do:
document.getElementById('btnOK').onclick=function(){window.evtHandler.dummyVar('clickOK')}
So when the button gets clicked and the .onclick fires, the ... part of function() {...} is executed in the context of the element clicked on (that means that 'this' is set to the button element - but we don't make use of that here)
Thus, when the button is clicked we execute:
window.evtHandler.dummyVar('clickOK')
Now we are at the reason for the bug report.  It looks like .dummyVar is being called like a function (The bug.  Furthermore, if I replaced .dummyVar with .clickOK or any other function from the class I'd get an error).  Instead, the last defined function within the evtHandler's class is called (this is conceivably correct here since VB uses this paradigm (and it's not even distressing since I can pass arguments), but it is an issue in the other bug (33386)), namely __construct.  Furthermore, that 'clickOK' is passed in as an argument (VERY cool.  VB does not let us pass arguments to the default function in this context).  In all the "calls" to dummyVar, my convention is that the first argument passed is the name of the real function within the evtHandler class that I want executed.  That's really what I would much rather replace 'dummyVar' with.  Ie.  I'd rather have window.evtHandler.clickOK() than window.evtHandler.dummyVar('clickOK')


On the receiving end, __construct is called when the class is first created, but with no arguments, so we filter out this first invocation by checking the number of arguments.  Every subsequent call from IE, with the silly .dummyVar style, will have the first argument indicating the real function that we want to handle the event.  The rest of the arguments (by my convention) are destined for the real intended event handler function named by the first argument (thus $argsRest).  Then the next line makes the actual call.  Note that I use "".func_get_arg(0) as the name of the  function to call.  That is because that 'string' (func_get_arg(0)) is coming from javascript and PHP views it as an object so I've got to coerce it or else call_user_func_array is not happy.

Finally, three comments.
(1)  The printing of the $ie->hwnd is simply to let me know that PHP is alive and still in the loop.
(2)  The code above works.  Run it with php.exe and the browser (IE) should come up and you should be able to click the buttons and get popups indicating what you did.  This is all working really well for me and there is no indication to the viewer that there are any problems.  The reason for filing the report is that the use of that .dummyVar indicates to me that something funny is going on under the hood, and I'd rather have it taken away now, if that's going to happen, than to suddenly realize a year later that I've been using a loophole.  But really, this is a fabulous technique so I hope the main pieces stay as is.

===>    Just to be clear, the above code accomplishes everything (in fact more than) I wanted.  But it seems incorrect to me that attempting to access a variable of the class (with arguments, no less), will cause a seemingly unrelated function of the class to be called, whereas attempting to access that function (or any other) directly will result in an error.   <===

(3).  There was no real cause to have __construct be the final function.  If it turns out that it is only a single, final function that can be called as an event handler, then a developer should use a separate one for that purpose and not mix in class creation.  __contruct being last in my code is an artifact left over from my initial testing when I had a single popup in __construct and couldn't figure out why the class instance kept getting recreated each time I tried to access it...

Thanks,
Csaba
 [2011-04-08 21:46 UTC] jani@php.net
-Package: Feature/Change Request +Package: COM related
 [2019-10-18 11:46 UTC] girgias@php.net
-PHP Version: 5CVS-2005-08-12 (dev) +PHP Version: Next Minor Version -Assigned To: +Assigned To: cmb
 [2019-10-18 11:46 UTC] girgias@php.net
Assigning to cmb for review as he's the new maintainer for COM.
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Thu Nov 14 04:01:33 2019 UTC