Dynamic invocation of constructors...

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

Roja wrote:
Hockey wrote:
Roja wrote: As much as I dislike the site, the tenor, the members, and the overall environment, these questions I find are often better answered at Sitepoint's php forums. Perhaps try asking there.
For what reason would that be?

Whats wrong with this place?
What reason would what be? That I dislike the site, the tenor, the members, and the overall environment at Sitepoint? Answering that would take the thread very very off-topic.

There is nothing wrong with this place. My point was that despite my dislike for Sitepoint, I find that the answers to OOP questions there are generally more robust than they are here.
Edit...I've decided it was inappropriate :P
Last edited by alex.barylski on Mon May 08, 2006 8:21 pm, edited 1 time in total.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

I'm confused as your first post says dynamic constructor invocation with arbitrary number of arguments

And you third post says they don't take unknown number of arguments...

So I assume what you mean is that the class ctor() "definitions" are fixed, but because the class can be *any* class the parameter list is therefore arbitrary???

Edit I've re-read your post a few times and it seems you solved the problem already, but I am missing something...

Why can't you call the ctor() normally via *new* operator as you've descirbed and pass it the variable number of arguments?

Code: Select all

$args = array('param1', '$param2', 213553);
$obj = new $myClass($args);

// This creates the object, returns the instance and 
// passes the ctor() an arbitrary number of parameters 
// depending on your situation???
//
// What am I missing?
//
// To my knowledge you can use a normal class as below
// with a fixed argument list, avoiding use of func_get_args(), etc...
What you have done or show in example won't work, because $className is just that...a class...so when you use call_user_func and pass it __construct the script will choke as ctor()/dtor() are are not *true* member functions as they can never return values and cannot be made static, etc...

They are special functions used by the host language object model framework...

You should not ever have to call ctor()/dtor() directly, but technically you can although ctor() makes no sense, as I've said, they cannot return values...so calling ctor() does nothing and I guess compilers might even complain as you cannot initialize an object which doesn't exist...

It is the new operator which returns the object instance not the ctor().

if $className was an object instead, the code works fine as you are now calling an objects method, but you still cannot call the ctor() this way or at least it serves no purpose as the object is already created...

So you now understand why either class or object is being used *you cannot call a ctor() and expect proper results* even if it's allowed by host language object model...

What you need to do is rely solely on the natural object creation facilities of PHP and do something like:

Code: Select all

include(dirname(__FILE__).'/class.test.php');

  $className = 'MyClass';

  $obj = new $className('fname', 'lname');
You already hinted at this and it's the way to go...if I understand you correctly anyways :P

$className is now dynamic and can be anything your heart desires...and you instantiate the object as normal...like calling a function dynamically - but in this case an special kind of function...

Now you can use the functions arborint suggested to get the list of arguments, or you can hardcode them as I expect you want to, like so:

Code: Select all

class MyClass{

  function __construct($fname, $lname)
  {
    echo 'construct() called<br />';

    echo $fname.'<br />';
    echo $lname.'<br />';
  }

  function __destruct()
  {
    echo 'destruct() called<br />';
  }
}
I'm truley confused by what you want now as the above works, I've tried it...

Allowing both arbitrary arguments and any class you desire...

Cheers :)
User avatar
cj5
Forum Commoner
Posts: 60
Joined: Tue Jan 17, 2006 3:38 pm
Location: Long Island, NY, USA

Post by cj5 »

arborint wrote:The problem with __get()/__set() is that they are error handlers -- not actually accessor functions.
I will be brief to not go off topic:

1. No they are not error handlers, that's what the try() {} catch; structure is for. Take a look at the link I mentioned, and you'll see what I mean.

2. The format you mentioned is either some other PHP methods, or is wrong. In PHP5 the syntax is get_<functionName>/set_<functionName>
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

cj5 wrote:
arborint wrote:The problem with __get()/__set() is that they are error handlers -- not actually accessor functions.
I will be brief to not go off topic:

1. No they are not error handlers, that's what the try() {} catch; structure is for. Take a look at the link I mentioned, and you'll see what I mean.

2. The format you mentioned is either some other PHP methods, or is wrong. In PHP5 the syntax is get_<functionName>/set_<functionName>
I'm not sure what arborint meant by error handlers...but getter/setters are certainly not that...

However, try/catch blocks are not for error handling either...but rather exception handling...

Errors can typically be handled by normal programming logic (if, switch, etc) whereas exceptions are (typically) thown by the system when a error which otherwise would choke a application might possibly be handled...so yes try/catch is for handling errors, but a different kind of error...and for all intent and purpose should be considered different...

As for point 2...on behalf of Aborint...that was likely just a typo :?

Cheers :)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

