I have two class A and B
I want to 'send' a method from A to B via B constructor and then execute it in B. I've been trying to work with anonymous functions just like this :
class A
{
public function __construct()
{
// Send testMethod() to B with an anonymous function
new B(function (string $test) {
$this->testMethod($test);
});
}
protected function testMethod(string $test) {
echo ($test);
}
}
class B
{
protected $testFct;
public function __construct($fctToExecute)
{
// Asign anonymous function to $testFct to be executed in all object
$this->testFct= function (string $test) {
$fctToExecute($test);
};
}
// Want to be able now to call this $testFct function in a method like :
protected function methodWhereICallTestfct() {
$this->testFct("I'm dumb!"); // It has to echo "I'm dumb!"
}
}
But when I try to set it up I get always an error like :
Uncaught Error: Call to undefined method testFct()
Do you have any idea what the problem is? I would like to specify that my php version is PHP 7.1.3.
EDIT :
Here is it possible to see a more similare code as mine which return the error
You have got 2 mistakes in your code.
First mistake is that you ignore the parameter $fctToExecute in B::__construct(). If you would like to save the passed closure into a property of object B then you do not need another closure.
public function __construct(closure $fctToExecute) {
// Asign anonymous function to $testFct to be executed in all object
$this->testFct = $fctToExecute;
}
The second problem is that when you are trying to execute the closure you are actually trying to execute a function called testFct. You should use parentheses to declare precedence of operations.
$this->testFct("I'm dumb!"); // This looks for a function called testFct
($this->testFct)("I'm dumb!"); // This will execute the closure stored in the property called $testFct
The parentheses make a lot of difference here.
Related
I have a class with methods that I want to use as callbacks.
How can I pass them as arguments?
Class MyClass {
public function myMethod() {
// How should these be called?
$this->processSomething(this->myCallback);
$this->processSomething(self::myStaticCallback);
}
private function processSomething(callable $callback) {
// Process something...
$callback();
}
private function myCallback() {
// Do something...
}
private static function myStaticCallback() {
// Do something...
}
}
Check the callable manual to see all the different ways to pass a function as a callback. I copied that manual here and added some examples of each approach based on your scenario.
Callable
A PHP function is passed by its name as a string. Any built-in or user-defined function can be used, except language constructs such as: array(), echo, empty(), eval(), exit(), isset(), list(), print or unset().
// Not applicable in your scenario
$this->processSomething('some_global_php_function');
A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1.
// Only from inside the same class
$this->processSomething([$this, 'myCallback']);
$this->processSomething([$this, 'myStaticCallback']);
// From either inside or outside the same class
$myObject->processSomething([new MyClass(), 'myCallback']);
$myObject->processSomething([new MyClass(), 'myStaticCallback']);
Static class methods can also be passed without instantiating an object of that class by passing the class name instead of an object at index 0.
// Only from inside the same class
$this->processSomething([__CLASS__, 'myStaticCallback']);
// From either inside or outside the same class
$myObject->processSomething(['\Namespace\MyClass', 'myStaticCallback']);
$myObject->processSomething(['\Namespace\MyClass::myStaticCallback']); // PHP 5.2.3+
$myObject->processSomething([MyClass::class, 'myStaticCallback']); // PHP 5.5.0+
Apart from common user-defined function, anonymous functions can also be passed to a callback parameter.
// Not applicable in your scenario unless you modify the structure
$this->processSomething(function() {
// process something directly here...
});
As of PHP 8.1, we now have first-class callables. They use the syntax $callable = functionName(...). The three dots are part of the syntax and not an omission.
You can use the new syntax to create callable class methods.
Class MyClass {
public function myMethod() {
// first-class callables
$this->processSomething($this->myCallback(...));
$this->processSomething(self::myStaticCallback(...));
}
private function processSomething(callable $callback) {
// Process something...
$callback();
}
private function myCallback() {
// Do something...
}
private static function myStaticCallback() {
// Do something...
}
}
The three dots are not an omission/placeholder for parameters. They are a special syntax for creating a callable. If the method accepts no parameters, the syntax remains the same.
Since 5.3 there is a more elegant way you can write it, I'm still trying to find out if it can be reduced more
$this->processSomething(function() {
$this->myCallback();
});
You can also to use call_user_func() to specify a callback:
public function myMethod() {
call_user_func(array($this, 'myCallback'));
}
private function myCallback() {
// do something...
}
You can set the method return type to callable. It works for PHP 7.1
protected function myMethod(): callable
{
return function (int $j) {
};
}
Then call it like this:
someFunction($this->myMethod());
I am trying to learn OOP PHP so much of this is new to me. I have seen other posts but havent found one that answers my question.
class Test
{
public function a(){
//getting data...
return $array;
}
public function b($array){
return true;
}
}
$test = new Test();
$x = $test->a()->b();
When I attempt the above I get the following error:
Fatal error: Uncaught Error: Call to a member function a() on array
Can someone explain why this doesnt work? a returns an array and b accepts an array.
In order to do what you're trying to do here, you'd need to use this instead:
$x = $test->b($test->a());
The second arrow in the expression
$x = $test->a()->b();
attempts to call an object method on the return value of $test->a(), which is an array as you know. It does not pass the return value of a() as an argument to b().
In order to use a syntax like ->a()->b(), a() must return an object with a method b(). If the method b() is a method from the same object, you can do that by returning $this in a(), but if it also needs to return an array, that won't work, so you won't be able to chain the methods like this.
It is possible to make it work if a() doesn't return the array, but instead stores in in an object property, and b() doesn't take an array, but instead operates on the data in the property.
class Test
{
protected $data;
public function a() {
//getting data...
$this->data = $theDataYouGot;
return $this;
}
public function b($array) {
// do something with $this->data
return true;
}
}
Personally I try to avoid this sort of thing because I think it adds unnecessary complexity and makes testing more difficult without much added benefit, but I have seen it done.
I'm trying to run a method on each element inside a collection. It's an object method residing in the same class:
protected function doSomething()
{
$discoveries = $this->findSomething();
$discoveries->each([$this, 'doSomethingElse']);
}
protected function doSomethingElse($element)
{
$element->bar();
// And some more
}
If I precede the call on Collection::each with the check is_callable([$this, 'doSomethingElse']) it returns true, so apparently it is callable. The call itself however throws an exception:
Type error: Argument 1 passed to Illuminate\Support\Collection::each() must
be callable, array given, called in ---.php on line 46
The method trying to be called can be found here.
I'm bypassing this by just passing a closure that itself simply calls that function, but this would definitely a much cleaner solution and I can't find out why it throws the error.
Change the visibility of your callback method to public.
protected function doSomething()
{
$discoveries = $this->findSomething();
$discoveries->each([$this, 'doSomethingElse']);
}
public function doSomethingElse($element)
{
$element->bar();
// And some more
}
Since PHP 7.1 you can leave your function protected. Now you can write:
protected function doSomething()
{
$discoveries = $this->findSomething();
$discoveries->each(\Closure::fromCallable([$this, 'doSomethingElse']));
}
protected function doSomethingElse($element)
{
$element->bar();
// And some more
}
Source
function with callback
public function foo(callable $callback){
$callback('something')
}
named function for callback
public function do($something) {
//
}
result
foo(function ($something) {
//
})
or call named function
foo(\Closure::fromCallable([$this, 'do']))
or
foo(this->do(...))
PHP >= 5.4
I wasn't able to reproduce your error, but my guess is that you should use $discoveries instead of $this in the callback array, like so:
$discoveries->each([$discoveries, 'doSomethingElse']);
Even though $discoveries and $this are of the same class, and therefore can access each other's protected and private methods, the type-hinting functionality may not check that the object in the callback array is the same class as the current class. However, the is_callable() method will check for this, which may explain why it returns true when you call it from inside the each() method.
PHP < 5.4
There is no type named callable, so when you use it as a type hint, it is referring to a class named callable. See this answer.
I created a basic class to play with Closure object a bit. I don't understand the behaviour of this application/closures so I wanted to ask a few things. My mind is pretty cloudy at the moment so I don't know why something runs or why not.
<?php
class Route
{
public static $bindings = array();
public static $dispatch = array();
public static function bind($bind)
{
self::$bindings[] = $bind;
}
public static function getAllBindings()
{
return (array) self::$bindings;
}
public static function get($binding, Closure $dispatch)
{
if(in_array($binding, self::$bindings))
{
if(is_callable($dispatch))
{
return call_user_func($dispatch);
}
else
{
die("Dispatch method is not callable.");
}
}
else
{
die("Binding is not found in bindings array.");
}
}
public static function test()
{
echo "Test ran!";
}
}
Basically, we bind bindings (such as /admin, /account, /profile etc.) Then, we try to call a method using closure.
// Let's bind account and admin as available bindings
Route::bind('account');
Route::bind('admin');
// Let's try doing a get call with parameter "account"
Route::get('account', function() {
// This is where I'm stuck. See below examples:
// Route::test();
// return "test";
// return "testa";
// return self::test();
});
If you checked above, here are my questions:
If I provide a non-existent method, is_callable check does not run and I get a php fatal error. Isn't is_callable a valid check for checking inexistent methods? Why does it happen?
If I provide return "Test"; in the closure, is my $closure parameter in get method going to contain "Test" string?
Can I pass a methods from different classes inside the closure? Like:
Route::get('account', function () {
if(User::isLoggedIn() !== true)
return Error::login_error('Unauthorized.');
});
If so, in which scope this call is being made? PHP's scope in closure, or does call_user_func call it inside Route class' scope since it is passed to it via closure? (To clear it a bit more, PHP's scope may do $route->get but Closure scope may use $this->get)
Is there any way to dump Closure object like var_dump/print_r to see it's contents?
A short guidance will get me going. I know PHP but using closures is pretty new to me.
Thanks alot and I appreciate your replies.
You won't need that is_callable() check as the Closure type hint in the method declaration already ensures this. Also you don't need call_user_func(). This will give you the following get() method:
public static function get($binding, Closure $dispatch)
{
if(!in_array($binding, self::$bindings))
{
die("Binding is not found in bindings array.");
}
return $dispatch();
}
Note : Currently the $binding param will just being used in a check, but not as a param to $dispatch(), what would I have expected. I can't see a reason for that. You should rethink this part
I found another hidden question in your post:
// Let's try doing a get call with parameter "account"
Route::get('account', function() {
// This is where I'm stuck. See below examples:
// Route::test();
// return "test";
// return "testa";
// return self::test();
});
It should look like:
// Let's try doing a get call with parameter "account"
Route::get('account', function() {
// Both should work well:
// Route::test();
// .. or
// return self::test();
});
Is there a way to bind an existing function to an anonymous one in php? Something like
$my_func = strip_tags();
Or must I half-redefine it, as a sort of anonymous wrapper, with the proper arguments and return value?
I tried googling this, but I suppose I didn't correctly suss the proper search phrase, as I didn't find results on the first page.
Edit I'm making a sort of function pipeline(?) where I can pass data and functions, and I want to pass functions as variables. I would like to keep the syntax the same and be able to use $output = $function($data) without having to write a bunch of anonymous wrappings for native functions. Also I would like to avoid using call_user_func so I don't have to re-write my existing code.
Simple.
$my_func = 'strip_tags';
$output = $my_func($data);
You can bind the function by it's name. Have a look at the callable Interface from php
Code from the manual mentioned above
<?php
// An example callback function
function my_callback_function() {
echo 'hello world!';
}
// An example callback method
class MyClass {
static function myCallbackMethod() {
echo 'Hello World!';
}
}
// Type 1: Simple callback
call_user_func('my_callback_function');
// Type 2: Static class method call
call_user_func(array('MyClass', 'myCallbackMethod'));
// Type 3: Object method call
$obj = new MyClass();
call_user_func(array($obj, 'myCallbackMethod'));
// Type 4: Static class method call (As of PHP 5.2.3)
call_user_func('MyClass::myCallbackMethod');
// Type 5: Relative static class method call (As of PHP 5.3.0)
class A {
public static function who() {
echo "A\n";
}
}
class B extends A {
public static function who() {
echo "B\n";
}
}
call_user_func(array('B', 'parent::who')); // A
?>