尊重/rest-fut

RESTful应用的轻量级控制器

维护者

详细信息

github.com/brenodouglas/Rest

来源

安装: 88

依赖: 0

建议者: 0

安全: 0

星星: 0

监视者: 1

分支: 102

0.5.1 2012-09-16 23:16 UTC

README

RESTful应用和API的轻量级控制器。

  • 非常轻量。
  • 不要尝试更改PHP,学习曲线小。
  • 完全RESTful,构建应用的正确方式。

安装

在PEAR和Composer上提供包。自动加载兼容PSR-0

功能指南

配置

引导很容易。只需创建一个Respect\Rest\Router实例。

<?php

use Respect\Rest\Router;

$r3 = new Router;

这假设你有一个.htaccess文件,将每个请求重定向到这个PHP文件,并且你从域名根目录运行它(例如http://example.com/没有子文件夹)。

如果你想从子文件夹使用它,你可以将虚拟根传递给Router

$r3 = new Router('/myapp');

这将指示路由器从http://example.com/myapp/工作。

你也可以在没有.htaccess文件的情况下使用Router。这使用CGI的PATH_INFO变量,可以声明为

$r3 = new Router('/index.php/');

也使用文件夹

$r3 = new Router('/myapp/index.php/');

这假设项目中每个URL都以这些命名空间开始。

调度

路由器是自动调度的,这意味着你不必调用任何其他东西,只需声明路由即可运行。如果你想忽略这种行为,你可以设置

$r3->isAutoDispatched = false;

然后你可以在过程结束时自己调度它

print $r3->run();

如果你想打印输出或存储在变量中,也可以。这允许你更好地测试和将路由器集成到现有应用程序中。

简单路由

“你好,世界”路由类似于这样

$r3->get('/', function() {
    return 'Hello World';
});

访问http://localhost/(考虑你的本地配置)将在浏览器上打印“你好,世界”。你可以声明任意多的路由

$r3->get('/hello', function() {
    return 'Hello from Path';
});

现在,访问http://localhost/hello将打印“Hello from Path”。

使用参数

你可以声明通过URL接收参数的路由。为此,每个参数都是路由路径上的/*。考虑前面的示例模型

$r3->get('/users/*', function($screenName) {
    echo "User {$screenName}";
});

使用任何用户名而不是alganet访问http://localhost/users/alganet将现在打印“User alganet”(或你传递给它的任何用户名)。

可以定义多个参数

$r3->get('/users/*/lists/*', function($user, $list) {
    return "List {$list} from user {$user}.";
});

默认情况下,路由路径上的最后一个参数是可选的,所以只需声明一个->get('/posts/*'就会匹配http://localhost/posts/而不带任何参数。然后可以声明一个第二个->get('/posts',然后路由器将正确匹配它,或者通过在传递给函数时使它们成为可空的来自己处理丢失的参数

$r3->get('/posts/*/*/*', function($year,$month=null,$day=null) {
    //list posts, month and day are optional
});
  1. 这将匹配/posts/2010/10/10, /posts/2011/01 和/posts/2010
  2. 仅在路由路径的末尾允许可选参数。这不允许可选参数:/posts/*/*/*/comments/*

通配符参数

有时你需要捕获一个未定义数量的参数。你可以使用像这样的带有通配符参数的路由

$r3->get('/users/*/documents/**', function($user, $documentPath) {
    return readfile(PATH_STORAGE. implode('/', $documentPath));
});
  1. 上面的示例将匹配/users/alganet/documents/foo/bar/baz/anything。回调参数$user将接收alganet,$documentPath将接收一个包含[foo,bar,baz,anything]的数组。
  2. 通配符参数由双星号/**定义。
  3. 通配符参数必须仅出现在路径末尾。路径中任何其他位置的双星号将被转换为单星号。
  4. 通配符参数将在匹配相同模式的其他任何路由之后进行匹配。

路由匹配

现在事情变得更加复杂。我们有了简单路由、带参数的路由、可选参数和通配符参数。要记住的一个简单规则是,Respect\Rest从最具体的路由匹配到最通用的路由。

  • 带有最多斜杠(/)的路由更具体,并首先进行匹配。
  • 带有参数的路由比不带参数的路由不那么具体。
  • 带有多个参数的路由甚至不那么具体。
  • 带有通配符参数的路由是最通用的。

总结:斜杠和星号的位置越靠前,越先进行匹配。

Respect\Rest会自动这样做,但强烈建议从最具体的路由到最通用的路由声明路由。这将提高代码的性能和可维护性。

匹配任何HTTP方法

有时您需要使用路由器将请求代理到其他路由器或将请求映射到类。使用any魔法方法,您可以传递任何方法到给定的函数。

$r3->any('/users/*', function($userName) {
    //do anything
});
  1. 任何HTTP方法都会匹配这个相同的路由。
  2. 您可以使用标准的PHP $_SERVER['REQUEST_METHOD']来找出方法。

类控制器

any非常有助于将类绑定到控制器,这是Respect\Rest最酷的功能之一

use Respect\Rest\Routable;

class MyArticle implements Routable {
    public function get($id) { }
    public function delete($id) { }
    public function put($id) { }
}

$r3->any('/article/*', 'MyArticle');
  1. 上面的代码将类方法绑定到使用相同路径的HTTP方法。
  2. 参数将被发送到类方法,就像其他示例中的回调一样。
  3. 控制器是懒加载和持久化的。当路由匹配其方法之一时,将实例化MyArticle类,并且这个实例将在其他请求(重定向等)中重用。
  4. 出于安全原因,类必须实现Respect\Rest\Routable接口。(想象一下有人自动将HTTP映射到PDO类,这就不对了)。

向类传递构造函数参数也是可能的

$r3->any('/images/*', 'ImageController', array($myImageHandler, $myDb));
  1. 这将把$myImageHandler$myDb作为参数传递给ImageController类的构造函数。

如果您想的话,也可以自己实例化这个类

$r3->any('/downloads/*', $myDownloadManager);
  1. 上面的示例将现有的$myDownloadManager分配为控制器。
  2. Respect\Rest也将重用这个实例

您甚至可以使用工厂或DI容器来构建控制器类

$r3->any('/downloads/*', 'MyControllerClass', array('Factory', 'getController'));
  1. 上面的示例将使用Factory::getController返回的MyController类
  2. Respect\Rest也将重用这个实例
  3. 第三个参数是任何callable变量,所以您可以在那里放置一个闭包来构建实例,如果您想的话。

路由流

有时您需要将用户路由到流。路由器不需要处理大文件或等待流完成才开始提供服务。

$r3->get('/images/*/hi-res', function($imageName) {
    header('Content-type: image/jpg');
    return fopen("/path/to/hi/images/{$imageName}.jpg", 'r');
});

