Pregunta

¿Es posible usar el equivalente para los atributos del método .NET en PHP, o de alguna manera simularlos?

Context

Tenemos una clase interna de enrutamiento de URL que nos gusta mucho. La forma en que funciona hoy es que primero tenemos que registrar todas las rutas con un administrador de rutas central, de esta manera:

$oRouteManager->RegisterRoute('admin/test/', array('CAdmin', 'SomeMethod'));
$oRouteManager->RegisterRoute('admin/foo/', array('CAdmin', 'SomeOtherMethod'));
$oRouteManager->RegisterRoute('test/', array('CTest', 'SomeMethod'));

Cada vez que se encuentra una ruta, se llama al método de devolución de llamada (en los casos anteriores son métodos de clase estática). Sin embargo, esto separa la ruta del método, al menos en código.

Estoy buscando algún método para acercar la ruta al método, como podría haber hecho en C #:

<Route Path="admin/test/">
public static void SomeMethod() { /* implementation */ }

Mis opciones tal como las veo ahora son crear algún tipo de extensión phpDoc que me permita hacer algo como esto:

/**
 * @route admin/test/
 */
public static function SomeMethod() { /* implementation */ }

Pero eso requeriría escribir / reutilizar un analizador para phpDoc, y probablemente será bastante lento.

La otra opción sería separar cada ruta en su propia clase y tener métodos como los siguientes:

class CAdminTest extends CRoute
{
    public static function Invoke() { /* implementation */ }
    public static function GetRoute() { return "admin/test/"; }
}

Sin embargo, esto aún requeriría registrar cada clase individual, y habría una gran cantidad de clases como esta (sin mencionar la cantidad de código adicional).

¿Cuáles son mis opciones aquí? ¿Cuál sería la mejor manera de mantener la ruta cerca del método que invoca?

¿Fue útil?

Solución

Así es como terminé resolviendo esto. El artículo proporcionado por Kevin fue de gran ayuda. Al usar ReflectionClass y ReflectionMethod :: getDocComment, puedo recorrer los comentarios de phpDoc con mucha facilidad. Una pequeña expresión regular encuentra cualquier @route y se registra en el método.

