2009-11-30 15 views
12

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

Contexto

Tenemos una clase de enrutamiento en el local URL que nos gusta mucho. La forma en que funciona hoy en día es que primero tenemos que registrar todas las rutas con un gestor de ruta central, así:

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

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

Busco algún método para poner la ruta más cerca del método, ya que podría haber hecho en C#:

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

Mis opciones como las veo ahora, son o bien para crear una especie de phpDoc extensión que me permite algo como esto:

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

Pero eso requeriría la escritura/reutilización de un analizador de phpDoc, y lo más probable es que sea bastante lento.

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

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

Sin embargo, esto todavía requeriría registrar cada clase única, y no habría un gran número de de clases como esta (por no 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?

Respuesta

10

Así es como terminé resolviendo esto. El article provided by Kevin fue de gran ayuda. Al utilizar ReflectionClass y ReflectionMethod :: getDocComment, puedo recorrer los comentarios de phpDoc con mucha facilidad. Una expresión regular pequeña encuentra @route y está registrada 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é el acabado lista de rutas en Memcached, por lo que la reflexión es innecesaria en cada carga de página. En total, pasamos de tomar 7 ms para registrar las rutas a 1,7 ms en promedio cuando se almacena en caché (reflejo de cada carga de página utilizada 18ms en promedio.

El código para hacer esto, que se puede anular en una subclase si que necesita el 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 un montón de buenas sugerencias aquí!Fuimos con 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; 
    } 
} 
+0

Muy bueno. Me interesaste en hacer algo similar a esto para proyectos futuros. – Kevin

+0

¡Gracias por destacar este punto! de mucha ayuda – Hilmi

5

Uso de PHP 5.3, se puede utilizar cierres o "Anonymous functions" para atar el código para 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! 
?> 
+0

No puedo esperar hasta que pueda utilizar esta función en producción, pero en este momento la mayor parte del tiempo ponerse a trabajar con alojamientos compartidos que solo son compatibles con las versiones 5.1.xo 5.2.x. –

+0

Las funciones anónimas son muy interesantes, pero lo que más me preocupa es la legibilidad. Imagine tener 50 de estas rutas registradas una después de la otra. Sin embargo, este es absolutamente el tipo de respuesta que estamos buscando, y me gusta el enfoque. :) –

+0

A falta de analizar phpDoc liderando una función o evaluando código de un archivo XML, no puedo ver un método PHP puro que sea claramente legible; sin ninguna basura adicional para leer. Siempre puede hacer que el enrutador sea estático y ajustar la llamada 'Router :: RegisterRoute' en una función' route() 'independiente. Debería hacerlo un poco más legible. Probablemente incluso más que su ejemplo de C# :-) – Atli

1

que haría uso de una combinación de interfaces y una clase Singleton para registrar rutas sobre la marcha.

Usaría la convención de 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 ruta

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

Un método 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 av 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 Router Manager

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 el mismo registro de superclase

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, el auto registro clase de router

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"; 
    } 
} 
+0

Me gusta su uso de la variable 'self :: $ register', que puede usarse durante el registro. Sin embargo, tener el método en sí mismo dos cosas separadas no es realmente deseable, y podría reducir la legibilidad del código. Aún así, esto hace exactamente lo que pedí; mantenga la ruta cerca de la implementación del método ... –

+0

Bueno, si lo desea, puede anular getRoutes y devolver una matriz que cree manualmente. Esto mantiene la definición de ruta dentro de la misma clase, aunque no dentro de la función. Y estoy seguro de que hay otras posibilidades también. –

1

lo más cerca que puede poner su camino hacia la la definición de la función (mi humilde opinión) está justo antes de la definición de la clase. por lo que tendría

$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() {} 
} 
+0

... esto está muy cerca de lo que estoy haciendo en este momento. Sin embargo, considere que podría haber 50 rutas allí, y algunos de los métodos son 10-20 líneas. La distancia es entonces demasiado larga, de acuerdo con mi definición ... –

+0

, así que quieres un código justo antes de la definición de la función que registraría esa función en alguna url. pero dado que eso es parte de la clase, no hay forma de ejecutar ese código. para que pueda hacer esto, o alguna función registerRoutes() dentro de cada clase que sea realmente incómoda, o simplemente use rutas como/clase/función - de esta manera no tiene que registrar nada solo aplique alguna validación para verificar qué funciones pueden ser llamado directamente así. – jab11

2

Aquí es 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, 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)); 
     } 
    } 
} 
+0

Esto está muy cerca de lo que tenemos actualmente. El "problema" es como antes, que la ruta real puede estar separada de la implementación del código, y la legibilidad sufre. Es bueno ver que alguien más pensó similar a lo que hicimos, aunque ... :) –

+2

Jajaja, bueno. El otro pensamiento que tuve fue utilizar algo como AttributeReader (http://interfacelab.com/metadataattributes-in-php/) y crear un script de línea de comando para almacenar en caché todas las rutas en un único archivo. Puede alcanzar el equilibrio entre la velocidad y la legibilidad, aunque debe ejecutar el script cada vez que cambie una ruta ... – Kevin

+0

El artículo al que se ha vinculado es increíble; si lo hubiera encontrado yo mismo, es posible que no haya planteado la pregunta. Dependiendo de cómo se desempeñe, podría terminar utilizándolo para analizar phpDoc-comments como se describe en mi pregunta, y encontrar alguna forma de almacenarlo en caché, así no tengo que usar el reflejo en cada carga de página. Gran descubrimiento! :) –

Cuestiones relacionadas