cj5 wrote:1. No they are not error handlers, that's what the try() {} catch; structure is for. Take a look at the link I mentioned, and you'll see what I mean.
Hockey wrote:I'm not sure what arborint meant by error handlers...but getter/setters are certainly not that...
The first two sentences in the PHP manual page regarding these functions:
Both method calls and member accesses can be overloaded via the __call, __get and __set methods. These methods will only be triggered when your object or inherited object doesn't contain the member or method you're trying to access.
They are not true accessor functions, they are only called in the event a property or method does not exist. If you check the internals you will find that they are indeed implemented as installable error handlers that are called on a "property/method not found" error. Other languages implement true accessor overloading. You may think it is a subtle difference, but you would be amazed at the powerful and useful things you cannot implement because of this.
(#10850)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

cj5 wrote:2. The format you mentioned is either some other PHP methods, or is wrong. In PHP5 the syntax is get_<functionName>/set_<functionName>
Hockey wrote:As for point 2...on behalf of Aborint...that was likely just a typo
:?:
(#10850)
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

The "property/method not found" event fires when there is a scope resolution issues too. Although on a technical level, yes they aren't true accessors, but they come close enough for me to deal with it nicely.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

They are not true accessor functions, they are only called in the event a property or method does not exist. If you check the internals you will find that they are indeed implemented as installable error handlers that are called on a "property/method not found" error. Other languages implement true accessor overloading. You may think it is a subtle difference, but you would be amazed at the powerful and useful things you cannot implement because of this.
My bad...I'm not familiar with PHP5 object model in it's entirety just yet... :P

Not sure I get the point of these getter/setter functions...as I've always been used to *true* accesser/mutators like those in C++...

So PHP5 getter/setter are called automatically if you access a data member directly instead of using a real getProperty() type function???

This way you can guarantee a user gets some value back, etc...ie: error handling???

Cheers :)
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

Again in my defense :P

I didn't bother to check with the manual...but I assumed you were familiar with what the function names were...

So really I was just defending you by chance you typo'ed while writing...

I hate it when people pick on syntactic discrepancies when really only semantics are important :)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

feyd wrote:The "property/method not found" event fires when there is a scope resolution issues too. Although on a technical level, yes they aren't true accessors, but they come close enough for me to deal with it nicely.
They are handy for many simple things, but for library builders they are close to useless. For example, try to implement a config container that acts like this:

Code: Select all

$config->database->server = 'localhost';
$config->database->username = 'admin';
$config->title = 'My Site';

$username = $config->database->username;
$title = $config->title;
Seems simple and it would be very natural and handy. Can't be done. Zend tried in the framework and failed -- just like Active Record and a bunch of other stuff. Hopefully they will fix some of these problems now that they are in the framework business, but others have been suffering for years. The walls on the cell close in around you when you try to do interesting OO stuff in PHP.
(#10850)
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

arborint wrote:
feyd wrote:The "property/method not found" event fires when there is a scope resolution issues too. Although on a technical level, yes they aren't true accessors, but they come close enough for me to deal with it nicely.
They are handy for many simple things, but for library builders they are close to useless. For example, try to implement a config container that acts like this:

Code: Select all

$config->database->server = 'localhost';
$config->database->username = 'admin';
$config->title = 'My Site';

$username = $config->database->username;
$title = $config->title;
Seems simple and it would be very natural and handy. Can't be done. Zend tried in the framework and failed -- just like Active Record and a bunch of other stuff. Hopefully they will fix some of these problems now that they are in the framework business, but others have been suffering for years. The walls on the cell close in around you when you try to do interesting OO stuff in PHP.
Unless I'm completely missing something, the following works quite nicely.

Code: Select all

<?php

if (php_sapi_name() == 'cli')
{
	$eol = "\n";
}
else
{
	$eol = "<br />\n";
}

class DNConfig
{
	private $data;
	
	function __construct()
	{
		$this->data = array();
	}
	
	function __get($aVar)
	{
		global $eol;
		if (!isset($this->data[$aVar]))
		{
			echo 'New internal config object.' . $eol;
			$this->data[$aVar] = new DNConfig();
		}
		
		echo 'returning ' . var_export($aVar, true) . $eol;
		return $this->data[$aVar];
	}
	
	function __set($aVar, $aValue)
	{
		global $eol;
		echo $aVar . ' =  ' . var_export($aValue, true) . $eol;
		$this->data[$aVar] = $aValue;
	}
}

$config = new DNConfig();

$config->database->server = 'localhost';
$config->database->username = 'admin';
$config->title = 'My Site';

$username = $config->database->username;
$title = $config->title;

var_dump($username, $title, $config);

?>

Code: Select all

New internal config object.
returning 'database'
server =  'localhost'
returning 'database'
username =  'admin'
title =  'My Site'
returning 'database'
returning 'username'
returning 'title'
string(5) "admin"
string(7) "My Site"
object(DNConfig)#1 (1) {
  ["data:private"]=>
  array(2) {
    ["database"]=>
    object(DNConfig)#2 (1) {
      ["data:private"]=>
      array(2) {
        ["server"]=>
        string(9) "localhost"
        ["username"]=>
        string(5) "admin"
      }
    }
    ["title"]=>
    string(7) "My Site"
  }
}
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

