I have this class for test propose that is loaded using FastRoute on the same page where the library is loaded:
class Controller {
public function demo()
{
echo 'Hello world';
}
}
Every time I access the root of my project which is a mapped route, I get this strange error
Deprecated: Non-static method Controller::demo() should not be called statically
I can't understand why the call:user_func_array() is thinking that the method is static.
here is the test code for routing. Any suggestion is appreciated
<?php
require_once __DIR__.'/vendor/autoload.php';
use FastRoute\RouteCollector;
use FastRoute\simpleDispatcher;
use FastRoute\Dispatcher;
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r){
// test route
$r->addRoute('GET', '/', 'Controller#demo');
});
// Fetch method and URI from somewhere
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
// Strip query string (?foo=bar) and decode URI
if (false !== $pos = strpos($uri, '?')) {
$uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
case FastRoute\Dispatcher::NOT_FOUND:
// ... 404 Not Found
break;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
$allowedMethods = $routeInfo[1];
// ... 405 Method Not Allowed
break;
case FastRoute\Dispatcher::FOUND:
$handler = $routeInfo[1];
$vars = $routeInfo[2];
list($class, $method) = explode("#", $handler, 2);
call_user_func_array([$class, $method], $vars);
break;
}
?>
Is because you're not specifying a class instance (object) but the class itself.
Try to instantiate a new object with new $class().
Related
I am trying to load (include file) the GetRiskSummaryCommandHandler.php (GetRiskSummary\CommandHandler) at runtime dynamically, while resolving Route api/risks to HandleCommand (of class CommandHandler) method.
How can I do this? If not can I modify any taken approach including modifying autoloader?
My api class snippet looks like this:
API.php
<?php
abstract class API
{
public function processRequest()
{
$id1 = $this->requestObj->id1;
//$id2 = $this->requestObj->id2;
$endpoint1 = $this->requestObj->endpoint1;
$endpoint2 = $this->requestObj->endpoint2;
$isDestination = in_array($id1, ['first', 'prev', 'next', 'last']);
$numSetEndpoints = (int)isset($endpoint1) + (int)isset($endpoint2);
switch($numSetEndpoints)
{
case 0:
if ($isDestination)
return json_decode($this->_response("No Endpoint: ", $endpoint1));
return json_decode($this->_response("ProjectAIM API"));
case 1:
$className = $endpoint1.'Controller';
break;
case 2:
$className = $endpoint2.'Controller';
break;
}
$class = "GetRiskSummaryCommandHandler";
$method = "HandleCommand";
if (class_exists($class))
{
if (method_exists($class, $method))
{
$response = (new $class($this->requestObj))->{$method}($this->requestObj);
if ($response['Succeeded'] == false)
{
return $response['Result'];
}
else if ($response['Succeeded'] == true)
{
header("Content-Type: application/json");
return $this->_response($response);
}
else if ($response['Result'])
{
header("Content-Type: text/html");
return $this->_response($response);
}
}
}
}
**Command Handler Snippet, uses a Route Attiribute
GetRiskSummaryCommandHandler.php
<?php
namespace GetRiskSummary;
use Infrastructure\CommandHandler;
class GetRiskSummaryCommandHandler extends CommandHandler
{
#[Route("/api/risks", methods: ["GET"])]
public function HandleCommand()
{
$qb = $this->getEntityManager()
->createQueryBuilder();
$qb->select('*')
->from('Risks', 'Risks')
->orderBy('RiskID', 'DESC');
$query = $qb->getQuery();
return $query->getResult();
}
}
Autoloader.php
<?php
namespace Autoloader;
class Autoloader
{
private static function rglob($pattern, $flags = 0) {
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge($files, self::rglob($dir.'/'.basename($pattern), $flags));
}
return $files;
}
public static function ClassLoader($path)
{
$pathParts = explode("\\", $path);
$path = $pathParts[array_key_last($pathParts)];
$matches = self::rglob("*/$path*");
foreach ($matches as $name)
{
$filePath = realpath($name);
if (file_exists($filePath))
include $filePath;
}
}
}
spl_autoload_register("AutoLoader\AutoLoader::ClassLoader");
I think the biggest missing feature of PHP Attributes would be that as far as I'm aware PHP Attributes are static and aren't evaluated at runtime. They are just glorified comments.
If it was true, we could do some truly amazing things like non-intrusively attaching pre/post-processing functions, just like what you wrote.
They work just like in symfony or doctrine. You can't use variables in them and you must write your own code to search your php class tree for attributes in the code and use php reflection to obtain the information and run the attributes.
To be honest it's pretty disappointing, although maybe it's just the first step that will be fixed in later versions of PHP.
But for now, I think what you're asking for is impossible. Because they are not running dynamically like you appear to be asking.
I have a string, route, and i explode it and then i have a classname. $callback = explode('#', $callback); $callback[0] is the classname. I am making a router and based on the class and methed i return this.
I want to get the full namespace and when i use this then it works but now i want to make it dynamically.
How do i concat BaseController to the ::class?
BasController::class
$fullclass = HERE I NEED THE FULL NAMESPACE$callback[0];
$class = new $fullclass;
$method = $callback[1];
$class->$method();
Exaple index.php
Router::route('/user/{id}/' , 'BaseController#show');
Router::execute($_SERVER['REQUEST_URI']);
Router:
namespace App;
class Router
{
private static $routes = array();
private function __construct()
{
}
private function __clone()
{
}
public static function route($pattern, $callback)
{
$pattern = $pattern;
self::$routes[$pattern] = $callback;
}
public static function execute($url)
{
foreach (self::$routes as $pattern => $callback) {
if ($pattern == $url) {
$callback = explode('#', $callback);
$fullclass = __NAMESPACE__ . '\\Controllers\\' . $callback[0];
$class = new $fullclass;
$method = $callback[1];
$class->$method();
}else{
echo 404;
}
}
}
}
BasController::class returns a full class name in string in that case you can simply concat any string on it. If $callback[0] is a string you can do below code.
$fullclass = BasController::class . $callback[0];
$class = new $fullclass;
$method = $callback[1];
$class->$method();
I hope you are trying to retrieve namespace
echo (new \ReflectionClass($object))->getNamespaceName() ;
If you are in that scope try
__NAMESAPCE__
See this document if you are looking for autoload
What i was looking for is giving the class as param into the route
Router::route('/user/{id}/' , App\Controllers\BaseController::class , 'show');
I'm having a few problems calling a function from a string, as its a lot more complicated than just calling a function, I need to do it inside another class, with another namespace.
I dispatch my routes using this method, I am using TreeRoute package on GitHub.
public function dispatch() {
$uri = substr($_SERVER['REQUEST_URI'], strlen(implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1)) . '/'));
$method = $_SERVER['REQUEST_METHOD'];
$result = $this->router->dispatch($method, $uri);
if (!isset($result['error'])) {
$handler = $result['handler'];
$params = $result['params'];
// TODO: Call the handler here, but how?
}
else {
switch ($result['error']['code']) {
case 404 :
echo 'Not found handler here';
break;
case 405 :
echo 'Method not allowed handler here';
$allowedMethods = $result['allowed'];
if ($method == 'OPTIONS') {
// OPTIONS method handler here
}
break;
}
}
}
I register routes like this:
public function __construct() {
$this->router = new \TreeRoute\Router();
}
public function setRoutes() {
$this->router->addRoute('GET', '/test', 'App/Controllers/SomeController/test');
}
What I want to do is call function 'test' in class 'SomeController', now 'SomeController has a namespace of 'App\Controllers'.
I looked into calluserfunc but I couldn't work out how to do it with my string format and with a namespace, can someone help me out?
http://php.net/manual/en/function.call-user-func.php
You can call call_user_func with full class path like this:
call_user_func('App\Controllers\SomeController::test', $arg1, $arg2)
// or
call_user_func(['App\Controllers\SomeController', 'test'], $arg1, $arg2)
// or better
use App\Controllers\SomeController;
call_user_func([SomeController::class, 'test'], $arg1, $args2);
I believe this will do the job
if (!isset($result['error'])) {
$handler = $result['handler'];
$params = $result['params'];
$method = substr( strrchr($handler, '/'), 1 ); // get the method's name from $handler string
$class = substr($handler, 0, -strlen($method)-1); // get the class name from $handler string
$class = str_replace('/', '\\', $class); // change slashes for anti-slashes
// then choose one of those :
// if your methods are static :
call_user_func_array([$class, $method], $params);
// otherwise :
(new $class)->{$method}(...$params); // needs php 7
}
I'm trying to implement Simple MVC Framework http://simplemvcframework.com/ and am going through the code line by line in the index.php file (https://github.com/simple-mvc-framework/v2/blob/master/index.php) and I've come across the following 2 lines..
//define routes
Router::any('', '\controllers\welcome#index');
Router::any('/subpage', '\controllers\welcome#subpage');
I understand :: is a scope resolution operator, and would think that Router::any() would be referencing a static method called any() in the Router class... however no such method exists... https://github.com/simple-mvc-framework/v2/blob/master/app/core/router.php. Though all other static method calls mentioned in the index.php file DO exist.
I thought maybe this was some sort of reserved name of a PHP function, but of course as you could imagine, searching for "PHP Any function" or similar searches in google doesn't come back with too many helpful results. My other thought is maybe this is just a implementation of static calls that I'm not familiar with?
I know this is a very specific question, but I'm trying to make sure I understand as much as possible with this framework and PHP in general before going too much futher.
Here is how it works.
It utilizes __callStatic magic method in PHP.
When a static call is made using Class/Object, and if the magic method is defined in the class, and if the called static function doesn't exist then this method is invoked.
If we dig deeper into the code,
public static function __callstatic($method, $params){
$uri = dirname($_SERVER['PHP_SELF']).'/'.$params[0];
$callback = $params[1];
array_push(self::$routes, $uri);
array_push(self::$methods, strtoupper($method));
array_push(self::$callbacks, $callback);
}
The method parameter which is any in our case is store as uppercase (ANY) with a callback.
When a request is made, dispatch function is called.
public static function dispatch(){
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
$searches = array_keys(static::$patterns);
$replaces = array_values(static::$patterns);
self::$routes = str_replace('//','/',self::$routes);
$found_route = false;
// parse query parameters
{
$query = '';
$q_arr = array();
if(strpos($uri, '&') > 0) {
$query = substr($uri, strpos($uri, '&') + 1);
$uri = substr($uri, 0, strpos($uri, '&'));
$q_arr = explode('&', $query);
foreach($q_arr as $q) {
$qobj = explode('=', $q);
$q_arr[] = array($qobj[0] => $qobj[1]);
if(!isset($_GET[$qobj[0]]))
{
$_GET[$qobj[0]] = $qobj[1];
}
}
}
}
// check if route is defined without regex
if (in_array($uri, self::$routes)) {
$route_pos = array_keys(self::$routes, $uri);
// foreach route position
foreach ($route_pos as $route) {
if (self::$methods[$route] == $method || self::$methods[$route] == 'ANY') {
$found_route = true;
//if route is not an object
if(!is_object(self::$callbacks[$route])){
//call object controller and method
self::invokeObject(self::$callbacks[$route]);
if (self::$halts) return;
} else {
//call closure
call_user_func(self::$callbacks[$route]);
if (self::$halts) return;
}
}
}
// end foreach
} else {
// check if defined with regex
$pos = 0;
// foreach routes
foreach (self::$routes as $route) {
$route = str_replace('//','/',$route);
if (strpos($route, ':') !== false) {
$route = str_replace($searches, $replaces, $route);
}
if (preg_match('#^' . $route . '$#', $uri, $matched)) {
if (self::$methods[$pos] == $method || self::$methods[$pos] == 'ANY') {
$found_route = true;
//remove $matched[0] as [1] is the first parameter.
array_shift($matched);
if(!is_object(self::$callbacks[$pos])){
//call object controller and method
self::invokeObject(self::$callbacks[$pos],$matched);
if (self::$halts) return;
} else {
//call closure
call_user_func_array(self::$callbacks[$pos], $matched);
if (self::$halts) return;
}
}
}
$pos++;
}
// end foreach
}
if (self::$fallback) {
//call the auto dispatch method
$found_route = self::autoDispatch();
}
// run the error callback if the route was not found
if (!$found_route) {
if (!self::$error_callback) {
self::$error_callback = function() {
header("{$_SERVER['SERVER_PROTOCOL']} 404 Not Found");
echo '404';
};
}
if(!is_object(self::$error_callback)){
//call object controller and method
self::invokeObject(self::$error_callback,null,'No routes found.');
if (self::$halts) return;
} else {
call_user_func(self::$error_callback);
if (self::$halts) return;
}
}
}
}
If you look deeply into the dispatch function, you will clearly see that, there are several lines containing:
if (self::$methods[$route] == $method || self::$methods[$route] == 'ANY')
This helps routing the request to defined callbacks based on the methods supplied including ANY method.
It's not that any() is a reserved method, it's that the class is using overloading to call that method. Look at this code for a second
/**
* Defines a route w/ callback and method
*
* #param string $method
* #param array #params
*/
public static function __callstatic($method, $params){
$uri = dirname($_SERVER['PHP_SELF']).'/'.$params[0];
$callback = $params[1];
array_push(self::$routes, $uri);
array_push(self::$methods, strtoupper($method));
array_push(self::$callbacks, $callback);
}
When any() is called, PHP first checks for that method being defined directly. Since it's not, it then calls this overloading magic method, which then executes the call.
There are few routers out there but I decided to create a very simple route for a very light site.
Here is my index.php
$route = new Route();
$route->add('/', 'Home');
$route->add('/about', 'About');
$route->add('/contact', 'Contact');
Here is my router:
<?php namespace Laws\Route;
use Laws\Controller\Home;
class Route
{
private $_uri = array();
private $_method = array();
private $_route;
public function __construct()
{
}
public function add($uri, $method = null)
{
$this->_uri[] = '/' . trim($uri, '/');
if ($method != null) {
$this->_method[] = $method;
}
}
public function submit()
{
$uriGetParam = isset($_GET['uri']) ? '/' . $_GET['uri'] : '/';
foreach ($this->_uri as $key => $value) {
if (preg_match("#^$value$#", $uriGetParam)) {
$useMethod = $this->_method[$key];
new $useMethod(); // this returns an error (cannot find Home'
new Home(); // this actually works.
}
}
}
}
new $useMethod(); does not work. returns error 'cannot find Home'
new Home(); actually works.
What am I missing here?
You can use your concurrent way for calling a class or you can use this:
call_user_func(array($classname,$methodname))