这将直接将文件重定向到浏览器,而不将其保存在内存中。

注意:我们在示例中做了一件非常错误的事情:直接将参数传递给fopen句柄。在使用参数之前请验证它。这只是一个示范。

路由静态值

这里没有秘密,您可以让路由返回一个纯字符串

$r3->get('/greetings', 'Hello!');

转发路由

Respect\Rest使用不同的方法来验证路由参数

$usersRoute = $r3->any('/users', 'UsersController');

只有当when上的回调匹配时,才会匹配路由。

$r3->any('/premium', function($user) use ($db, $usersRoute) {
    if (!$db->userPremium($user)) {
      return $usersRoute;
    }
});

上面的说明性示例将在用户不是高级用户时内部重定向到处理普通用户的另一个路由。

当路由(if)时

Respect\Rest使用不同的方法来验证路由参数

$r3->get('/documents/*', function($documentId) {
    //do something
})->when(function($documentId) {
    return is_numeric($documentId) && $documentId > 0;
});
  1. 只有当when上的回调匹配时,才会匹配路由。
  2. $documentId参数必须在操作和条件(但不需要以相同的顺序出现)中具有相同的名称。
  3. 您可以为每个条件回调指定多个参数。
  4. 您可以指定多个回调: when($cb1)->when($cb2)->when($etc)
  5. 条件也将与绑定类和实例方法上的参数同步。