Hockey wrote:I'm confused as your first post says dynamic constructor invocation with arbitrary number of arguments

And you third post says they don't take unknown number of arguments...

So I assume what you mean is that the class ctor() "definitions" are fixed, but because the class can be *any* class the parameter list is therefore arbitrary???
Lets say you have two different classes that when instantiated are supposed to create immutable objects.

Code: Select all

class Contact (
   private $name;
   private $title;
   private $email;
   function __ construct($n,$t,$e) {
      $this->name=$n;
      $this->title=$t;
      $this->email=$e;
  }
  function getName() { return $this->name; }
  function getTitle() { return $this->title; }  
  function getEmail() { return $this->email; }
}
class Deadline {
  private $deadlineDate;
  private $label;
  function __construct($d,$l) {
    $this->deadlineDate=$d;
    $this->label=$l;
  }
  function getDeadlineDate() { return $this->deadlineDate;}
  function getFormattedDate($s) {return date($s,$this->deadlineDate); }
  function getLabel() { return $this->label;}
}
Both of these classes are being loaded by a ORM layer when a request for a Competition is received. The Competition class has a 1:m relationship with both Contacts and Deadlines. The Competition class deals with its contained Contacts and Deadlines via normal collection interfaces -- add/remove/iterate. The orm layer knows to call addContact(Contact $contact) for each row returned from the contact table. It knows that each row of the table must be turned into a Contact object following the template constructor in the mapping file "(Create contact Contact(name, title, email)" where name, title, and email are the column names in the table. "contact" is the alias used for the entity in other places in the mapping file and "Contact" is the name of the class to instantiate. Thus when the script reaches this point it has all the information it needs to invoke any of the mapped classes constructor and with the proper aarguments, but no "clean" way to do so in the language. I would prefer not to use eval, only because that requires adding extra security checks that the direct invocation of a function can avoid. However it appears that eval is my only choice. And while I've already ensures text data fields from the DB won't cause problems, I haven't enforces type safety on numeric types. While I can't see a way for the db to return a eval-dangerous field from an in/float/numeric field, I still feel I need to add some protection there, that would be un-needed with a call_user_func style approach.

Your example of changing/requiring each class to have an array constructor is one of the types of requirements I'm trying to avoid in my ORM. So far I've been able to grow it to supporting a rather complicated domain model without injecting anything into the model that's only needed by the ORM. Yes it means the mapping file is complicated, and I'll need to spend some type cleaning it back up and refactoring the parsing code.

But there's no no requirement for a default constructor, there's no requirement for setters on all attributes (or __set), there's no requirement for a surrogate key in the domain model's attributes, etc. There's no link to the DB at all (unless a loaded class is actually a thunk/proxy, but that's transparent to the domain model and the proxy is created within the ORM so the domain author doesn't need to create proxy versions of the real classes manually)
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

arborint wrote: Seems simple and it would be very natural and handy. Can't be done. Zend tried in the framework and failed -- just like Active Record and a bunch of other stuff. Hopefully they will fix some of these problems now that they are in the framework business, but others have been suffering for years. The walls on the cell close in around you when you try to do interesting OO stuff in PHP.
Its code like this makes me understand why Java's reflection is allowed to bypass visibility (protected/private) concerns; I never understood why that was allowed before, but now.... It sure makes writing general purpose libraries easier. And yes I'm trying to implement a PHP-flavored Hibernate....
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

feyd wrote:Unless I'm completely missing something, the following works quite nicely.
No, of course you are right and I am wrong. I know full well that they added recursive support (especially to __set()) in 5.1. You can now implement a nicer container (I use something very similar myself).

My point was that this is nice for app programmers, but for lib programmers this still really isn't property support. There is still a name clash. They still aren't really properties, for example you can't use get_object_vars() without know the implementation. You can't easily mix real properties and virtual ones. Etc. Perhaps they will incrementally implement workarounds until they get there -- but they could have skipped all this and provided real accessor support rather than adding an error handler hook and then tweeking its utility.

This all relates to nielsene's having to resort to eval rather than there being real support for mixins, call interception, etc. They can be done to various extents via various workarounds. As I said, I think we will see much more progress in this area now that Zend is in the tool business.
Last edited by Christopher on Tue May 09, 2006 3:14 pm, edited 1 time in total.
(#10850)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

nielsene wrote:I would prefer not to use eval, only because that requires adding extra security checks that the direct invocation of a function can avoid. However it appears that eval is my only choice. And while I've already ensures text data fields from the DB won't cause problems, I haven't enforces type safety on numeric types. While I can't see a way for the db to return a eval-dangerous field from an in/float/numeric field, I still feel I need to add some protection there, that would be un-needed with a call_user_func style approach.
I don't think that eval is necessarily bad. And because you are aware of the security implications there should be no problem. The problem with eval is its unsafe use -- but it is a useful tool in scripting languges.
(#10850)
Post Reply