PHP: Calling another class function with namespace? - php

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
}

Related

How to use external function in php route

I am building simple php route But I
I have the following, which is working
Route::add('/', function(){
echo 'Hello world message';
});
My challange is that... I want to be able to load the function directly from a controller which I am calling
use Controllers\LoginController;
Route::add('/', LoginController::login());
the login() is a function i created inside LoginController class
I get this error
<b>Warning</b>: call_user_func_array() expects parameter 1 to be a valid callback, no array or string given in <b>C:\xampp\htdocs\Config\Route.php</b> on line <b>75</b><br />
My Route.php
<?php
namespace Config;
class Route{ private static $routes = Array(); private static $pathNotFound = null; private static $methodNotAllowed = null;
public static function add($expression, $function, $method = 'get') {
array_push(self::$routes,Array(
'expression' => $expression,
'function' => $function,
'method' => $method
)); }
public static function pathNotFound($function){
self::$pathNotFound = $function; }
public static function methodNotAllowed($function){
self::$methodNotAllowed = $function; }
public static function run($basepath = '/')
{
// Parse current url
$parsed_url = parse_url($_SERVER['REQUEST_URI']);//Parse Uri
if(isset($parsed_url['path'])){
$path = $parsed_url['path'];
}else{
$path = '/';
}
// Get current request method
$method = $_SERVER['REQUEST_METHOD'];
$path_match_found = false;
$route_match_found = false;
foreach(self::$routes as $route){
// If the method matches check the path
// Add basepath to matching string
if($basepath!=''&&$basepath!='/'){
$route['expression'] = '('.$basepath.')'.$route['expression'];
}
// Add 'find string start' automatically
//$route['expression'] = '^'.$route['expression'];
// Add 'find string end' automatically
$route['expression'] = $route['expression'].'$';
//echo $path.'<br/>';
// Check path match
if(preg_match('#'.$route['expression'].'#',$path,$matches)){
$path_match_found = true;
// Check method match
if(strtolower($method) == strtolower($route['method'])){
array_shift($matches);// Always remove first element. This contains the whole string
if($basepath!=''&&$basepath!='/'){
array_shift($matches);// Remove basepath
}
call_user_func_array($route['function'], $matches);
$route_match_found = true;
// Do not check other routes
break;
}
}
}
// No matching route was found
if(!$route_match_found){
// But a matching path exists
if($path_match_found){
header("HTTP/1.0 405 Method Not Allowed");
if(self::$methodNotAllowed){
call_user_func_array(self::$methodNotAllowed, Array($path,$method));
}
}else{
header("HTTP/1.0 404 Not Found");
if(self::$pathNotFound){
call_user_func_array(self::$pathNotFound, Array($path));
}
}
}
}
}
Is there anyway i can make use of external function inside the route?
It wants a callback, so you need to pass a function itself. In your case using the parenthesis () invokes (calls) the function and returns the result of it instead of passing the function itself.
So here is a simple example of how you could do it:
Route::add('/', LoginController::login);
Wrapping it into a another function enables you to even pass arguments.
// shorthand function syntax since 7.4
Route::add('/', fn() => LoginController::login());
// example arguments (just a prototype)
Route::add('/', fn() => LoginController::login($username, $password));
or
Route::add('/', function() { LoginController::login() });
// example arguments (just a prototype)
Route::add('/', function() { LoginController::login($username, $password) });

call_user_func_array non static method call

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().

PHP get dynamically the namespace off a class by concating it

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');

use of class:any() in PHP, is ANY a reserved method?

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.

How to get controller and action name in zf2

in zf1, we can get controller and action name using
$controller = $this->getRequest()->getControllerName();
$action = $this->getRequest()->getActionName();
How we can achieve this in zf2?
UPDATE:
I tried to get them using
echo $this->getEvent()->getRouteMatch()->getParam('action', 'NA');
echo $this->getEvent()->getRouteMatch()->getParam('controller', 'NA');
But I am getting error
Fatal error: Call to a member function getParam() on a non-object
I like to get them in __construct() method;
Ideally I would like to check if there is no Action is defined it will execute noaction() method. I would check using php method method_exists.
Even simpler:
$controllerName =$this->params('controller');
$actionName = $this->params('action');
you can't access these variables in controller __construct() method, but you can access them in dispatch method and onDispatch method.
but if you would like to check whether action exist or not, in zf2 there is already a built in function for that notFoundAction as below
public function notFoundAction()
{
parent::notFoundAction();
$response = $this->getResponse();
$response->setStatusCode(200);
$response->setContent("Action not found");
return $response;
}
but if you still like to do it manually you can do this using dispatch methods as follow
namespace Mynamespace\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Stdlib\ResponseInterface as Response;
use Zend\Mvc\MvcEvent;
class IndexController extends AbstractActionController
{
public function __construct()
{
}
public function notFoundAction()
{
parent::notFoundAction();
$response = $this->getResponse();
$response->setStatusCode(200);
$response->setContent("Action not found");
return $response;
}
public function dispatch(Request $request, Response $response = null)
{
/*
* any customize code here
*/
return parent::dispatch($request, $response);
}
public function onDispatch(MvcEvent $e)
{
$action = $this->params('action');
//alertnatively
//$routeMatch = $e->getRouteMatch();
//$action = $routeMatch->getParam('action', 'not-found');
if(!method_exists(__Class__, $action."Action")){
$this->noaction();
}
return parent::onDispatch($e);
}
public function noaction()
{
echo 'action does not exits';
}
}
You will get module , controller and action name like this in Zf2 inside your controller...
$controllerClass = get_class($this);
$moduleNamespace = substr($controllerClass, 0, strpos($controllerClass, '\\'));
$tmp = substr($controllerClass, strrpos($controllerClass, '\\')+1 );
$controllerName = str_replace('Controller', "", $tmp);
//set 'variable' into layout...
$this->layout()->currentModuleName = strtolower($moduleNamespace);
$this->layout()->currentControllerName = strtolower($controllerName);
$this->layout()->currentActionName = $this->params('action');
$controllerName = strtolower(Zend_Controller_Front::getInstance()->getRequest()->getControllerName());

Categories