Question

Est-il possible d'utiliser l'équivalent pour les attributs de méthode .NET en PHP, ou en quelque sorte de les simuler?

Contexte

Nous avons une classe de routage d'URL interne que nous aimons beaucoup. La façon dont cela fonctionne aujourd'hui est que nous devons d'abord enregistrer toutes les routes avec un gestionnaire de routes central, comme suit:

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

Chaque fois qu'une route est rencontrée, la méthode de rappel (dans les cas précédents, il s'agit de méthodes de classe statiques) est appelée. Cependant, cela sépare la route de la méthode, au moins en code.

Je cherche une méthode pour rapprocher la route de la méthode, comme vous auriez pu le faire en C #:

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

Mes options, telles que je les vois maintenant, sont soit de créer une sorte d'extension phpDoc qui me permet quelque chose comme ceci:

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

Mais cela nécessiterait l'écriture / la réutilisation d'un analyseur syntaxique pour phpDoc, et sera probablement plutôt lent.

L’autre option consisterait à séparer chaque route dans sa propre classe et à utiliser des méthodes telles que:

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

Cependant, cela nécessiterait toujours d'enregistrer chaque classe et il y aurait un grand nombre de classes comme celle-ci (sans parler de la quantité de code supplémentaire).

Alors, quelles sont mes options ici? Quel serait le meilleur moyen de garder la route proche de la méthode qu’elle invoque?

Était-ce utile?

La solution

C’est ainsi que j’ai finalement résolu le problème. L'article fourni par Kevin était d'une aide précieuse. En utilisant ReflectionClass et ReflectionMethod :: getDocComment, je peux parcourir les commentaires phpDoc très facilement. Une petite expression régulière trouve tout @route et est enregistrée dans la méthode.

La réflexion n’est pas aussi rapide (dans notre cas, environ 2,5 fois plus lente que celle des appels codés en dur à RegiserRoute dans une fonction distincte), et comme nous avons beaucoup de routes, nous avons dû mettre en cache la liste finale. des itinéraires dans Memcached, la réflexion n’est donc pas nécessaire à chaque chargement de page. Au total, nous avons fini par prendre 7 ms pour enregistrer les itinéraires à 1,7 ms en moyenne après la mise en cache (la réflexion sur chaque chargement de page utilisait en moyenne 18 ms.

Le code permettant d'effectuer cette opération, qui peut être remplacé dans une sous-classe si vous avez besoin d'un enregistrement manuel, est le suivant:

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

Merci à tous de m'avoir orienté dans la bonne direction, il y avait beaucoup de bonnes suggestions ici! Nous avons choisi cette approche simplement parce que cela nous permet de garder la route proche du code qu’elle invoque:

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

Autres conseils

À l'aide de PHP 5.3, vous pouvez utiliser des fermetures ou des " fonctions anonymes " pour associer le code à la route.

Par exemple:

<?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!
?>

Voici une méthode qui peut répondre à vos besoins. Chaque classe contenant des itinéraires doit implémenter une interface, puis faire une boucle sur toutes les classes définies qui implémentent cette interface pour collecter une liste d'itinéraires. L'interface contient une seule méthode qui attend qu'un tableau d'objets UrlRoute soit renvoyé. Ceux-ci sont ensuite enregistrés à l'aide de votre classe de routage d'URL existante.

Éditer: Je pensais juste que la classe UrlRoute devrait probablement contenir aussi un champ pour ClassName. Ensuite, $ oRouteManager- > RegisterRoute ($ urlRoute- > route, array ($ className, $ urlRoute- & method)) pourrait être simplifié en $ oRouteManager- > RegisterRoute ($ urlRoute) . Cependant, cela nécessiterait une modification de votre cadre existant ...

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

J'utiliserais une combinaison d'interfaces et une classe singleton pour enregistrer des itinéraires à la volée.

J'utiliserais une convention pour nommer les classes de routeur telles que FirstRouter, SecondRouter, etc. Cela permettrait à cela de fonctionner:

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

Cela enregistrerait toutes les classes déclarées auprès de mon gestionnaire de routeur.

C’est le code pour appeler la méthode route

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

Une méthode de routeur ressemblerait à ceci

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

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

Et ceci est ma définition d'un itinéraire

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

Le gestionnaire de routeur

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

Et la super classe d'auto-enregistrement

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

Et enfin la classe de routeur auto-enregistrante

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

le plus proche, vous pouvez mettre votre chemin vers la définition de la fonction (IMHO) est juste avant la définition de la classe. donc vous auriez

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

et

$oRouteManager->RegisterRoute('admin/test/', array('CAdmin', 'SomeMethod'));
$oRouteManager->RegisterRoute('admin/foo/', array('CAdmin', 'SomeOtherMethod'));
class CAdmin {
    public static function SomeMethod() {}
    public static function SomeOtherMethod() {}
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top