How to pass parameters to anonymous functions in PHP? - php

I have the following piece of code:
$evManager = $di->getShared('eventsManager');
$evManager->attach('dispatch', function($event, $dispatcher, $exception){
$dispatcher = new \Phalcon\Mvc\Dispatcher();
$dispatcher->setEventsManager($evManager);
return $dispatcher;
})
$evManager is object that has a method called attach which takes two arguments and it's clear for me. The second parameter is an anonymous function which has three arguments ($event, $dispatcher, $exception).
So my question is what are these three parameters? Why they aren't empty? What pass they to the anonymous function? I can't understand it...
I know that the anonymous function returns dispatcher object and the method attach do something on it. The only question is about parameters.

Think of that anonymous function as being an ordinary object with a method on it. You could write that code like this:
class MyDispatcherHelper {
public function handle($event, $dispatcher, $exception) {
$dispatcher = new \Phalcon\Mvc\Dispatcher();
$dispatcher->setEventsManager($evManager);
return $dispatcher;
}
}
$evManager = $di->getShared('eventsManager');
$evManager->attach('dispatch', new MyDispatcherHelper());
So now there's no more anonymous function.
The "magic" happens inside $evManager->attach. It's definition looks something like this:
class EventsManager {
public function attach($eventName, $handler) {
// somehow listen for events named $eventName
...
// and get an instance of the Event
$myEvent = $listener->theEvent;
// if it's an exception maybe set $exception to something usefull?
...
//_call_ $handler when event occurs
call_user_func($handler, [$myEvent, $this, $exception]);
}
}
You should read the docs for call_user_func.
Now if we continue with my "replace anonymous function with class example" the above code would look like this:
class EventsManager {
public function attach($eventName, MyDispatcherHelper $handler) {
// somehow listen for events named $eventName
...
// and get an instance of the Event
$myEvent = $listener->theEvent;
// if it's an exception maybe set $exception to something usefull?
...
//_call_ $handler when event occurs
$handler->handle($myEvent, $this, $exception);
}
}
That's what an anonymous function does.
Your code has nothing to do with calling that function. It is not under your control, you cannot tell it what parameters to call the anonymous function with, that's what eventsManager does.
An anonymous function is not called where you define it, and you can define any number of parameters on it and name them whatever you like.
Also the code inside the anonymous function might look like it does some magic regarding the code outside of it but it does not. $dispatcher->setEventsManager($evManager) is also wrong, I'm not seeing a global $evManager anywhere.

Those parameters usually tend to provide some additional information when working with plugin-like architecture. For example, if you have a Dependency Injection container, like
$di->register('translator', function($di){
// You can omit usage of $di here, because you don't need to grab from the container at right now
return new Translator();
});
$di->register('Message', function($di){
$translator = $di->get('translator');
return new Message($translator);
});
Then in some cases you might need to grab a dependency, while in some cases you don't.
How it works?
That's simple.
You simply assume that a parameter will be a function and therefore you pass arguments to it right at declaration. For example, in that $di class definition, it would look like this:
class Di
{
public function register($name, $provider)
{
// We will assume that $provider is a function
// and therefore pass some data to it
$this->data[$name] => $provider($this); // or another parameter (s)
}
}

Related

How to reference this in closure