La reflexión no es tan rápida (en nuestro caso, aproximadamente 2,5 veces más lenta que tener llamadas codificadas a RegiserRoute en una función separada), y dado que tenemos muchas rutas, tuvimos que almacenar en caché la lista terminada de rutas en Memcached, por lo que la reflexión es innecesaria en cada carga de página. En total, terminamos pasando de 7 ms para registrar las rutas a 1,7 ms en promedio cuando se almacenaron en caché (la reflexión en cada carga de página usó 18 ms en promedio.

El código para hacer esto, que puede anularse en una subclase si necesita registro manual, es el siguiente:

public static function RegisterRoutes()
{
    $sClass = get_called_class(); // unavailable in PHP < 5.3.0
    $rflClass = new ReflectionClass($sClass);
    foreach ($rflClass->getMethods() as $rflMethod)
    {
        $sComment = $rflMethod->getDocComment();
        if (preg_match_all('%^\s*\*\s*@route\s+(?P<route>/?(?:[a-z0-9]+/?)+)\s*$%im', $sComment, $result, PREG_PATTERN_ORDER)) 
        {
            foreach ($result[1] as $sRoute)
            {
                $sMethod = $rflMethod->GetName();
                $oRouteManager->RegisterRoute($sRoute, array($sClass, $sMethod));
            }
        }
    }
}

¡Gracias a todos por señalarme en la dirección correcta, había muchas buenas sugerencias aquí! Seguimos este enfoque simplemente porque nos permite mantener la ruta cerca del código que invoca:

class CSomeRoutable extends CRoutable
{
    /**
     * @route /foo/bar
     * @route /for/baz
     */
    public static function SomeRoute($SomeUnsafeParameter)
    {
        // this is accessible through two different routes
        echo (int)$SomeUnsafeParameter;
    }
}

Otros consejos

Usando PHP 5.3, puede usar cierres o " Funciones anónimas " para vincular el código a la ruta.

Por ejemplo:

<?php
class Router
{
    protected $routes;
    public function __construct(){
        $this->routes = array();
    }

    public function RegisterRoute($route, $callback) {
       $this->routes[$route] = $callback;
    }

    public function CallRoute($route)
    {
        if(array_key_exists($route, $this->routes)) {
            $this->routes[$route]();
        }
    }
}


$router = new Router();

$router->RegisterRoute('admin/test/', function() {
    echo "Somebody called the Admin Test thingie!";
});

$router->CallRoute('admin/test/');
// Outputs: Somebody called the Admin Test thingie!
?>

Aquí hay un método que puede satisfacer sus necesidades. Cada clase que contiene rutas debe implementar una interfaz y luego recorrer todas las clases definidas que implementan esa interfaz para recopilar una lista de rutas. La interfaz contiene un único método que espera que se devuelva una matriz de objetos UrlRoute. Estos se registran utilizando su clase de enrutamiento de URL existente.

Editar: Estaba pensando que la clase UrlRoute probablemente también debería contener un campo para ClassName. Entonces $ oRouteManager- > RegisterRoute ($ urlRoute- > route, array ($ className, $ urlRoute- > method)) podría simplificarse a $ oRouteManager- > RegisterRoute ($ urlRoute) . Sin embargo, esto requeriría un cambio en su marco existente ...

interface IUrlRoute
{
    public static function GetRoutes();
}

class UrlRoute
{
    var $route;
    var $method;

    public function __construct($route, $method)
    {
        $this->route = $route;
        $this->method = $method;
    }
}

class Page1 implements IUrlRoute
{
    public static function GetRoutes()
    {
        return array(
            new UrlRoute('page1/test/', 'test')
        );
    }

    public function test()
    {
    }
}

class Page2 implements IUrlRoute
{
    public static function GetRoutes()
    {
        return array(
            new UrlRoute('page2/someroute/', 'test3'),
            new UrlRoute('page2/anotherpage/', 'anotherpage')
        );
    }

    public function test3()
    {
    }

    public function anotherpage()
    {
    }
}

$classes = get_declared_classes();
foreach($classes as $className)
{
    $c = new ReflectionClass($className);
    if( $c->implementsInterface('IUrlRoute') )
    {
        $fnRoute = $c->getMethod('GetRoutes');
        $listRoutes = $fnRoute->invoke(null);

        foreach($listRoutes as $urlRoute)
        {
            $oRouteManager->RegisterRoute($urlRoute->route, array($className, $urlRoute->method));  
        }
    }
}

Usaría una combinación de interfaces y una clase singleton para registrar rutas sobre la marcha.

Usaría una convención para nombrar las clases de enrutadores como FirstRouter, SecondRouter, etc. Esto permitiría que esto funcione:

foreach (get_declared_classes() as $class) {
    if (preg_match('/Router$/',$class)) {
    new $class;
    }
}

Eso registraría todas las clases declaradas con mi administrador de enrutadores.

Este es el código para llamar al método de ruta

$rm = routemgr::getInstance()->route('test/test');

Un método de enrutador se vería así

static public function testRoute() {
if (self::$register) {
    return 'test/test'; // path
}
echo "testRoute\n";
}

Las interfaces

interface getroutes {
    public function getRoutes();
}

interface router extends getroutes {
    public function route($path);
    public function match($path);
}

interface routes {
    public function getPath();
    public function getMethod();
}

Y esta es mi definición de una ruta

class route implements routes {
    public function getPath() {
    return $this->path;
    }
    public function setPath($path) {
    $this->path = $path;
    }
    public function getMethod() {
    return $this->method;
    }
    public function setMethod($class,$method) {
    $this->method = array($class,$method);
    return $this;
    }
    public function __construct($path,$method) {
    $this->path = $path;
    $this->method = $method;
    }
}

El administrador del enrutador

class routemgr implements router {
    private $routes;
    static private $instance;
    private function __construct() {
    }
    static public function getInstance() {
    if (!(self::$instance instanceof routemgr)) {
        self::$instance = new routemgr();
    }
    return self::$instance;
    }
    public function addRoute($object) {
    $this->routes[] = $object;
    }
    public function route($path) {
    foreach ($this->routes as $router) {
        if ($router->match($path)) {
        $router->route($path);
        }
    }
    }
    public function match($path) {
    foreach ($this->routes as $router) {
        if ($router->match($path)) {
        return true;
        }
    }
    }
    public function getRoutes() {
    foreach ($this->routes as $router) {
        foreach ($router->getRoutes() as $route) {
        $total[] = $route;
        }
    }
    return $total;
    }
}

Y la superclase de registro automático

class selfregister implements router {
    private $routes;
    static protected $register = true;
    public function getRoutes() {
    return $this->routes;
    }
    public function __construct() {
    self::$register = true;
    foreach (get_class_methods(get_class($this)) as $name) {
        if (preg_match('/Route$/',$name)) {
        $path = call_user_method($name, $this);
        if ($path) {
            $this->routes[] = new route($path,array(get_class($this),$name));
        }
        }
    }
    self::$register = false;
    routemgr::getInstance()->addRoute($this);
    }
    public function route($path) {
    foreach ($this->routes as $route) {
        if ($route->getPath() == $path) {
        call_user_func($route->getMethod());
        }
    }
    }
    public function match($path) {
    foreach ($this->routes as $route) {
        if ($route->getPath() == $path) {
        return true;
        }
    }
    }
}

Y finalmente la clase de enrutador de registro automático

class aRouter extends selfregister {
    static public function testRoute() {
    if (self::$register) {
        return 'test/test';
    }
    echo "testRoute\n";
    }
    static public function test2Route() {
    if (self::$register) {
        return 'test2/test';
    }
    echo "test2Route\n";
    }
}

lo más cerca que puede poner su ruta a la definición de función (en mi humilde opinión) es justo antes de la definición de clase. entonces tendrías

$oRouteManager->RegisterRoute('test/', array('CTest', 'SomeMethod'));
class CTest {
    public static function SomeMethod() {}
}

y

$oRouteManager->RegisterRoute('admin/test/', array('CAdmin', 'SomeMethod'));
$oRouteManager->RegisterRoute('admin/foo/', array('CAdmin', 'SomeOtherMethod'));
class CAdmin {
    public static function SomeMethod() {}
    public static function SomeOtherMethod() {}
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top