Jump to content

Access $this in a callback


NotionCommotion

Recommended Posts

Trying to build a simple router.  Still needs work, but one place I am stuck on is where I define my endpoints in index.php.  Specifically, how do I access $router when within the callback?  I see that my use of $this is incorrect as I am not in object context.  What do I need to change to make it in object context? Thanks

$router->get('pattern', function(Request $request){
    //How do I access $this?
});

 

 

index.php

<?php
error_reporting(E_ALL);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);

spl_autoload_register(function ($classname) {
    $parts = explode('\\', $classname);
    require __DIR__."/../src/".end($parts).".php";
});

$c = new Pimple();

$c['view'] = function ($c) {
    return new View();
};

$router = new Router($c);

$router->get('/^\/test\/?$/', function(Request $request, Response $response){
    var_dump($request); var_dump($response);
});
$router->get('/^\/test\/(\w+)\/?$/', function(Request $request, Response $response){
    var_dump($request); var_dump($response);
});
$router->get('/^\/test\/(\w+)\/(\d+)\/?$/', function(Request $request, Response $response){
    var_dump($request); var_dump($response);
});
$router->get('/^\/index.php\/?$/', function(Request $request){
    var_dump($this);var_dump($request); var_dump($response);
});

$router->run();
<?php
class Router {

    private $routes     =   [];
    private $container  =   [];

    public function __construct($container)
    {
        $this->container=$container;
    }

    public function get($pattern, $callback) {
        $this->routes['GET'][$pattern] = $callback;
    }
    public function post($pattern, $callback) {
        $this->routes['POST'][$pattern] = $callback;
    }
    public function put($pattern, $callback) {
        $this->routes['PUT'][$pattern] = $callback;
    }
    public function delete($pattern, $callback) {
        $this->routes['DELETE'][$pattern] = $callback;
    }

    public function run() {
        $uri=$_SERVER['REQUEST_URI'];
        foreach ($this->routes[$_SERVER['REQUEST_METHOD']] as $pattern => $callback) {
            if (preg_match($pattern, $uri, $params) === 1) {
                    return call_user_func($callback, new Request($params));
            }
        }
    }
}
<?php
class Request {

    private $params  =   [];
    private $uri;

    public function __construct(array $params)
    {
        $this->uri=$params[0];
        array_shift($params);
        $this->params=array_values($params);
    }

    public function getUri() {
        return $this->uri;
    }
    public function getParams() {
        return $this->params;
    }
    public function getData() {
        switch($_SERVER['REQUEST_METHOD']){
            case 'GET':$data=$_GET;break;
            case 'POST':$data=$_POST;break;
            case 'PUT':
                parse_str(file_get_contents("php://input"),$data);
                /*
                //Or do it per http://php.net/manual/en/features.file-upload.put-method.php?
                $putdata = fopen("php://input", "r");
                // Read the data 1 KB at a time and write to a stream
                while ($data = fread($putdata, 1024)) {
                fwrite($fp, $data);
                }
                fclose($fp);
                */
                break;
            case 'DELETE':
                //Can a delete method have data?  Is it the same as $_GET?
                $data=$_GET;
                break;
        }
        return $data;
    }
}

 

Link to comment
Share on other sites

Either use it

 

$router->get('/^\/test\/?$/', function(Request $request, Response $response) use ($router){
    var_dump($request); var_dump($response);
});
or have your router pass it into the callback as a parameter.

$router->get('/^\/test\/?$/', function(Request $request, Response $response, Router $router){
    var_dump($request); var_dump($response);
});
if (preg_match($pattern, $uri, $params) === 1) {
    return call_user_func($callback, new Request($params), $this);
}

On another note, your callback examples show them taking a Response parameter, but your router code provides no such parameter, only a Request. Either add a response parameter to your call_user_func code or remove it from your callback parameter lists.

Link to comment
Share on other sites

Thanks kicken,  I had tried your second solution but tried to assign the passed $this to variable name $this in the callback, but as I am sure you know, it wouldn't work.  How do you think Slim allows $this to be used in the callback?

 

Yes, I know about the missing Response parameter.  Going back and forth on whether I should include it, and got side tracked on $this issue.

 

PS.  Sorry for the poor post title.  Just realized now that it was only "Access".  Not what I meant to use.  If any mod can change it, please do to something like "Accessing $this in a callback".

 

Thanks!

 

https://www.slimframework.com/docs/objects/router.html#how-to-create-routes
Closure binding

If you use a Closure instance as the route callback, the closure’s state is bound to the Container instance. This means you will have access to the DI container instance inside of the Closure via the $this keyword:

$app = new \Slim\App();
$app->get('/hello/{name}', function ($request, $response, $args) {
// Use app HTTP cookie service
$this->get('cookies')->set('name', [
'value' => $args['name'],
'expires' => '7 days'
]);
});

 

 

Link to comment
Share on other sites

You can use Closure::call (7+) or Closure::bindTo (5.4+) to execute a closure with a specific $this value.

if (preg_match($pattern, $uri, $params) === 1) {
    if ($callback instancof \Closure){
        return $closure->call($this, new Request($params));
    } else {
        return call_user_func($callback, new Request($params), $this);
    }
}
I'm generally not a fan of such shenanigans because it can lead to confusion.

<?php

class Router {
    private $routeList;
    public function addRoute($url, $callback){
        $this->routeList[$url] = $callback;
    }
    
    public function route($url){
        $this->routeList[$url]->call($this, $url);
    }
}

class HomeController {
   public function __construct($router){
       $router->addRoute('/', function(){
           return $this->doHomePage();
       });
   }
   
   private function doHomePage(){
       $quote = $this->getRandomQuote();
       return new Response('home.html', ['quote' => $quote]);
   }
   
   private function getRandomQuote(){
       $quotes = ['You can do anything, but not everything.—David Allen', ' The richest man is not he who has the most, but he who needs the least. —Unknown Author','You miss 100 percent of the shots you never take.—Wayne Gretzky'];
       return $quotes[array_rand($quotes)];
   }
}

class Response {
    public function __construct($template, $data){
    
    }
}

$router = new Router();
$controller = new HomeController($router);

$router->route('/');
Fatal error: Uncaught Error: Call to undefined method Router::doHomePage() in /in/WlsRe:17
Looking just at the HomeController class there's no reason why it shouldn't work. the doHomePage method is clearly defined and everyone knows that $this is a reference to the current object, so WTF?!.

 

It's not until you dig into the code for Router (or read docs if they exist) that you realize that it's changing what $this is.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.