Is there any way, in PHP, to call methods from a parent class using the arbitrary-argument call_user_func_array? Essentially, I want to write a little bit of boilerplate code that, while slightly less optimal, will let me invoke the parent to a method arbitrarily like this:
function childFunction($arg1, $arg2, $arg3 = null) {
// I do other things to override the parent here...
$args = func_get_args();
call_user_func_array(array(parent, __FUNCTION__), $args); // how can I do this?
}
Is this an odd hack? Yeah. I will be using this boilerplate in many places, though, where there's likely to be error in transcribing the method args properly, so the tradeoff is for fewer bugs overall.
Try either one of
call_user_func_array(array($this, 'parent::' . __FUNCTION__), $args);
or
call_user_func_array(array('parent', __FUNCTION__), $args);
... depending on your PHP version. Older ones tend to crash slightly, careful :)
You can call any method on a parent class, as long as it is not overloaded closer to the class of the instance. Just use $this->methodName(...)
For slightly more advanced magic, here's a working example of what you seem to want:
Please note that i do not believe this to be a good idea
class MathStuff
{
public function multiply()
{
$total = 1;
$args = func_get_args();
foreach($args as $order => $arg)
{
$total = $total * $arg;
}
return $total;
}
}
class DangerousCode extends MathStuff
{
public function multiply()
{
$args = func_get_args();
$reflector = new ReflectionClass(get_class($this));
$parent = $reflector->getParentClass();
$method = $parent->getMethod('multiply');
return $method->invokeArgs($this, $args);
}
}
$danger = new DangerousCode();
echo $danger->multiply(10, 20, 30, 40);
Basically, this looks up the method MathStuff::multiply in the method lookup table, and executes its code on instance data from a DangerousCode instance.
Related
This may be a bit of an XY questions, so I'm going to explain what I'm trying to do first. I'm attempting to create a single php file to handle all of my page refresh AJAX calls. That means I want to be able to send it a class name, plus a list of the variables that the class constructor takes, and for it to then create the class.
I can create the class fine. $class = new $className(); works just fine for creating the class. The problem is passing in the default variables. Most of the variables are objects containing other classes, so I can't just include this once the class is created, I need to pass them as the class is created.
I was thinking something along the lines of:
$varStr = '';
$s = '';
foreach($vars as $var) {
switch($var['type']) {
case 'object':
$varStr .= $s . '$' . $var['value'];
break;
case 'variable':
$varStr .= $s . $var['value'];
}
$s = ',';
}
$class = new $className(echo $varStr);
Now obviously echo $varStr isn't going to work there, but I have no idea what will. Is there anything I can do that will output the variables from my array into the class constructor like that? Is what I'm trying to do even possible? Is there a better way?
Whilst I understand I could just pass the whole array to the class constructor, this would complicate the main part of the program, and I would rather just ditch the idea of a single page for AJAX refresh than go down that route.
So basically you're trying to pass a variable number of arguments to a constructor? In a regular function, you could do something like:
function foo() {
$args = func_get_args();
...
}
call_user_func_array('foo', array('bar', 'baz'));
This won't work for constructors, since the calling mechanism is different. You could do:
class Foo {
public function __construct() {
$args = func_get_args();
...
}
}
$class = new ReflectionClass('Foo');
$obj = $class->newInstanceArgs(array('bar', 'baz'));
But really, what you should be doing is this:
class Foo {
public function __construct(array $args) {
...
}
}
$obj = new Foo(array('bar', 'baz'));
or
class Foo {
public function __construct($bar, $baz) {
...
}
}
$obj = new Foo('bar', 'baz');
Anything else is quite insane. If your object constructor is so complicated, you probably need to simplify it.
This is a wild guess at what you're trying to do but maybe this is what you're after:
// Generate constructor args
$args = array();
foreach($vars as $var) {
switch($var['type']) {
$value = $var['value'];
case 'object':
args[] = ${$value}; // evaluate, I think that's what you want?
break;
case 'variable':
args[] = $value; // use as is
break;
}
}
// Instanciate class with args
$class = new ReflectionClass($className);
$obj = $class->newInstanceArgs($args);
For this to work, it would require $vars to enumerates args in the correct order expected by each class constructor.
I have a custom PHP class with few methods in it. Is is possible to call class method this way:
<?php
class someClass{
function someMethod_somename_1(){
echo 'somename1';
}
function someMethod_somename_2(){
echo 'somename2';
}
}
$obj = new someClass();
$methodName = $_GET['method_name'];
$obj->someMethod_{$methodName}(); //calling method
?>
My real world application is more complex, but here I provide just this simple example to get the main idea. Maybe I can use eval function here?
Please don't use eval() because it's evil in most situations.
Simple string concatenation helps you:
$obj->{'someMethod_'.$methodName}();
You should also verify the user input!
$allowedMethodNames = array('someone_2', 'someone_1');
if (!in_array($methodName, $allowedMethodNames)) {
// ERROR!
}
// Unrestricted access but don't call a non-existing method!
$reflClass = new ReflectionClass($obj);
if (!in_array('someMethod_'.$methodName, $reflClass->getMethods())) {
// ERROR!
}
// You can also do this
$reflClass = new ReflectionClass($obj);
try {
$reflClass->getMethod('someMethod_'.$methodName);
}
catch (ReflectionException $e) {
// ERROR!
}
// You can also do this as others have mentioned
call_user_func(array($obj, 'someMethod_'.$methodName));
Of course, take this:
$obj = new someClass();
$_GET['method_name'] = "somename_2";
$methodName = "someMethod_" . $_GET['method_name'];
//syntax 1
$obj->$methodName();
//alternatively, syntax 2
call_user_func(array($obj, $methodName));
Concatenate the whole method name before you call it.
Update:
Directly calling methods based on user input is never a good idea. Consider doing some previous validation of the method name before.
You may also take advantage of php magic methods, namely __call() in combination with call_user_func_array() and method_exists():
class someClass{
public function __call($method, $args) {
$fullMethod = 'someMethod_' . $method;
$callback = array( $this, $fullMethod);
if( method_exists( $this, $fullMethod)){
return call_user_func_array( $callback, $args);
}
throw new Exception('Wrong method');
}
// ...
}
For safety purposes you may want to create a wrapper which would prohibit calling other methods, like this:
class CallWrapper {
protected $_object = null;
public function __construct($object){
$this->_object = $object;
}
public function __call($method, $args) {
$fullMethod = 'someMethod_' . $method;
$callback = array( $this->_object, $fullMethod);
if( method_exists( $this->_object, $fullMethod)){
return call_user_func_array( $callback, $args);
}
throw new Exception('Wrong method');
}
}
And use it as:
$call = new CallWrapper( $obj);
$call->{$_GET['method_name']}(...);
Or maybe create execute method and than add to someClass method GetCallWrapper().
This way you'll get functionality well encapsulated into objects (classes) and won't have to copy it every time (this may come in handy if you'll need to apply some restrictions, i.e. privileges checking).
It is possible to use variable as function.
For example if you have function foo() you can have some variable $func and call it. Here is example:
function foo() {
echo "foo";
}
$func = 'foo';
$func();
So it should work like $obj->$func();
Can I have two methods sharing the same name, but with different arguments?
One would be public static and would take 2 arguments, the other one just public and takes only one argument
example
class product{
protected
$product_id;
public function __construct($product_id){
$this->product_id = $product_id;
}
public static function getPrice($product_id, $currency){
...
}
public function getPrice($currency){
...
}
}
No. PHP does not support classic overloading. (It does implement something else that is called overloading.)
You can get the same result by using func_get_args() and it's related functions though:
function ech()
{
$a = func_get_args();
for( $t=0;$t<count($a); $t++ )
{
echo $a[$t];
}
}
I'm just giving you the super lazy option:
function __call($name, $args) {
$name = $name . "_" . implode("_", array_map("gettype", $args)));
return call_user_func_array(array($this, $name), $args);
}
That would for example invoke the real function name getPrice_string_array for two parameters of that type. That's sort of what languages with real method signature overloading support would do behind the scenes.
Even lazier would be just counting the arguments:
function __callStatic($name, $args) {
$name = $name . "_" . count($args);
return call_user_func_array(array($this, $name), $args);
}
That would invoke getPrice_1 for 1 argument, or getPrice_2 for, you guessed it, two arguments. This might already suffice for most use cases. Of course you can combine both alternatives, or make it more clever by search for all alternative real method names.
If you want to keep your API pretty and user-friendly implementing such elaborate workarounds is acceptable. Very much so.
PHP currently doesn't support overloading in known way, but you can still achieve your goal by using magic methods.
From PHP5 manual: overloading.
You could, kind of...
I consider it very much "hack" solutions, but you could make a single function and assign a standard value, that wouldn't otherwise be okay to use, to the parameters as needed. Then if you do not pass the function a certain parameter, it will be set to fx "-1".
public function getPrice($product_id = "-1", $currency) {
if($product_id = "-1") {
//do something
}else {
//do something
}
}
Or if you really need one method to be static, you can make a method that evaluates which method to call and call that instead of your getPrice:
public function whichGetPrice($product_id = "-1", $currency) {
if($product !== "-1") {
getStaticPrice($product_id, $currency);
}else {
getPrice($currency);
}
}
Like I said, very much "hack" solutions. It's not exactly pretty, nor a way people would expect you to do it. So I wouldn't necessarily recommend it, but it can help you do what you want.
php 5.3
Is there a way to do this (viable in java for example)
(new MyClass())->myMethod();
i am receving: Parse error: syntax error, unexpected T_OBJECT_OPERATOR in D.. on line 7
Add
I really need that RFC to be implemented in the next PHP version!
http://wiki.php.net/rfc/instance-method-call
Is there a way we can subscribe to it so it can get more attention?
No, its not possible. There is a RFC for that
http://wiki.php.net/rfc/instance-method-call
But no one knows, when this will come to the userland.
Jacob mentioned the static method. There are other more or less useful methods to achieve the same
function instanciate($className, $arg1 = null) {
$args = func_get_args();
array_shift($args);
$c = new ReflectionClass($className);
return $c->newInstanceArgs($c);
}
instanciate('Classname', 1, 2, 3)->doSomething();
However, I prefer the temporary variable (like in the question).
Update:
I can swear there where an example for the temporary variable stuff in the question in the past. However, I meant this
$x = new Class;
$x->method();
where $x is the temporary variable.
That is not valid syntax. A handy way to achieve what you want is to use a static method to create the object.
In your MyClass:
public static function create() {
return new MyClass();
}
Then you can use:
MyClass::create()->myMethod();
However it is extra code that you have to maintain, if for example the constructor is changed or the class is extended. So you need to weigh up the benefits.
You can do something like this:
function chain_statements($statement1, $statement2) { return $statement2; }
class TClass { public Method() { ...; return $this; } }
$b = chain_statements($a = new TClass(), $a->Method());
... or more generalized:
function chain_statements(array $statements) { return end($statements); }
For example:
function chain_statements($statement1, $statement2) { return $statement2; }
function chain_statements2(array $statements) { return end($statements); }
class TClass
{
public $a = 0;
public function Method1() { $this->a = $this->a + 1; return $this; }
public function Method2() { $this->a = $this->a + 2; return $this; }
}
$b = chain_statements($c = new TClass(), $c->Method1()); echo($b->a);
$b = chain_statements2(array($c = new TClass(), $c->Method1(), $c->Method2())); echo($b->a);
... or even better:
function call_method($object) { return $object; }
$b = call_method(new TClass())->Method2(); echo($b->a);
Not as such. In PHP new is not an expression, but a language construct. The common workaround is to provide a static instantiation method for MyClass::get()->... use.
A more concise alternative is a hybrid factory function:
function MyClass() { return new MyClass; }
class MyClass {
...
}
Which then simplifies the instantiation to MyClass()->doSomething();
You can put it in one statement if you really wanted to. Use eval() ;p
But you probably shouldn't.
I had this same problem a while ago but I found this simple solution which is pretty readable too. I like the fact it uses only the standard PHP functions. There's no need to create any utility functions of your own.
call_user_func(
array(new ClassToInstance(), 'MethodName'),
'Method arguments', 'go here'
);
You can also use call_user_func_array to pass the arguments as an array.
call_user_func_array(
array(new ClassToInstance(), 'MethodName'),
array('Method arguments', 'go here')
);
PHP
mysql database
I have created a follow on question to this one here that is specifically about pagination
I need to call a method from one class in another, and be able to change the method that is called. Like so
class db{
function a(){ echo 'I run a query';}
function b(){ echo 'im the other query';}
}
class YourClass {
var $fcn;
$db = new db()
function invoke(){
call_user_func($this->fcn);
}
}
$instance = new YourClass;
$instance->fcn = 'db->a';
$instance->invoke();
I want to use a method 'a' from the db class in the 'yourClass' method 'invoke'
Thanks
Ok this is what i have put together from the answers provided and it works.
class A {
function a(){
$x = 'Method a is used';
return $x;
}
function b(){
$x = 'Method b is used';
return $x;
}
}
class B {
function invoke($obj, $method){
echo call_user_func( array( $obj, $method) );
}
}
$instance = new B();
$instance->invoke(new A(),"a");
Which writes, 'Method a is used' to the screen
But i really want to be able to pass arguments to method "a" so i tried the code below.
class A {
function a($var1,$var2,$var3){
$x = 'the three passed values are ' . $var1 . ' and ' . $var2 . ' and ' . $var3;
return $x;
}
function b(){
$x = 'im method b';
return $x;
}
}
class B {
function invoke($obj,$arguments){
echo call_user_func_array($obj,$arguments);
}
}
$arguments = array('apple','banana','pineapple');
$use_function = array(new A(),"a");
$instance = new B();
$instance->invoke($use_function,$arguments);
It almost works but i get these errors above the correct answer
Missing argument 1 for A::a(),.....for argument 2 and 3 as well but then the answer prints to the screen
"the three passed values are apple and banana and pineapple"
I'm probably making a rookie mistake I've been coding all day. If someone could fix the script above and submit the working code, I would be eternally grateful. I have to put this issue to bed so i can go to bed.
Thanks
As of PHP5.3 you could use closures or functors to pass methods around. Prior to that, you could write an anonymous function with create_function(), but that is rather awkward.
Basically, what you are trying to do could be done with the Strategy Pattern.
removed example code, as it wasn't helpful anymore after the OP changed the question (see wiki)
Apart from that, you might want to look into Fowlers's Data Source Architectural Patterns. The Zend Framework (and pretty much all other PHP frameworks) offers database access classes you could use for these patterns and there is also a paginator class, so why not check them out to learn how they did it.
removed EDIT 1 as it wasn't helpful anymore after the OP changed the question (see wiki)
EDIT 2
Ok, let's take a step by step approach to this (not using a Strategy Pattern though)
What you are asking for in the question can easily be solved with this code:
class Foo
{
public function bar()
{
echo 'bar method in Foo';
}
}
class MyInvoker
{
protected $myObject;
public function __construct()
{
$this->myObject = new Foo();
}
public function __call($method, $args)
{
$invocation = array($this->myObject, $method);
return call_user_func_array($invocation, $args);
}
}
With this code you'd just call the appropriate methods. No setting of methods names. No clumsy extra invoke method. No reinventing of how methods are called. You dont need it, because PHP has the __call function that you just taught to send all methods not existing in MyInvoker to $myObject, e.g. Foo.:
$invoker = new MyInvoker;
$invoker->bar(); // outputs 'bar method in Foo called'
You might just as well have extended MyInvoker to be a subclass of Foo, e.g.
class MyInvoker extends Foo {}
and then you could do the same. This not what you need though and it illustrates how pointless it is, to do such a thing. MyInvoker now does nothing by itself. It is an empty class and effectively the same as Foo. Even with the previous approach using the __call method it is not doing anything. This is why I have asked you to be more specific about the desired outcome, which is a Paginator.
First try:
class Paginator()
{
// A class holding all possible queries of our application
protected $queries;
// A class providing access to the database, like PDO_MySql
protected $dbConn;
public function __construct()
{
$this->db = new MyPdo();
$this->queries = new DbQueries();
}
public function __call($method, $args)
{
$invocation = array($this->queries, $method);
$query = call_user_func_array($invocation, $args);
return $this->dbConn->query($query);
}
}
With that code, our Paginator creates everything it needs inside itself, tightly coupling the db connection class and all queries and it allows you to call upon these, like so
$paginator = new Paginator;
// assuming we have something like getImageCount() in DbQueries
echo $paginator->getImageCount();
What is happening then is, Paginator will recognize it doesnt know getImageCount() and will invoke the __call method. The __call method will try to invoke the getImageCount() method on the DbQueries. Since it exists, it will return the query, which in turn is passed to the db connection to execute it. Great you'd say, but it's not. In fact, this is horrible. Your paginator's responsibility is to count items in a table and fetch items from this table in a certain range and amount. But right now, it is not doing anything like this. It is completely oblivious to whats going on, so lets try a new class:
class Paginator
{
protected $dbConn;
protected $itemCount;
public function __construct($dbConn)
{
$this->dbConn = $dbConn;
}
public function countItems($query)
{
$this->itemCount = $this->dbConn->query('select count(*) from (?)', $query);
return $this->itemCount;
}
public function fetchItems($query, $offset = 0, $limit = 20)
{
$sql = sprintf('select * from (?) LIMIT %d, %d', $offset, $limit);
return $this->dbConn->query($sql, $query);
}
}
Much better. Now our Paginator is an aggregate instead of a composite, meaning it does not instantiate objects inside itself, but requires them to be passed to it in the constructor. This is called dependency injection (and also provides a loose coupling, when dbConn uses an interface) which will make your app much more maintainable, as it is easy to exchange components now. This will also come in handy when Unit Testing your code.
In addition, your Paginator now concentrates on what it is supposed to do: counting and fetching items of an arbitrary query. No need to pass methods around. No need for obscure method invocation. You'd use it like this:
$paginator = new Paginator($dbConn);
$query = $dbQueries->findImagesUploadedLastWeek(); // returns SQL query string
$images = $paginator->countItems($query);
if($images > 0) {
$images = $paginator->fetchItems($query);
}
And that's it. Well, almost. You'd have to render the pagination of course. But this should be rather trivial, if you extend what you already have above. The $imageCount property is a hint at where to go next.
Anyway, hope that I could shed some light.
P.S. The $this->dbConn->query($sql, $query) calls are of course dummy code. Dont expect to be able to copy and paste it and get it working. In addition, you should make sure the queries added to the Paginator SQL is safe to use. You wouldnt want someone to insert a query that deletes all your db rows. Never trust user input.
P.P.S. $query should be an SQL query string. Check the PHP manual for PDO::prepare. In general, it yields better performance and security to prepare a statement before executing it. The page in the manual will give you the clues about the ? in the query calls. If you dont want to use PDO, just use sprintf() or str_replace() to replace ? with $query, e.g. $this->dbConn->query(sprintf('SELECT count(*) from (%s)', $query) but keep in mind that this has none of the benefits of a prepared statement and potentially opens the door for SQL Injection vulnerabilities.
P.P.P.S Yes, Dependency Injection is generally a preferred strategy. This is an advanved topic though and might be too much to fully grasp right now, but it's well worth looking into it. For now, it should be enough if you try to favor favor aggregation over composition. Your classes should only do what they are responsible for and get any dependencies through the constructor.
Here are two ways of doing it:
class YourClass {
var $fcn;
function invoke($arguments){
//one way:
$this->{$this->fcn}($arguments);
//another way:
call_user_func_array(array($this, $this->fcn), $arguments);
}
function a(){
echo 'I am a()';
}
}
$instance = new YourClass;
$instance->fcn = 'a';
$instance->invoke();
This'll print out "I am a()" from inside the class.
you are almost there
class db {
function a(){ echo 'I run a query';}
function b(){ echo 'im the other query';}
}
class YourClass {
var $fcn;
function __construct() {
$this->db = new db();
}
function invoke() {
call_user_func(array(
$this->{$this->fcn[0]},
$this->fcn[1]
));
}
}
$instance = new YourClass;
$instance->fcn = array('db', 'a');
$instance->invoke();
$instance->fcn = array('db', 'b');
$instance->invoke();
the syntax is quite fancy, but it works
// edit: from your comment it looks like the simplest option is to pass method name as string, like this
class Paginator {
function __consturct($db, $counter_func) ...
function get_count() {
$args = func_get_args();
return call_user_func_array(
array($this->db, $this->counter_func),
$args);
}
}
new Paginator($db, 'get_num_products');
I am guessing that you are using php here. Php supports variable functions which might solve you problem but as far as I am aware does not support delegates/function pointers.
What database are you using? I would be against putting queries within the code and using stored procedures as an alternative, if this is supported in the database you are using. This may solve the underlying problem you have.
Are you asking if PHP has functional references? It doesn't. But it does let you call functions by putting their name in a string, or an array of a class name and method name.
See call_user_func() for a description, and variable functions.
class DB {
function a(){ echo 'I run a query';}
function b(){ echo 'im the other query';}
}
class B {
protected $db;
private $method;
function __constructor($db) { $this->db; }
function invoke($m){
$this->method = $m;
// Non static call
call_user_func( array( $this->db, $this->method ) );
}
}
$db = new DB();
$b = new B($db);
$b->invoke('a');
I have made little modifications to my initial answer. You could also check out this post, it may help:
Database and OOP Practices in PHP
The Observer Design Pattern may be useful for this sort of thing, or it might be a misuse of the pattern; I don't know yet. Anyway, for your consideration:
class DbObserver implements SplObserver
{
public function update(SplSubject $subject) // Required
{
$method = $subject->getObserverMethod();
$args = $subject->getObserverArgs();
$this->$method($args);
}
private function a($args)
{
echo 'I run query ' . $args[0] . '<br />';
}
private function b($args)
{
echo 'I run query ' . $args[0] . ' because ' . $args[1] . '<br />';
}
private function c()
{
echo 'I have no argument' . '<br />';
}
}
class ObserverObserver implements SplObserver
{
public function update(SplSubject $subject) // Required
{
if (count($subject->getAttached()) > 1) {
echo 'I saw that<br />';
} else {
echo 'Nothing happened<br />';
}
}
}
class DbSubject implements SplSubject
{
private $observerMethod;
private $observerArgs = array();
private $attached = array();
public function notify() // Required
{
foreach ($this->attached as $each) {
$each->update($this);
}
}
public function attach(SplObserver $observer) // Required
{
$this->attached[] = $observer;
}
public function detach(SplObserver $observer) // Required
{
$key = array_keys($this->attached, $observer);
unset($this->attached[$key[0]]);
}
public function setObserverMethod($method, $args = array())
{
$this->observerMethod = $method;
$this->observerArgs = $args;
return $this;
}
public function getObserverMethod()
{
return $this->observerMethod;
}
public function getObserverArgs()
{
return $this->observerArgs;
}
public function getAttached()
{
return $this->attached;
}
}
$db_subj = new DbSubject;
$db_obs = new DbObserver;
$db_subj->attach($db_obs);
$args = array('A');
$db_subj->setObserverMethod('a', $args)->notify();
$args = array('B', 'I can');
$db_subj->setObserverMethod('b', $args)->notify();
$obsvr = new ObserverObserver;
$db_subj->attach($obsvr);
$db_subj->setObserverMethod('c')->notify();
$db_subj->detach($db_obs);
$db_subj->notify();
/**
I run query A
I run query B because I can
I have no argument
I saw that
Nothing happened
**/
You need to make this change:
$arguments = array('apple','banana','pineapple');
$a = new A();
$use_function = array(&$a,"a"); // Make this changes to your code
$instance = new B();
$instance->invoke($use_function,$arguments);
class A {
function a(){ echo 'I run a query';}
function b(){ echo 'im the other query';}
}
class B {
function Test() {
invoke(new $A(), "a");
}
function invoke($obj, $method){
// Non static call
call_user_func( array( $obj, $method ) );
// Static call
//call_user_func( array( 'ClassName', 'method' ) );
}
}
I hope this helps.