I am using PHP Version 7.1.1.
In index.php of my MVC I create a route collection ($routes). To it I want to add a route group (with addGroup). This addition process relies on a closure (e.g. an anonymous function), passed as argument to addGroup. Inside of closure I am trying to reference the collection with $this, so that I can add one or more routes to it (with addRoute, a method of the collection).
Unfortunately I could not achieve that, although I made some tries. I miss a piece of the closures theory, somewhere. I would appreciate, if you would provide me an advice.
The "problem" is, that, in order to achieve a simplified usability for the user, I am trying to avoid the direct use of a closure parameter. Why? Because inside addGroup I am already in the collection's scope and from there I am calling the closure. This way I can pass the collection's instance ($this) as argument to the executeGroupHandler method and, therefore to executeGroupHandler as well.
Thank you for your time and patience!
index.php
$routes = new RouteCollection();
$routes->addGroup('/test', function() {
$this->addRoute('GET', '/group', function() {
echo 'Hello from route /test/group';
});
});
RouteCollection
class RouteCollection implements CollectionInterface {
public function addGroup(string $groupPattern, callable $groupHandler) {
$group = new RouteGroup($groupPattern, $groupHandler);
return $group->executeGroupHandler($this);
}
public function addRoute($httpMethod, string $routePattern, $routeHandler) {
//...
return $this;
}
}
RouteGroup
class RouteGroup {
private $groupPattern;
private $groupHandler;
public function __construct(string $groupPattern, callable $groupHandler) {
$this->groupPattern = $groupPattern;
$this->groupHandler = $groupHandler;
}
public function executeGroupHandler(CollectionInterface $routeCollection) {
return call_user_func_array($this->groupHandler, [$routeCollection]);
}
}
Try this:
$routes->addGroup('/test', function() use ($routes) {
$routes->addRoute('GET', '/group', function() {
echo 'Hello from route /test/group';
});
});
You are trying to access $this or $routes from a closure that is outside the scope of the class
In an abstract sense you can achieve this using the bind function of a closure:
$routes = new RouteCollection();
$routeHandler = Closure::bind(function() {
$this->addRoute('GET', '/group', function() {
echo 'Hello from route /test/group';
});
}, $routes);
$routes->addGroup('/test', $routeHandler);
This is useful when you want to pass this closure around but want to maintain scope.
I decided to present the solution in this post, as accepted answer, only to provide a compact overview of it, together with its implementation. But:
Credits:
The solution that elegantly fulfills all my requirements, and that I have chosen to use, was kindly presented by #apokryfos. The idea proposed by #ArtisticPhoenix was very correct as well.
Thank you very much guys! I appreciate your help.
Solution:
The idea is, that, if you bind the closure to an object - in my case the collection ($routes), then the object is accessible within the scope of the closure and can be referenced with $this.
Based on this idea I could achieve an implementation which not only allows the user to not pass any arguments to the group handler callback (the closure function), but also to still pass arguments (the container object $routes) if he wishes it.
Implementation of the solution:
index.php
$routes = new RouteCollection();
$routes
// The most simple and elegant use-case.
->addGroup('/group1', function() {
$this->addRoute('GET', '/route1', function() {
echo 'Hello from route /group1/route1';
});
})
// An optional use-case.
->addGroup('/group2', function($routes) {
$routes->addRoute('GET', '/route1', function() {
echo 'Hello from route /group2/route1';
});
})
// Another optional use-case.
->addGroup('/group3', function() use ($routes) {
$routes->addRoute('GET', '/route1', function() {
echo 'Hello from route /group3/route1';
});
})
;
RouteCollection
class RouteCollection implements CollectionInterface {
public function addGroup(string $groupPattern, callable $groupHandler) {
if ($groupHandler instanceof Closure) {
$groupHandler = $groupHandler->bindTo($this);
}
$group = new RouteGroup($groupPattern, $groupHandler);
$group->executeGroupHandler($this);
return $this;
}
}
RouteGroup
class RouteGroup {
public function executeGroupHandler(CollectionInterface $routeCollection) {
return call_user_func_array($this->groupHandler, [$routeCollection]);
}
}

what to add to my container ioc ? php

i create my own container, i made"bind" method that get "string $alias" and "Closure $closure". the method bind the alias to the object on the $container array , like that:
public function bind(string $alias,$closure)
{
$this->container[$alias] = $closure();
}
The second method is "call", that simply call to some instance from the $container. Of course first the method check if the given alias exists, and if doesnt throw an exception
public function call(string $alias)
{
if(array_key_exists($alias,$this->container))
return $this->container[$alias];
throw new \Exception();
}
is that good ? what more i need to add ? cause i saw on laravel for example that the container is full of method and props, and i dont know why. its just bind and call, isnt ?
My suggestion is to do the following:
Bind
public function bind(string $alias,$closure)
{
$this->container[$alias] = $closure;
}
Call (new instance per call)
public function call(string $alias)
{
if(array_key_exists($alias,$this->container)) {
$closure = $this->container[$alias];
return $closure();
}
throw new \Exception();
}
Call (singleton behaviour)
public function call(string $alias)
{
if (array_key_exists($alias,$this->instanceContainer)) {
return $this->instanceContainer[$alias];
}
if(array_key_exists($alias,$this->container)) {
$closure = $this->container[$alias];
$this->instanceContainer[$alias] = $closure();
return $this->instanceContainer[$alias];
}
throw new \Exception();
}
Which one of the two implementations you pick for the call will depend on your needs. If you need to call the function only once per object go for the second one. You could also have 2 methods: bind and bindSingleton to differentiate the two different binding types.
There are two main reasons I recommend this:
The object is generated on demand, which means that if you bind 1000 objects but only use 10 you'll only create 10 objects.
The result of the $closure() method may be different based on the time it was called. For example, if you want to instantiate an object which contains a time-stamp, instantiating it on call rather than on bind is more reasonable.

Binding a closure to a class as a new method

