Подтвердить что ты не робот

Атрибуты метода Faking в PHP?

Можно ли использовать эквивалент для атрибутов метода .NET в PHP или каким-то образом имитировать эти?

Контекст

У нас есть собственный класс маршрутизации URL-адресов, который нам очень нравится. То, как он работает сегодня, состоит в том, что мы сначала должны зарегистрировать все маршруты с помощью центрального диспетчера маршрутов, например:

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

Всякий раз, когда встречается маршрут, вызывается метод обратного вызова (в случаях, указанных выше, это статические методы класса). Однако это отделяет маршрут от метода, по крайней мере, в коде.

Я ищу некоторый метод, чтобы поместить маршрут ближе к методу, как вы могли бы сделать в С#:

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

Однако это все равно потребует регистрации каждого отдельного класса, и было бы так много таких классов (не говоря уже о количестве дополнительного кода).

Итак, какие у меня варианты? Каким будет лучший способ сохранить маршрут близко к методу, который он вызывает?

4b9b3361

Ответ 1

Вот как я решил это решить. статья, предоставленная Кевином, была огромной помощью. Используя ReflectionClass и ReflectionMethod:: getDocComment, я могу легко просмотреть комментарии phpDoc. Небольшое регулярное выражение находит любой @route и регистрируется в методе.

Отражение не так быстро (в нашем случае примерно в 2,5 раза медленнее, чем с жестко запрограммированными вызовами в RegiserRoute в отдельной функции), и поскольку у нас много маршрутов, нам пришлось кэшировать готовый список маршрутов в Memcached, поэтому отражение не нужно при каждой загрузке страницы. В итоге мы закончили переход от 7 мс для регистрации маршрутов до 1,7 мс в среднем при кэшировании (отражение на каждой загрузке страницы в среднем составляло 18 мс.

Код для этого, который может быть переопределен в подклассе, если вам нужна ручная регистрация, выглядит следующим образом:

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

Ответ 2

Используя PHP 5.3, вы можете использовать закрытие или "Anonymous functions" , чтобы привязать код к маршруту.

Например:

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

Ответ 3

Вот способ, который может удовлетворить ваши потребности. Каждый класс, содержащий маршруты, должен реализовывать интерфейс, а затем более поздний цикл через все определенные классы, которые реализуют этот интерфейс для сбора списка маршрутов. Интерфейс содержит единственный метод, который ожидает возврата массива объектов 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));  
        }
    }
}

Ответ 4

Я бы использовал комбинацию интерфейсов и одноэлементного класса для регистрации маршрутов на лету.

Я бы использовал соглашение об именах классов маршрутизаторов, таких как 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();
}

И это мое определение av route

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

Ответ 5

ближайший путь к определению функции (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() {}
}