I'm trying to use __callStatic to wrap the calls to differents static functions in my class.
This current version of the code allows me to do so with functions that have 0 or 1 parameter:
class test {
public static function __callStatic($name, $args) {
if (method_exists(__CLASS__, $name)) {
echo "it does exist\n";
return forward_static_call([__CLASS__, $name], $args[0]);
} else {
echo "it does not exist\n";
}
}
private static function mfunc($param1, $param2) {
echo "that's my func \n";
echo "$param1\n";
echo "$param2\n";
}
}
test::notafunc("one", 'two');
test::mfunc("one", "two");
However it will fail for mfunc since it has two parameters.
How can i spread the different arguments when i'm calling the function ?
I can't use the new spread operator because I am locked on PHP 7.2.
I can't modify mfunc either, because I have way too much methods to change in the project.
The spread operator is still usable in PHP 7.2, what you point to is something slightly different (it's about being able to use it as part of an array). So you can use
return forward_static_call([__CLASS__, $name], ...$args);
As a more legible version...
return static::{$name}(...$args);
Related
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.
So I know I can use __callStatic() to allow for:
Example::test();
Too work... But what I was wondering if its possible to overload
Example::test->one()
Example::test->two()
Is this possible at all with PHP? If so does anyone have a working example?
Or does anyone know of a work around?
Edit
Thought I would add some more details on why am wanting to do this and its basically cause I have a API with has methods like api.method.function and want to build a simple script to interact with that api.
There is also some methods that are just api.method
To quote the manual:
__callStatic() is triggered when invoking inaccessible methods in a static context.
So it doesn't work with properties - what you would need for your example to work as you described it is __getStatic. But, that doesn't exist.
The closest workaround that I can think of would be to use something like:
Example::test()->one();
Example::test()->two();
Where test() can be defined through __callStatic and it returns an object which has the methods one and two, or has __call defined.
EDIT:
Here's a small example (code hasn't been tested):
class ExampleTest {
public function __call($name, $arguments) {
if ($name == 'one') {
return 'one';
}
if ($name == 'two') {
return 'two';
}
}
}
class Example {
public static function __callStatic($name, $arguments) {
if ($name == 'test') {
// alternatively it could be return ExampleTest.getInstance() if you always want the same instance
return new ExampleTest();
}
}
}
I have a couple of functions inside a class that essentially do the same thing:
public function fn_a(){
return __FUNCTION__;
}
public function fn_b(){
return __FUNCTION__;
}
public function fn_c(){
return __FUNCTION__;
}
I need those functions to remain in their current names so I intentionally did not do:
public function fn_($letter){
return __FUNCTION__.$letter;
}
I was hoping for some sort of way to minify the verboseness of code here, since they all do the same. The ultimate situation would be something like this:
public functions fn_a, fn_b, fn_c() {
return __FUNCTION__;
}
Another solution, if applicable, might be doing something like Class's "extends":
fn_b, fn_c extend fn_a?
What do you think guys?
Any syntax like the one you suggested is not possible : if you want several distinct functions, you have to declare all those functions.
Still, a possibility could be that your fn_a, fn_b and fn_c functions just be simple wrappers arround a more complex one :
public function fn_a(){
return fn_all('a');
}
public function fn_b(){
return fn_all('b');
}
public function fn_c(){
return fn_all('c');
}
public function fn_all($name) {
// a lot of complex stuff here
}
With that, depending on the length on the fn_all function, of course, you would reduce the amount of code-duplication.
Another idea (not sure how this could be done with methods, so I'll demonstrate with functions) would be to use Closures -- which means PHP >= 5.3
The basic idea being that you'd have a first function, that would return another one -- which would bind the parameter passed to the first one :
First, the function that creates the others :
function creator($name) {
return function () use ($name) {
return $name;
};
}
And, then, let's get three functions, using that creator one :
$fn_a = creator('a');
$fn_b = creator('b');
$fn_c = creator('c');
And now, calling those three functions :
echo $fn_a() . '<br />';
echo $fn_b() . '<br />';
echo $fn_c() . '<br />';
We get the following output :
a
b
c
I've never good at explaining how anonymous functions and closures work -- but searching for "closure" on google should help you understand ; note that you can read tutorial about closures in Javascript : the idea is exactly the same.
(And, as closures are new in PHP -- arrived with PHP 5.3 -- you will not find as many tutorials as for Javascript)
public function fn_catcher($letter) {
return __FUNCTION__.$letter;
}
public function __call($name) {
if (substr($name, 0, 3) == 'fn_')
{
return $this->fn_catcher($name);
}
}
Like that?
What's the best way to do something like this in PHP?:
$a = new CustomClass();
$a->customFunction = function() {
return 'Hello World';
}
echo $a->customFunction();
(The above code is not valid.)
Here is a simple and limited monkey-patch-like class for PHP. Methods added to the class instance must take the object reference ($this) as their first parameter, python-style.
Also, constructs like parent and self won't work.
OTOH, it allows you to patch any callback type into the class.
class Monkey {
private $_overload = "";
private static $_static = "";
public function addMethod($name, $callback) {
$this->_overload[$name] = $callback;
}
public function __call($name, $arguments) {
if(isset($this->_overload[$name])) {
array_unshift($arguments, $this);
return call_user_func_array($this->_overload[$name], $arguments);
/* alternatively, if you prefer an argument array instead of an argument list (in the function)
return call_user_func($this->_overload[$name], $this, $arguments);
*/
} else {
throw new Exception("No registered method called ".__CLASS__."::".$name);
}
}
/* static method calling only works in PHP 5.3.0 and later */
public static function addStaticMethod($name, $callback) {
$this->_static[$name] = $callback;
}
public static function __callStatic($name, $arguments) {
if(isset($this->_static[$name])) {
return call_user_func($this->_static[$name], $arguments);
/* alternatively, if you prefer an argument list instead of an argument array (in the function)
return call_user_func_array($this->_static[$name], $arguments);
*/
} else {
throw new Exception("No registered method called ".__CLASS__."::".$name);
}
}
}
/* note, defined outside the class */
function patch($this, $arg1, $arg2) {
echo "Arguments $arg1 and $arg2\n";
}
$m = new Monkey();
$m->addMethod("patch", "patch");
$m->patch("one", "two");
/* any callback type works. This will apply `get_class_methods` to the $m object. Quite useless, but fun. */
$m->addMethod("inspect", "get_class_methods");
echo implode("\n", $m->inspect())."\n";
Unlike Javascript you can't assign functions to PHP classes after the fact (I assume you are coming from Javascript becuase you are using their anonymous functions).
Javascript has a Classless Prototypal system, where as PHP has a Classical Classing System. In PHP you have to define every class you are going to use, while in Javascript, you can create and change each object however you want.
In the words of Douglas Crockford: You can program in Javascript like it is a Classical System, but you can't program in a Classical System like it is Javascript. This means that a lot of the stuff you are able to do in Javascript, you can't do in PHP, without modifications.
I smell Adapter Pattern, or maybe even Decorator Pattern!
I started off OOP with Java, and now I'm getting pretty heavy into PHP. Is it possible to create multiples of a function with different arguments like in Java? Or will the interpreted / untyped nature of the language prevent this and cause conflicts?
Everyone else has answers with good code explanations. Here is an explanation in more high level terms: Java supports Method overloading which is what you are referring to when you talk about function with the same name but different arguments. Since PHP is a dynamically typed language, this is not possible. Instead PHP supports Default arguments which you can use to get much the same effect.
If you are dealing with classes you can overload methods with __call() (see Overloading) e.g.:
class Foo {
public function doSomethingWith2Parameters($a, $b) {
}
public function doSomethingWith3Parameters($a, $b, $c) {
}
public function __call($method, $arguments) {
if($method == 'doSomething') {
if(count($arguments) == 2) {
return call_user_func_array(array($this,'doSomethingWith2Parameters'), $arguments);
}
else if(count($arguments) == 3) {
return call_user_func_array(array($this,'doSomethingWith3Parameters'), $arguments);
}
}
}
}
Then you can do:
$foo = new Foo();
$foo->doSomething(1,2); // calls $foo->doSomethingWith2Parameters(1,2)
$foo->doSomething(1,2,3); // calls $foo->doSomethingWith3Parameters(1,2,3)
This might not be the best example but __call can be very handy sometimes. Basically you can use it to catch method calls on objects where this method does not exist.
But it is not the same or as easy as in Java.
Short answer: No. There can only be one function with a given name.
Longer answer: You can do this by creating a convoluted include system that includes the function with the right number of arguments. Or, better yet, you can take advantage of PHP allowing default values for parameters and also a variable amount of parameters.
To take advantage of default values just assign a value to a parameter when defining the function:
function do_something($param1, $param2, $param3 = 'defaultvaule') {}
It's common practice to put parameters with default values at the end of the function declaration since they may be omitted when the function is called and makes the syntax for using them clearer:
do_something('value1', 'value2'); // $param3 is 'defaultvaule' by default
You can also send a variable amount of parameters by using func_num_args() and func_get_arg() to get the arguments:
<?php
function dynamic_args() {
echo "Number of arguments: " . func_num_args() . "<br />";
for($i = 0 ; $i < func_num_args(); $i++) {
echo "Argument $i = " . func_get_arg($i) . "<br />";
}
}
dynamic_args("a", "b", "c", "d", "e");
?>
Following isn't possible with php
function funcX($a){
echo $a;
}
function funcX($a,$b){
echo $a.$b;
}
Instead do this way
function funcX($a,$b=null){
if ($b === null) {
echo $a; // even though echoing 'null' will display nothing, I HATE to rely on that
} else {
echo $a.$b;
}
}
funcX(1) will display 1, func(1,3) will display 13
Like everyone else said, it's not supported by default. Felix's example using __call() is probably the best way.
Otherwise, if you are using classes that inherit from each other you can always overload the method names in your child classes. This also allows you to call the parent method.
Take these classes for example...
class Account {
public function load($key,$type) {
print("Loading $type Account: $key\n");
}
}
class TwitterAccount extends Account {
public $type = 'Twitter';
public function load($key) {
parent::load($key,$this->type);
}
}
Then you can call them like so...
$account = new Account();
$account->load(123,'Facebook');
$twitterAccount = new TwitterAccount();
$twitterAccount->load(123);
And your result would be...
Loading Facebook Account: 123
Loading Twitter Account: 123
No this isn't possible, because PHP cannot infer from the arguments which function you want (you don't specify which types you expect). You can, however, give default values to arguments in php.
That way the caller can give different amounts of arguments. This will call the same function though.
Example is:
function test($a = true)
This gives a default of true if 0 arguments are given, and takes the calling value if 1 argument is given.
I know it's a bit old issue, but since php56 you can:
function sum(...$numbers) {
$acc = 0;
foreach ($numbers as $n) {
$acc += $n;
}
return $acc;
}
echo sum(1, 2, 3, 4);
ref: http://php.net/manual/en/functions.arguments.php
Overloading is not possible in PHP but you can get around it to some extend with default parameter values as explained in other responses.
The limit to this workaround is when one wants to overload a function/method according to the parameter types. This is not possible in PHP, one need to test the parameter types yourself, or write several functions. The functions min and max are a good example of this : if there is one parameter of array type it returns the min/max of the array, otherwise it returns the min/max of the parameters.
I had the idea of something like:
function process( $param1 , $type='array' ) {
switch($type) {
case 'array':
// do something with it
break;
case 'associative_array':
// do something with it
break;
case 'int_array':
// do something with it
break;
case 'string':
// do something with it
break;
// etc etc...
}
}
I have got 2 methods, getArrayWithoutKey which will output all the entries of an array without supplying any key value. The second method getArrayWithKey will output a particular entry from the same array using a key value. Which is why I have used method overloading there.
class abcClass
{
private $Arr=array('abc'=>'ABC Variable', 'def'=>'Def Variable');
public function setArr($key, $value)
{
$this->Arr[$key]=$value;
}
private function getArrWithKey($key)
{
return $this->Arr[$key];
}
private function getArrWithoutKey()
{
return $this->Arr;
}
//Method Overloading in PHP
public function __call($method, $arguments)
{
if($method=='getArr')
{
if(count($arguments)==0)
{
return $this->getArrWithoutKey();
}
elseif(count($arguments)==1)
{
return $this->getArrWithKey(implode(',' , $arguments));
}
}
}
}
/* Setting and getting values of array-> Arr[] */
$obj->setArr('name', 'Sau');
$obj->setArr('address', 'San Francisco');
$obj->setArr('phone', 7777777777);
echo $obj->getArr('name')."<br>";
print_r( $obj->getArr());
echo "<br>";