这使得用户可以使用任何自定义程序验证参数,而不仅仅是像 intstring 这样的数据类型。

我们强烈建议您在使用此功能时使用强大的验证库。考虑使用 Respect\Validation

$r3->get('/images/*/hi-res', function($imageName) {
    header('Content-type: image/jpg');
    return fopen("/path/to/hi/images/{$imageName}.jpg", 'r');
})->when(function($imageName) {
    // Using Respect Validation alias to `V`
    return V::alphanum(".")->length(5,155)
            ->noWhitespace()->validate($imageName);
});

通过常规(之前)

有时您需要在路由执行其工作之前运行某些操作。这对于日志记录、身份验证和类似目的非常有用。

$r3->get('/artists/*/albums/*', function($artistName, $albumName) {
    //do something
})->by(function($albumName) use ($myLogger) {
    $myLogger->logAlbumVisit($albumName);
});
  1. 这将执行在 by 上定义的回调,在路由操作之前。路由需要匹配。
  2. 参数也通过名称同步,而不是顺序,如 when
  3. 您可以为每个代理回调指定多个参数。
  4. 您可以为多个代理指定多个代理: by($cb1)->by($cb2)->by($etc)
  5. 代理中的 return false 将停止执行后续代理和路由操作。
  6. 代理也将与绑定类和实例方法上的参数同步。

如果您的 By 程序返回 false,则将不会处理路由方法/函数。如果您返回另一个路由的实例,则将执行内部转发。

通过常规(之后)

类似于 ->by,但在路由完成后运行。以下示例显示了在保存一些新信息后清除缓存的行为。

$r3->post('/artists/*/albums/*', function($artistName, $albumName) {
    //save some artist info
})->through(function() use($myCache) {
    $myCache->clear($artistName, $albumName);
});
  1. by 代理将在路由操作之前执行,through proxies 将在之后执行。
  2. 您不需要同时使用它们。
  3. through 也可以按名称接收参数。

上面的示例允许您基于路由参数执行某些操作,但在路由运行之后处理某些内容时,还希望处理其输出。这可以通过嵌套闭包实现。

$r3->any('/settings', 'SetingsController')->through(function(){
    return function($data) {
        if (isset($settings['admin_user'])) {
            unset($settings['admin_user']);
        }
        return $data;
    };
});

上面的示例在输出结果之前从设置控制器中删除敏感密钥。

控制器拆分

鼓励使用常规将控制器逻辑拆分为组件。您可以使用它们

$logRoutine = function() use ($myLogger, $r3) {
    $myLogger->logVisit($r3->request->path);
};

$r3->any('/users', 'UsersController')->by($logRoutine);
$r3->any('/products', 'ProductsController')->by($logRoutine);

将常规应用于路由器上的每个路由的简单方法是

$r3->always('By', $logRoutine);

您可以使用参数同步来利用这一点

$r3->always('When', function($user=null) {
    if ($user) {
      return strlen($user) > 3;
    }
});

$r3->any('/products', function () { /***/ });
$r3->any('/users/*', function ($user) { /***/ });
$r3->any('/users/*/products', function ($user) { /***/ });
$r3->any('/listeners/*', function ($user) { /***/ });

由于有三个带有 $user 参数的路由,when 将自动通过名称验证它们。

内容协商

Respect 目前支持四种不同的内容协商类型:Mimetype、Encoding、Language 和 Charset。使用示例

$r3->get('/about', function() {
    return array('v' => 2.0);
})->acceptLanguage(array(
    'en' => function($data) { return array("Version" => $data['v']); },
    'pt' => function($data) { return array("Versão"  => $data['v']); }
))->accept(array(
    'text/html' => function($data) {
        list($k,$v)=each($data);
        return "<strong>$k</strong>: $v";
    },
    'application/json' => 'json_encode'
));

