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();
});
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 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.
I'm trying to dynamically create the base for a DB entity generalization for a project I'm working on. I basically want to dynamically create a set of standard methods and tools for the properties in any class that extends this. Much like the tools you get for free with Python/Django.
I got the idea from this guy: http://www.stubbles.org/archives/65-Extending-objects-with-new-methods-at-runtime.html
So I've implemented the __call function as described in the post above,
public function __call($method, $args) {
echo "<br>Calling ".$method;
if (isset($this->$method) === true) {
$func = $this->$method;
$func();
}
}
I have a function which gives me the objects public/protected properties through get_object_vars,
public function getJsonData() {
$var = get_object_vars($this);
foreach($var as &$value) {
if (is_object($value) && method_exists($value, 'getJsonData')) {
$value = $value->getJsonData;
}
}
return $var;
}
and now I want to create some methods for them:
public function __construct() {
foreach($this->getJsonData() as $name => $value) {
// Create standard getter
$methodName = "get".$name;
$me = $this;
$this->$methodName = function() use ($me, $methodName, $name) {
echo "<br>".$methodName." is called";
return $me->$name;
};
}
}
Thanks to Louis H. which pointed out the "use" keyword for this down below.
This basically creates an anonymous function on the fly. The function is callable, but it is no longer within the context of it's object. It produces a "Fatal error: Cannot access protected property"
Unfortunately I'm bound to PHP version 5.3, which rules out Closure::bind. The suggested solution in Lazy loading class methods in PHP will therefore not work here.
I'm rather stumped here... Any other suggestions?
Update
Edited for brevity.
Try it like this (you have to make the variables you'll need available to the method)
$this->$methodName = function() use ($this, $methodName, $name){
echo "<br>".$methodName." is called";
return $this->$$name;
};
You should have access to the object context through $this.
Instead of updating the original question above, I include the complete solution here for anybody struggling with the same issues:
First of all, since the closure cannot have real object access, I needed to include the actual value with the "use" declaration when creating the closure function (see original __construct function above):
$value =& $this->$name;
$this->$methodName = function() use ($me, $methodName, &$value) {
return $value;
};
Secondly the __call magic method did not just need to call the closure function, it needed also to return any output from it. So instead of just calling $func(), I return $func();
This did the trick! :-)
I have a class that generates data based on a few things. I would like to format that data from the outside. So I am trying to pass a function into the class so that it would format that data. I have looked at many examples, but it seems this is unique.
Can anybody give an idea of how to do this? The following code gives an error.
<?php
class someClass {
var $outsideFunc; // placeholder for function to be defined from outside
var $somevar='Me'; // generated text
function echoarg($abc){
$outsideFunc=$this->outsideFunc; // bring the outside function in
call_user_func($outsideFunc,$abc); // execute outside function on text
echo $abc;
}
}
function outsidefunc($param){ // define custom function
$param='I am '.$param;
}
$someClass=new someClass();
$someClass -> outsideFunc = 'outsideFunc'; // send custom function into Class
$someClass -> echoarg($someClass->somevar);
$someClass -> outsidefunc = 'outsidefunc';
In PHP, function names are not case sensitive, yet object property names are. You need $someClass->outsideFunc, not $someClass->outsidefunc.
Note that good OOP design practice calls for the use of getter and setter methods rather than just accessing properties directly from outside code. Also note that PHP 5.3 introduced support for anonymous functions.
Yeah. You are right. Now there is no error. But it does not work either.
By default, PHP does not pass arguments by reference; outsidefunc() does not actually do anything useful. If you want it to set $param in the caller to something else, and do not want to just return the new value, you could change the function signature to look like this:
function outsidefunc(&$param) {
You would also need to change the way you call the function, as call_user_func() does not allow you to pass arguments by reference. Either of these ways should work:
$outsideFunc($abc);
call_user_func_array($outsideFunc, array(&$abc));
Why not pass your function as an argument?
<?php
class someClass {
public $somevar="Me";
public function echoarg($abc,$cb=null) {
if( $cb) $cb($abc);
echo $abc;
}
}
$someClass = new someClass();
$someClass->echoarg($someClass->somevar,function(&$a) {$a = "I am ".$a;});
i am not sure what exactly you are looking for, but what i get is, you want to pass object in a function which can be acheive by
Type Hinting in PHP.
class MyClass {
public $var = 'Hello World';
}
function myFunction(MyClass $foo) {
echo $foo->var;
}
$myclass = new MyClass;
myFunction($myclass);
OP, perhaps closures are what you're looking for?
It doesn't do EXACTLY what you're looking for (actually add function to class), but can be added to a class variable and executed like any normal anonymous function.
$myClass->addFunc(function($arg) { return 'test: ' . $arg });
$myClass->execFunc(0);
class myClass {
protected $funcs;
public function addFunc(closure $func) {
$this->funcs[] = $func;
}
public function execFunc($index) { $this->funcs[$index](); } // obviously, do some checking here first.
}
Warning: preg_replace_callback() [function.preg-replace-callback]: Requires argument 2, 'info', to be a valid callback in [...]
public function getDisplay(){
$info = array_merge($this->info,array());
return preg_replace_callback('!\{\{(\w+)\}\}!', 'info', $this->display);
}
In a public function from "MyClass", stopped working when I moved from another class to this one. Which was:
public function getEdit( $type){
$all_info = $this->info;
return preg_replace_callback('!\{\{(\w+)\}\}!', 'all_info', $edit_contents);
}
Both are cleaned up, and now I can't retest with previous class because it's already long gone.
I'm sure using variables is not allowed, but it was working, so I'm clueless.
When I do this, as suggested in some stackoverflow thread, but obviously it's not made to be used within objects:
public function getDisplay(){
$display_info = $this->info;
function display_info($matches) {
global $display_info;
return $display_info[$matches[1]];
}
return preg_replace_callback('!\{\{(\w+)\}\}!', 'display_info', $this->display);
}
So I need some love and guidance cuz php is driving me crazy this week...
Instead of using anonymous functions or more complex methods, you can just use one of methods supported for passing callbacks (see the documentation on callbacks):
A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1.
To pass "info" method of current object as a callback, just do the following:
array($this, 'info')
and pass it wherever you want to use "info" method as a callback within one of the objects other methods.
The correct way to do this would be with a closure:
public function getDisplay() {
// While $this support in closures has been recently added (>=5.4.0), it's
// not yet widely available, so we'll get another reference to the current
// instance into $that first:
$that = $this;
// Now we'll write that preg... line of which you speak, with a closure:
return preg_replace_callback('!\{\{(\w+)\}\}!', function($matches) use($that) {
return $that->info[$matches[1]];
}, $this->display);
}
This solved it:
public function getDisplay(){
return preg_replace_callback('!\{\{(\w+)\}\}!', array(get_class($this), '_preg_replace_callback'), $this->display);
}
private function _preg_replace_callback($matches){
return $this->info[$matches[1]];
}
I did try this approach before, but didn't use the get_class() function to wrap $this. Oh bother...