是否可以在PHP中使用.NET方法属性的等价物,或以某种方式模拟这些?

<强>上下文

我们有一个我们非常喜欢的内部URL路由类。它今天的工作方式是我们首先必须使用中央路由管理器注册所有路由,如下所示:

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

每当遇到路由时,都会调用回调方法(在上面的情况下,它们是静态类方法)。但是,这会将路径与方法分开,至少在代码中是这样。

我正在寻找一些方法让路线更接近方法,就像你在C#中所做的那样:

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

我现在看到的选项是要么创建某种phpDoc扩展,允许我这样:

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

但是这需要为phpDoc编写/重用解析器,并且很可能会很慢。

另一种选择是将每条路线分成它自己的类,并使用如下方法:

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

然而,这仍然需要注册每一个类,并且会有很多这样的类(更不用说额外代码的数量)。

那么我的选择是什么?保持路线接近它调用的方法的最佳方法是什么?

有帮助吗?

解决方案

这就是我最终解决这个问题的方法。 Kevin提供的文章是一个巨大的帮助。通过使用ReflectionClass和ReflectionMethod :: getDocComment,我可以非常轻松地浏览phpDoc注释。一个小的正则表达式找到任何@route,并在方法中注册。

反射并不那么快(在我们的例子中,在单独的函数中对RegiserRoute进行硬编码调用的速度大约是2.5倍),并且因为我们有很多路由,所以我们必须缓存完成的列表Memcached中的路由,因此每次加载页面都不需要反射。总的来说,当缓存时,我们最终从7ms开始将路由注册到1,7ms(每页加载的反射平均使用18ms。

如果您需要手动注册,可以在子类中重写的代码如下:

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

感谢大家指点我正确的方向,这里有很多好建议!我们采用这种方法只是因为它允许我们保持路由接近它调用的代码:

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

其他提示

使用PHP 5.3,您可以使用闭包 <!> quot;匿名函数<!> quot; 将代码绑定到路由。

例如:

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

这是一种可以满足您需求的方法。每个包含路由的类必须实现一个接口,然后循环遍历所有定义的类,这些类实现该接口以收集路由列表。该接口包含一个方法,该方法需要返回一组UrlRoute对象。然后使用现有的URL路由类注册它们。

编辑:我只是想,UrlRoute类可能还应该包含ClassName的字段。然后$oRouteManager->RegisterRoute($urlRoute->route, array($className, $urlRoute->method))可以简化为$oRouteManager->RegisterRoute($urlRoute)。但是,这需要更改现有框架......

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

我会使用接口和单例类的组合来动态注册路由。

我会使用命名路由器类的约定,如FirstRouter,SecondRouter等。这样可以实现:

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

这将使用我的路由器管理器注册所有声明的类。

这是调用路由方法的代码

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

路由器方法看起来像这样

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

接口

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

这是我的定义路线

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

路由器管理器

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

自注册超类

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

最后是自注册路由器类

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

最接近你可以把你的路径放到函数定义(IMHO)就在类定义之前。所以你会有

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

$oRouteManager->RegisterRoute('admin/test/', array('CAdmin', 'SomeMethod'));
$oRouteManager->RegisterRoute('admin/foo/', array('CAdmin', 'SomeOtherMethod'));
class CAdmin {
    public static function SomeMethod() {}
    public static function SomeOtherMethod() {}
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top