Route control (URL) in PHP to execute API requests

2

I've been thinking for a while now about the best way to solve this and I do not come up with a satisfactory solution.

I would like to exercise strict control over the routes that are introduced in an API that I am designing in my domain.

There are several entry points, for example:

  • When this URL is written I want to offer general information about the padres module:

    http://www.example.com/padres
    
  • When writing this URL I want to offer specific information about padre with id equal to 1 :

    http://www.example.com/padres/1
    
  • When this URL is written I want to offer general information about the colecciones module:

    http://www.example.com/padres/colecciones
    
  • When I write this URL I want to offer specific information about coleccion with id equal to 1 :

    http://www.example.com/padres/colecciones/1
    

I am capturing what is entered in the URL with this code:

$requestUri=$_SERVER['REQUEST_URI'];
$arrURL = explode("/",trim($requestUri,'/'));  

For example, for the last cited URL, the value of $arrURL would be:

Array
(
    [0] => padres
    [1] => colecciones
    [2] => 1
)

What I would like is to be able to control the content of $arrURL to, based on it, make the requests of place to the database or raise Exceptions if there are misspelled URLs.

If for example a user types this:

http://www.example.com/padres/colecciones/1/ddhtfgdgj

I would have this array, which I must identify with an invalid request:

Array
(
    [0] => padres
    [1] => colecciones
    [2] => 1
    [3] => ddhtfgdgj
)

I have tried to create an array that contains the entry points, for example:

$arrRecursos=array('padres','colecciones');

and trying to verify if these entry points are in the variable $arrURL , but it is quite tedious, especially detect possible errors that the user can write and identify the type of request that I must execute. p>

I have tried to control that in $arrURL there are no more than 3 elements (in that case all requests would be invalid), but I find the problem that both in the general request of colecciones and in the request of a padre in specific the array would have the same number of elements and I can not find the way to properly manage the resources with the respective number that has been introduced.

I would like to know if there is any easy and effective way to control URLs and launch requests according to their content.

  • Note: I would like to do it in pure PHP, without having to resort to a framework for it. I'm in a domain with shared hosting, I do not want to have to load it with frameworks which I also do not know if they would be compatible with my hosting provider.
asked by A. Cedano 24.11.2017 в 16:17
source

2 answers

2

A rudimentary solution but one that could work is to adopt the convention that

  • the first element is mapped to a controller
  • the second one method
  • the rest are arguments

Let's say you have the controller padresController.php with a static method ver . If ver does not receive arguments, it displays general info. If you receive a numeric argument, use it as $ id, if the argument is non-numeric it returns an error, and if it receives more than one parameter, another error.

class padresController {
  public static function ver($id=null) {
       if (count(func_get_args())>1) {
         echo 'este endpoint solo acepta un parámetro';
       } else if($id===null) {
         echo 'info general sobre padres';
       } else if (is_int($id)) {
         echo 'info sobre padre '.$id;
       } else {
         echo 'peticion invalida';
       }
  }
}

You can use a function that takes REQUEST_URI without domain ( /padres/ver/1 ) and returns an array containing: [$controller, $metodo, $argumentos] ;

function url_to_args($request_uri) {
    $arrURL = explode("/",trim($request_uri,'/'));  
    $argumentos = [];
    foreach ($arrURL as $key => $val) {
        if ($key == 0 || $key == 1)    {
            // no hago nada porque no son argumentos
        }     else    {
            $argumentos[$key] = $val;
        }
    }
    $controller=$arrURL[0].'Controller';
    $metodo=$arrURL[1];
    return [$controller, $metodo, $argumentos];
}

The route

http://www.example.com/padres/ver/1

I should tell you to call padresController::ver(1) and that's what you do with:

list($controller, $metodo, $argumentos) = url_to_args('/padres/ver/1');
call_user_func_array(array($controller, $metodo), $argumentos);

The validation of the controller and the method can be done with class_exists and method_exists . The rest of the validations you would have to do them within each method analyzing the arguments that the static function received.

You can also use non-static functions if instead of the name of the controller you return an instance of it.

Working example

    
answered by 24.11.2017 в 16:57
0

This is a mission for regex !!!

$url   = 'http://www.example.com/padres/colecciones/1';
$regex = '~^https?://[^/]+/(?P<modulo>[-\p{L}]+)(?:/(?P<coleccion>[-\p{L}]+))?(?:/(?P<id>\d+))?/?$~i';

if (preg_match( $regex, $url, $matches)) {
    $modulo    = $matches['modulo'];
    $coleccion = isset($matches['coleccion']) ? $matches['coleccion'] : '';
    $id        = isset($matches['id']) ? $matches['id'] : '';
} else {
    // URL inválida
}

If it matches, the content of $matches is:

array (
  0 => 'http://www.example.com/padres/colecciones/1',
  'modulo' => 'padres',
  1 => 'padres',
  'coleccion' => 'colecciones',
  2 => 'colecciones',
  'id' => '1',
  3 => '1',
)

If a part is not present in the URL, $matches can contain the value '' (empty), or not have that key present (depending on whether it is the last group that did not match).

Demo: link


The regex explained: [regex101.com]

$regex = '~
         ^                               # inicio del texto
         https?://                       # http o https
         [^/]+                           # host: cualquier caracter excepto "/"

         /                               # barra
         (?P<modulo>  [-\p{L}]+  )       # Grupo 1 "modulo": guión, letras (y diacríticos)

         (?:                             # Opcional: (grupo sin captura)
             /                           #     barra
             (?P<coleccion> [-\p{L}]+ )  #     Grupo 2 "coleccion".
         )?                              # (fin del opcional)

         (?:                             # Opcional: (grupo sin captura)
             /                           #     barra
             (?P<id>  \d+  )             #     Grupo 3 "id": dígitos
         )?                              # (fin del opcional)

         /?                              # barra al final (opcional)
         $                               # fin del texto

         # Modificadores:
         # [i] ignorar may/min
         # [x] ignorar espacios y comentariosen este regex
         ~ix';

- You can use it like this, with comments in the code if you prefer.


The same can be done from the .htaccess if it is more practical:

RewriteRule ^([-\wáéíóúüñÁÉÍÓÚÜÑ]+)(?:/([-_a-záéíóúüñA-ZÁÉÍÓÚÜÑ]+))?(?:/(\d+))?/?$ index.php?modulo=$1&coleccion=$2&id=$3 [QSA]
    
answered by 28.01.2018 в 15:35