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]);
}
}
Related
Reading about laravel bindings,I understand $this->app->bind,$this->app->singleton and $this->app->instance because they are almost the same.
But $this->app->when is a little bit tricky for me.
In laravel example
$this->app->when('App\Http\Controllers\UserController')
->needs('$variableName')
->give($value);
In my understanding it injects some primitive value and the App\Http\Controllers\UserController is the alias of the object that will be binded.But where is the object?
Can anyone explain? Thank you for your help.
Contextual binding does not work on variable names, but on types. It is used to provide different implementations for interfaces to consuming classes or functions, depending on the context. In fact, you can actually read the method calls and it does exactly what you'd expect. To elaborate on this, I'll take the example of the documentation and adapt it slightly:
$this->app->when(Worker::class)
->needs(Money::class)
->give(function () {
return new Dollar();
});
$this->app->when(Boss::class)
->needs(Money::class)
->give(function () {
return new Cheque();
});
In this example, Money is an interface and Dollar as well as Cheque are implementations of the interface. The example literally means that if you typehint Money on a Worker class, it will resolve to an instance of Dollar while it will resolve to Cheque on a Boss class. To illustrate, here the implementations and the results:
interface Money
{
public function getAmount();
}
class Dollar implements Money
{
public function getAmount()
{
return 1;
}
}
class Cheque implements Money
{
public function getAmount()
{
return 100000;
}
}
And now we typehint the Money interface to see what we'll get:
class Worker
{
public function __construct(Money $money)
{
echo $money->getAmount(); // prints '1'
}
}
class Boss
{
public function __construct(Money $money)
{
echo $money->getAmount(); // prints '100000'
}
}
It means that if a class of UserController is instantiated and it needs a variable with the name $variableName Larvel will automatically resolve this variable with the given value and you don't have to provide it.
For example:
$value = "Sven"
$this->app->when('App\Http\Controllers\UserController')
->needs('$userName')
->give($value);
This would insert the value 'Sven' into the UserController whenever it needs the variable with the name $userName
In other words, if you had a function like public function __construct(Request $request) Laravel knows what to insert because it knows that a Request object is expected. When you use a function like public function __construct($name) Laravel has no clue what to insert here, essentially you tell Laravel how to resolve this variable with the bindings.
This is an example of primitive binding, for Contextual binding see the answer of #Namoshek
In case anyone finds this thread, trying to achieve contextual binding on an app()->make() or similar situation:
use Closure;
use Illuminate\Container\Container;
class FooBar {
public function doSomething(): void
{
//...do your thing
$this->getFooObject();
}
private function getFooObject(): FooAbstract
{
$class = FooAbstract::class;
$buildStack = static::class;
app()->beforeResolving(
$class,
Closure::bind(
function () use ($buildStack) {
$this->buildStack[] = $buildStack;
},
app(),
Container::class,
)
);
return app($class);
}
}
In the above example laravel's app container is bind as $this to this closure. Since the buildStack property, that's used to identify the object that "needs" the object that is being created, is protected, we have access to it, and we can add the classname to the stack.
for example: if $buildStack = Bar::class; you can do the following
app()->when(FooBar::class)->needs(FooAbstract::class)->give(FooImplementation::class);
I am struggling to get dependency injection to work the way I expect -
I am trying to inject a class, Api, which needs to know which server to connect to for a particular user. This means that overriding constructor properties in a config file is useless, as each user may need to connect to a different server.
class MyController {
private $api;
public function __construct(Api $api) {
$this->api = $api;
}
}
class Api {
private $userServerIp;
public function __construct($serverip) {
$this->userServerIp = $serverip;
}
}
How can I inject this class with the correct parameters? Is it possible to override the definition somehow? Is there some way of getting the class by calling the container with parameters?
To (hopefully) clarify - I'm trying to call the container to instantiate an object, while passing to it the parameters that would otherwise be in a definition.
Since IP depends on the user you probably have some piece of logic that does the user=>serverIP mapping. It might be reading from the db or simple id-based sharding, or whatever. With that logic you can build ApiFactory service that creates Api for a particular user:
class ApiFactory {
private function getIp(User $user) {
// simple sharding between 2 servers based on user id
// in a real app this logic is probably more complex - so you will extract it into a separate class
$ips = ['api1.example.com', 'api2.example.com'];
$i = $user->id % 2;
return $ips[$i];
}
public function createForUser(User $user) {
return new Api($this->getIp($user);
}
}
Now instead of injecting Api into your controller you can inject ApiFactory (assuming your controller knows the user for which it needs the Api instance)
class MyController {
private $apiFactory;
public function __construct(ApiFactory $apiFactory) {
$this->apiFactory = $apiFactory;
}
public function someAction() {
$currentUser = ... // somehow get the user - might be provided by your framework, or might be injected as well
$api = $this->apiFactory->createForUser($currentUser);
$api->makeSomeCall();
}
}
I am not sure I understand your question fully, but you can configure your Api class like this:
return [
'Foo' => function () {
return new Api('127.0.0.1');
},
];
Have a look at the documentation for more examples or details: http://php-di.org/doc/php-definitions.html
Edit:
return [
'foo1' => function () {
return new Api('127.0.0.1');
},
'foo2' => function () {
return new Api('127.0.0.2');
},
];
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.
Originally, my Slim Framework app had the classic structure
(index.php)
<?php
$app = new \Slim\Slim();
$app->get('/hello/:name', function ($name) {
echo "Hello, $name";
});
$app->run();
But as I added more routes and groups of routes, I moved to a controller based approach:
index.php
<?php
$app = new \Slim\Slim();
$app->get('/hello/:name', 'HelloController::hello');
$app->run();
HelloController.php
<?php
class HelloController {
public static function hello($name) {
echo "Hello, $name";
}
}
This works, and it had been helpful to organize my app structure, while at the same time lets me build unit tests for each controler method.
However, I'm not sure this is the right way. I feel like I'm mocking Silex's mount method on a sui generis basis, and that can't be good. Using the $app context inside each Controller method requires me to use \Slim\Slim::getInstance(), which seems less efficient than just using $app like a closure can.
So... is there a solution allowing for both efficiency and order, or does efficiency come at the cost of route/closure nightmare?
I guess I can share what I did with you guys. I noticed that every route method in Slim\Slim at some point called the method mapRoute
(I changed the indentation of the official source code for clarity)
Slim.php
protected function mapRoute($args)
{
$pattern = array_shift($args);
$callable = array_pop($args);
$route = new \Slim\Route(
$pattern,
$callable,
$this->settings['routes.case_sensitive']
);
$this->router->map($route);
if (count($args) > 0) {
$route->setMiddleware($args);
}
return $route;
}
In turn, the Slim\Route constructor called setCallable
Route.php
public function setCallable($callable)
{
$matches = [];
$app = $this->app;
if (
is_string($callable) &&
preg_match(
'!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!',
$callable,
$matches
)
) {
$class = $matches[1];
$method = $matches[2];
$callable = function () use ($class, $method) {
static $obj = null;
if ($obj === null) {
$obj = new $class;
}
return call_user_func_array([$obj, $method], func_get_args());
};
}
if (!is_callable($callable)) {
throw new \InvalidArgumentException('Route callable must be callable');
}
$this->callable = $callable;
}
Which is basically
If $callable is a string and (mind the single colon) has the format ClassName:method then it's non static, so Slim will instantiate the class and then call the method on it.
If it's not callable, then throw an exception (reasonable enough)
Otherwise, whatever it is (ClassName::staticMethod, closure, function name) it will be used as-is.
ClassName should be the FQCN, so it's more like \MyProject\Controllers\ClassName.
The point where the controller (or whatever) is instantiated was a good opportunity to inject the App instance. So, for starters, I overrode mapRoute to inject the app instance to it:
\Util\MySlim
protected function mapRoute($args)
{
$pattern = array_shift($args);
$callable = array_pop($args);
$route = new \Util\MyRoute(
$this, // <-- now my routes have a reference to the App
$pattern,
$callable,
$this->settings['routes.case_sensitive']
);
$this->router->map($route);
if (count($args) > 0) {
$route->setMiddleware($args);
}
return $route;
}
So basically \Util\MyRoute is \Slim\Route with an extra parameter in its constructor that I store as $this->app
At this point, getCallable can inject the app into every controller that needs to be instantiated
\Util\MyRoute.php
public function setCallable($callable)
{
$matches = [];
$app = $this->app;
if (
is_string($callable) &&
preg_match(
'!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!',
$callable,
$matches
)
) {
$class = $matches[1];
$method = $matches[2];
$callable = function () use ($app, $class, $method) {
static $obj = null;
if ($obj === null) {
$obj = new $class($app); // <--- now they have the App too!!
}
return call_user_func_array([$obj, $method], func_get_args());
};
}
if (!is_callable($callable)) {
throw new \InvalidArgumentException('Route callable must be callable');
}
$this->callable = $callable;
}
So there it is. Using this two classes I can have $app injected into whatever Controller I declare on the route, as long as I use a single colon to separate controller from method. Using paamayim nekudotayim will call the method as static and therefore will throw an error if I try to access $this->app inside it.
I ran tests using blackfire.io and... the performance gain is negligible.
Pros:
this saves me the pain of calling $app = \Slim\Slim::getInstance() on every static method call accounting for about 100 lines of text overall.
it opens the way for further optimization by making every controller inherit from an abstract controller class, which in turn wraps the app methods into convenience methods.
it made me understand Slim's request and response lifecycle a little better.
Cons:
performance gains are negligible
you have to convert all your routes to use a single colon instead of paamayin, and all your controller methods from static to dynamic.
inheritance from Slim base classes might break when they roll out v 3.0.0
Epilogue: (4 years later)
In Slim v3 they removed the static accessor. In turn, the controllers are instantiated with the app's container, if you use the same convention FQCN\ClassName:method. Also, the method receives the request, response and $args from the route. Such DI, much IoC. I like it a lot.
Looking back on my approach for Slim 2, it broke the most basic principle of drop in replacement (Liskov Substitution).
class Route extends \Slim\Route
{
protected $app;
public function __construct($app, $pattern, $callable, $caseSensitive = true) {
...
}
}
It should have been
class Route extends \Slim\Route
{
protected $app;
public function __construct($pattern, $callable, $caseSensitive = true, $app = null) {
...
}
}
So it wouldn't break the contract and could be used transparently.
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)
}
}