How to use external function in php route - php

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

Related

How to handle not found routes in my router

So my issue is, I have no idea how to handle page not found handling, Since routing runs every time a route is added if you do anything other than the first route it'll have 2 outputs.
Routes.php
Route::set('index.php', function() {
Index::CreateView('Index');
});
Route::set('test', function() {
Test::CreateView('Test');
});
?>
Routes.php (class)
<?php
class Route {
public static $validRoutes = array();
public static function set($route, $function) {
self::$validRoutes[] = $route;
$url = $_GET['url'];
if($url == $route) {
$function->__invoke();
die();
}
if(!in_array($url, self::$validRoutes)) {
Controller::CreateView("404");
die();
}
}
}
?>
I'm trying to understand how I'd even handle if its not found.
How about something like:
<?php
class Router
{
public $routes = [];
public function add($route, $function)
{
$this->routes[$route] = $function;
}
public function route($path)
{
$function =
$this->routes[$path] ??
$this->routes['404'] ?? null;
if(is_null($function))
http_response_code(404) && exit('404 Not Found.');
$function->__invoke();
}
}
$router = new Router;
$router->add('foo', function() {
echo 'bar';
});
$router->add('greeting', function() {
echo 'hello earth';
});
$router->route('greeting');
Output:
hello earth
Rather than trying to resolve your routes upon each addition, you can add them all, and then resolve/dispatch/route later.
I've simplified the Router::routes array to use the paths as keys.
When resolving, if the path cannot be found (the index does not exist) it will try and check for a '404' key in the routes array. Failing that it responds with a basic 404.

PHP: Calling another class function with namespace?

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
}

PHP - Generate Functions from array of string

In my application i need many getter and setter and my idea was to generate them from an array, for example:
protected $methods = ['name', 'city'];
With this two parameters, i will need to generate the following methods:
public function getNameAttribute() {
return $this->getName();
}
public function getName($lang = null) {
return $this->getEntityValue('name', $lang);
}
And for city, the method will be:
public function getCityAttribute() {
return $this->getCity();
}
public function getCity($lang = null) {
return $this->getEntityValue('city', $lang);
}
Sure, i should need to generate the setter too (with the same logic).
As you can see, i will need a method with get<variable_name>Attribute and inside this call get<variable_name> and the other (getName) return even the same method (for each getter) and just change the 'name' parameter.
Every method have the same logic and i would like to generate them "dynamically". I don't know if this is possible..
You can leverage __call() to do this. I'm not going to provide a full implementation but you basically want to do something like:
public function __call($name, $args) {
// Match the name from the format "get<name>Attribute" and extract <name>.
// Assert that <name> is in the $methods array.
// Use <name> to call a function like $this->{'get' . $name}().
// 2nd Alternative:
// Match the name from the format "get<name>" and extract <name>.
// Assert that <name> is in the $methods array.
// Use <name> to call a function like $this->getEntityValue($name, $args[0]);
}
Send this params(name, city or other) as parameter to universal method(if you don't know what params you can get)
public function getAttribute($value) {
return $this->get($value);
}
public function get($value, $lang = null) {
return $this->getEntityValue($value, $lang);
}
If you know yours parameters, you can use this:
public function getNameAttribute() {
return $this->getName();
}
$value = 'Name'; //for example
$methodName = 'get' . $value . 'Attribute';
$this->$methodName; //call "getNameAttribute"
Take a look at this and let me know is it your requirement or not.
$methods = ['name', 'city'];
$func = 'get'.$methods[1].'Attribute';
echo $func($methods[1]);
function getNameAttribute($func_name){
$another_func = 'get'.$func_name;
echo 'Name: '.$another_func();
}
function getCityAttribute($func_name){
$another_func = 'get'.$func_name;
echo 'City: '.$another_func();
}
function getCity(){
return 'Dhaka';
}
function getName(){
return 'Frayne';
}

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.

define anonymous function using values from current scope?

I am trying to create an anonymous function but need to access variables from the current scope in it's definition:
class test {
private $types = array('css' => array('folder' => 'css'));
public function __construct(){
//define our asset types
foreach($this->types as $name => $attrs){
$this->{$name} = function($file = ''){
//this line is where is falls over!
//undefined variable $attrs!
return '<link href="'.$attrs['folder'].'/'.$file.'" />';
}
}
}
}
$assets = new test();
Obviously this example is very very minimalistic but it gets across what I am trying to do.
So, my question is,
How can I access the parent scope only for the definition of the function?
(once defined I obviously don't need that context when the function is called).
Edit #1
Ok so after using Matthew's answer I have added use as below; but now my issue is that when I call the function I get no output.
If i add a die('called') in the function then that is produced, but not if I echo or return something.
class test {
private $types = array('css' => array('folder' => 'css'));
public function __construct(){
//define our asset types
foreach($this->types as $name => $attrs){
$this->{$name} = function($file = '') use ($attrs){
//this line is where is falls over!
//undefined variable $attrs!
return '<link href="'.$attrs['folder'].'/'.$file.'" />';
}
}
}
public function __call($method, $args)
{
if (isset($this->$method) === true) {
$func = $this->$method;
//tried with and without "return"
return $func($args);
}
}
}
$assets = new test();
echo 'output: '.$assets->css('lol.css');
function($file = '') use ($attrs)

Categories