与每个常规一样,conneg 常规将以您附加到路由的顺序执行。您也可以使用 ->always 将此常规应用于路由器上的每个路由。

请注意,当返回流时,conneg 常规也会被调用。您可以利用此处理流。以下示例直接使用 deflate 编码向浏览器提供文本

$r3->get('/text/*', function($filename) {
  return fopen('data/'.$filename, 'r+');
})->acceptEncoding(array(
    'deflate' => function($stream) {
        stream_filter_append($stream, 'zlib.deflate', STREAM_FILTER_READ);
        return $stream; //now deflated on demand 
    }
));

当将 conneg 常规应用于多个可以返回流的路由时,您(真的)应该在执行任何操作之前检查 is_resource()

基本 HTTP 认证

对基本 HTTP 认证的支持已作为常规实现

$r3->get('/home', 'HomeController')->authBasic('My Realm', function($user, $pass) {
    return $user === 'admin' && $user === 'p4ss';
}); 

您将收到用户提供的用户名和密码,您只需返回 true 或 false。true 表示用户可以认证。

Respect\Rest 将处理认证流程,在未认证时发送适当的头。您还可以返回另一个路由,它将作为内部转发(参见上面的部分)。

浏览器过滤

以下是说明如何阻止来自移动设备请求的示例

$r3->get('/videos/*', 'VideosController')->userAgent(array(
    'iphone|android' => function(){
        header('HTTP/1.1 403 Forbidden');
        return false; //do not process the route.
    }
));

您可以在数组中传递多个项目,就像任何conneg例程一样。数组键是一个没有分隔符的正则表达式匹配器。

输入Content-Type

请注意,此功能目前尚未实现。

默认情况下,HTML表单以multipart/form-data发送POST数据,但API客户端可以发送任何其他格式。PUT请求通常发送其他MIME类型。您可以在进行任何操作之前预处理这些数据。

$r3->post('/timeline', function() {
    return file_get_contents('php://input');
})->contentType(array(
    'multipart/form-data' => function($input) {
        parse_str($input, $output);
        return $output;
    },
    'application/json' => function($input) {
        return my_json_converter($input);
    },
    'text/xml' => function($input) {
        return my_xml_converter($input);
    },
));

HTTP错误

Respect\Rest当前默认实现以下错误

  • 404,当找不到匹配的路径时。
  • 401,当客户端使用authBasic例程向路由发送未认证的请求时。
  • 405,当找到匹配的路径但没有匹配的方法时。
  • 400,当when验证失败时。
  • 406,当路由路径和方法匹配,但内容协商不匹配时。

RESTful附加功能

  • HEAD请求会自动发送所有GET头部信息而不带主体。您可以通过声明自定义head路由来覆盖此行为。
  • *或任何路由路径发送OPTIONS请求会正确返回Allow头部。
  • 当返回405时,Allow头部也会被正确设置。

反模式

  • 您可以通过设置$r3->methodOverriding = true来允许在URI上使用?_method=ANYMETHOD来覆盖默认的HTTP方法。默认情况下为false

您自己的例程

例程是位于Respect\Rest\Routines命名空间中的类,但您可以通过实例使用来添加您自己的例程

$r3->get('/greetings', 'Hello World')->appendRoutine(new MyRoutine);

在上面的示例中,MyRoutine是一个提供用户例程,声明为类,并附加到路由器上。自定义例程有几种不同的接口可以实现

  • IgnorableFileExtension - 指示路由器在请求中忽略文件扩展名
  • ParamSynced - 同步参数与路由函数/方法。
  • ProxyableBy - 指示路由器在路由之前运行方法by()
  • ProxyableThrough - 指示路由器在路由之后运行方法through()
  • ProxyableWhen - 指示路由器运行方法when()来验证路由匹配。
  • Unique - 使得当为同一类型声明多个时,此例程将被替换而不是附加。

您可以使用上述任何组合,并且您还需要实现Routinable。