I am building an API class that extends the functionality of a vendor class. The vendor class expects to be extended, and will check for the existence of methods like this:
if (method_exists($this, 'block'.$CurrentBlock['type']))
{
$CurrentBlock = $this->{'block'.$CurrentBlock['type']}($CurrentBlock);
}
So since my API is also a vendor file, I thought I'd do something clever and try to let people pass closures into my API and have that extend the class.
public function extendBlock($blockName, Closure $closure)
{
$methodName = camel_case("block_{$blockName}");
$this->{$methodName} = $closure;
return method_exists($this, $methodName);
}
This would theoretically bind the closure so that the call in my first codeblock would succeed... but that doesn't happen. It is not seen as a method, but rather a property which contains a closure. Not only does method_exist fail, but attempting to call the method fails.
Here's a modified version where I'm trying to figure out what's going wrong.
public function extendBlock($blockName, Closure $closure)
{
$methodName = camel_case("block_{$blockName}");
$newClosure = clone $closure;
$newClosure = $newClosure->bindTo($this);
$this->{$methodName} = $newClosure;
$this->{$methodName}();
return method_exists($this, $methodName);
}
None of this works. The property is definitely set and the scope for $this in $closure is currently pointing to the $this of that method.
If I run this instead, the closure executes correctly.
$this->{$methodName} = $newClosure;
//$this->{$methodName}();
$foobar = $this->{$methodName};
$foobar();
So yeah. I was really hoping for a nice, tidy way of satisfying the check in my first codeblock without requiring the user to inherit my class and write them directly, but I don't think that's possible.
Edit: This is slightly different from Storing a Closure Function in a Class Property in PHP -- while the solution with __call that was provided there is excellent and is worth looking into if you're curious about binding closures to a class, this method does not trick the method_exists check.
It will not work with method_exists() as that function provides information based on methods which are declared explicitly in the class scope. However, there is still workaround with magic methods. __call() to be precise:
class Caller
{
public function bind($method, Closure $call)
{
$this->$method = $call;
}
public function __call($method, $args)
{
if (isset($this->$method) && $this->$method instanceof Closure) {
return call_user_func_array($this->$method, $args);
}
}
}
Will allow you to force call on your "property callable". For example,
$c = function($x) {
return $x*$x;
};
$obj = new Caller();
$obj->bind('foo', $c);
var_dump($obj->foo(4)); //16
See sample here.
There may be ways to change the class itself dynamically (runkit and company), but I would strongly recommend to stay away from that as long as possible.
With latest Runkit from http://github.com/zenovich/runkit you can simply write runkit_method_add(get_class($this), $methodName, $newClosure);
to do this.

how to call two method with single line in php?

I have seen in Laravel calling multiple method in the single line, example:
DB::get('test')->toJson();
I have a cool class and view method in that class.
$this->call->view('welcome')->anotherMethod();
I would like to call another method also? Where should I make that method?
DB::get() seems to be a method returning an object, where you can call other functions (I think a result object of a database query). If you want to call multiple functions on one object in one line, you have to return $this in your functions, e.g.:
class View {
public static function factory {
// The question is: How useful is this factory function. In fact: useless in
// the current state, but it can be extended in any way
return new self;
}
public function one() {
// do something
return $this;
}
public function two() {
// do something
return $this;
}
}
Then you can do:
$class = new View();
$class->one()->two();
// it's also possible to use the `factory` function
// you should think about, how useful this approach is in your application
$class = View::factory()->one()->two();
That's how you can do it in php, if laravel has some helpers for that, i can't say :)

how to pass a parameter to method with php's is_callable

I have to create a variable that is callable with php's is_callable
I have done this:
$callable = array(new MyClass, 'methodName');
But I want to pass a parameter to the method.
How can I do that?
Cause using symfony's event dispatcher component will be like:
$sfEventDispatcher->connect('log.write', array(new IC_Log('logfile.txt'), 'write'));
The first parameter is just a event name, the second is the callable variable.
But I can only call the write method, I want to pass a parameter to it.
Since PHP 5.3 you can use anonymous functions.
You should connect the listener like this:
$dispatcher->connect('my.event', function($event) {
$my_class = new MyClass;
$my_class->myMethod($event, array('my_param' => 'my_value'));
});
Then you will be able to get the parameters array in the listener:
class MyClass {
public function myMethod(sfEvent $event, $parameters) {
$my_value = $parameters['my_param'];
$event_param_value = $event['event_param'];
}
}
Now you can notify the event normally:
$dispatcher->notify(new sfEvent($this, 'my.event', array('event_param' => 'event_param_value')));
Take care that this listener can't be disconnected.
If you need to disconnect it, put the anonymous function in a variable:
$dispatcher->connect('my.event', $my_listener = function($event) {
$my_class = new MyClass;
$my_class->myMethod($event, array('my_param' => 'my_value'));
});
You should be able to disconnect with:
$dispatcher->disconnect('my.event', $my_listener);
You don't pass parameters to your listener callbacks (without extending the core). Symfony will be the one calling it and will pass an event object. If you need additional info, you can create a different method that calls another method where you can control the parameters.
$callable1 = array(new MyWriter, 'write1');
$callable2 = array(new MyWriter, 'write2'); // or $callable2 = array($callable1[0], 'write2');
$sfEventDispatcher->connect('log.write', $callable1);
$sfEventDispatcher->connect('log.write', $callable2);
And your callback class methods can be something like:
class MyWriter
{
public function write($event, $num)
{
// do something
}
public function write1($event)
{
$this->write($event, 1);
}
public function write2($event)
{
$this->write($event, 2);
}
}
Alternatively, you can create properties that act as state that your write function can check:
class MyWriter
{
public $state = 1;
public function write($event)
{
if ($this->state == 1) {
// do this
} else {
// do this instead
}
}
}
This is trickier as you'd have to set state before a pertinent event is triggered which may not prove feasable, depending on the specifics of your situation:
$callable[0]->state = 2;

Categories