metanull / restful-service
RESTfulService 提供了实现 RESTful 网络服务所需的功能。
README
为了使用此类创建 RESTful Web 服务,我们需要
- 一个应用程序类
- 一些请求处理器(或路由)
- 在应用程序中注册我们的处理器
- 运行应用程序
以下是一个简单的示例实现。
导入库
- 如果尚未完成,请安装 composer
- 为您的项目创建一个目录,在该目录中
- 安装 MetaNull\RESTfulService 包:
composer require metanull/restful-service
- 创建以下文件
应用程序类
这是应用程序的核心。它负责
- 初始化系统,通过实例化一个
RESTfulService
对象 - 注册应用程序的请求处理器
- 调用
RESTfulService
的Serve()
方法 - 注意:
Serve()
查找请求的合适处理器并调用它。
./Application/Application.php
<?php
namespace Application;
use MetaNull\RESTfulService\Network\Http\Http;
use MetaNull\RESTfulService\Network\Http\RESTfulService;
use MetaNull\RESTfulService\Application\ApplicationInterface;
/** The RESTfulService application */
class Application implements ApplicationInterface
{
public ?RESTfulService $service = null;
public function __construct()
{
$this->service = new RESTfulService(); // Create the RESTfulService
}
public function Run()
{
// Add some headers that will be returned with any response
$this->service->serviceData['ExtraHeaders'][Http::HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = Http::ALLOW_ORIGIN_ANY;
$this->service->serviceData['ExtraHeaders'][Http::HEADER_VARY] = Http::VARY_ORIGIN;
// Add request handlers - in order of precedence
$this->service->Route(Routes\Root::class, '/'); // Handler for requests to "/"
$this->service->Route(Routes\Item::class, '%^/item/([\d+])$%'); // Handler fro requests matching the provided perl regexp
$this->service->Route(Routes\Fallback::class); // Handler for anything else
// Handle the received request
$this->service->Serve();
}
}
请求处理器类
处理 "GET /"
首先我们想要处理任何到达我们 API 根目录的 GET
请求(例如:/
)。
为了被识别为 GET
请求的处理器,我们的处理器应实现 Get
接口。
所有基本请求处理器类和接口都实现了实现 RouteHandlerInterface
。它定义了一个机制,通过该机制应用程序可以 询问 处理器是否能够处理给定的路由。您可以定义自己的路由处理器类,但系统附带了一些预定义的。
在这种情况下,我们将扩展 StringMatchRouteHandler
类。这是一个将 route
与字符串进行比较的处理器。如果它们严格相等,则处理器可以处理请求。
注意:该字符串的值可以在类本身中定义(使用 SpecializedRouteHandler::SetExpression()
方法),但将所有 '路由' 定义在应用程序初始化函数中更为灵活且易于阅读。
以下是如何在 Application
类的 Run()
方法中完成的
// Registers our 'Root' handler for requests to `/`.
$this->service->Route('\Application\Routes\Root', '/');
./Application/Routes/Root.php
<?php
namespace Application\Routes;
use MetaNull\RESTfulService\Network\Http\Get;
use MetaNull\RESTfulService\Network\Http\StringMatchRouteHandler;
use MetaNull\RESTfulService\Network\Http\Request;
use MetaNull\RESTfulService\Network\Http\Response;
use MetaNull\RESTfulService\Network\Http\ResponseFactory;
class Root extends StringMatchRouteHandler implements Get
{
/**
* A RequestHandler provides a method to handle a HTTP Request, this processing produces a HTTP Response
* @param Request $request The received HTTP request
* @param array $serviceData Data shared by the controller
* @param string $routeParameters,... May receive parameters that were extracted from the Request's Route)
* @return Response The resulting HTTP Response
* @throws HttpException
*/
public function Handle(Request $request, array &$serviceData, string ...$routeParameters) : Response
{
$response = ResponseFactory::Json([
'message' => 'It works!',
'handler' => __METHOD__,
]);
return $response;
}
}
处理 "GET /Item/{itemId}"
与前面的示例类似,这次我们的处理器需要支持一些参数。
它应接收一个项目的 ID,并返回匹配的项目。
我们可以通过扩展 RegexpMatchRouteHandler
类来实现这一点。与字符串比较不同,这个类将使用 PERL 正则表达式对路由进行正则表达式匹配。如果路由匹配,则处理器处理请求。如果正则表达式提取了一些信息,则提取的值将自动通过处理器 Handle()
方法的第三个参数传递给我们的处理器: string ...$routeParameters
。
在 Application
类的 Run()
方法中,我们使用以下方式注册了我们的处理器
// Registers our 'Item' handler for requests to `/item/{itemId}`.
// itemId is expected to be composed of one or more digits.
$this->service->Route('\Application\Routes\Item', '%^/item/([\d+])$%');
表达式 %^/item/(\d+)$%
将从路由中提取 exactly one 组: (\d+)
。 注意: \d+
代表一系列 1 或多个连续的数字。
我们的处理器将接收提取的值作为 $routeParameters[0]
./Application/Routes/Item.php
// Registers our 'Item' handler for requests to `/item/{itemId}`.
// itemId is expected to be composed of one or more digits.
$this->service->Route('\Application\Routes\Item', '%^/item/(\d+)$%');
<?php
namespace Application\Routes;
use MetaNull\RESTfulService\Network\Http\Get;
use MetaNull\RESTfulService\Network\Http\RegexpMatchRouteHandler;
use MetaNull\RESTfulService\Network\Http\Request;
use MetaNull\RESTfulService\Network\Http\Response;
use MetaNull\RESTfulService\Network\Http\ResponseFactory;
class Item extends RegexpMatchRouteHandler implements Get
{
/**
* A RequestHandler provides a method to handle a HTTP Request, this processing produces a HTTP Response
* @param Request $request The received HTTP request
* @param array $serviceData Data shared by the controller
* @param string $routeParameters,... May receive parameters that were extracted from the Request's Route)
* @return Response The resulting HTTP Response
* @throws HttpException
*/
public function Handle(Request $request, array &$serviceData, string ...$routeParameters) : Response
{
dd($request->route);
$response = ResponseFactory::Json([
'message' => 'It works!',
'item_id' => $routeParameters[0] ?? null; // The ItemId received from the route.
'handler' => __METHOD__,
]);
return $response;
}
}
处理 "任何" 其他 GET 请求
最后,我们希望拦截任何未被我们的处理器处理的请求。这是通过扩展 AnyRouteHandler
类来实现的。
与前面的两个例子不同,AnyRouteHandler 不需要检查是否可以处理该路由。它将简单地处理 所有 请求。正因为如此,定义此路由时,将其放在最后非常重要,因为处理器的评估顺序是它们定义的顺序。
如果这个路由首先被定义,它将 吞没 它看到的任何 GET
请求。
以下是如何通过我们的 Application
类的 Run()
方法注册处理器的
// Registers our 'Fallback' handler for any other request.
$this->service->Route('\Application\Routes\Fallback');
./Application/Routes/Fallback.php
<?php
namespace Application\Routes;
use MetaNull\RESTfulService\Network\Http\Get;
use MetaNull\RESTfulService\Network\Http\AnyRouteHandler;
use MetaNull\RESTfulService\Network\Http\Request;
use MetaNull\RESTfulService\Network\Http\Response;
use MetaNull\RESTfulService\Network\Http\ResponseFactory;
class Fallback extends AnyRouteHandler implements Get
{
/**
* A RequestHandler provides a method to handle a HTTP Request, this processing produces a HTTP Response
* @param Request $request The received HTTP request
* @param array $serviceData Data shared by the controller
* @param string $routeParameters,... May receive parameters that were extracted from the Request's Route)
* @return Response The resulting HTTP Response
* @throws HttpException
*/
public function Handle(Request $request, array &$serviceData, string ...$routeParameters) : Response
{
$response = ResponseFactory::Json([
'message' => 'It works!',
'handler' => __METHOD__,
]);
return $response;
}
}
我们还需要什么?
一个引导文件
引导是应用程序的起点。它负责设置自动加载器,并调用应用程序的主方法。
./bootstrap.php
<?php
// Your autoloader (see below)
require_once __DIR__ . '/autoload.php';
// Composer autoloader
require_once __DIR__ . '/vendor/autoload.php';
use Application\Application;
$app = new Application();
$app->Run();
为我们的类提供自动加载器
自动加载器能够加载 '你的' 应用程序类(定义在 '/Application' 目录中)。
./autoload.php
<?php
function autoload($class)
{
// Define an array of namespace prefixes and their corresponding base directory paths
$prefixes = [
'Application\\' => __DIR__ . '/Application',
];
// Iterate over the prefixes and try to match the class with the corresponding file path
foreach ($prefixes as $prefix => $baseDir) {
// Check if the class uses the current namespace prefix
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
continue;
}
// Remove the namespace prefix and convert the namespace separators to directory separators
$relativeClass = substr($class, $len);
$relativeClass = str_replace('\\', '/', $relativeClass);
// Construct the file path by appending the base directory path and the relative class name with '.php' extension
$file = $baseDir . '/' . $relativeClass . '.php';
// Require the file if it exists
if (file_exists($file)) {
require_once $file;
}
}
}
// Register the autoloader function with spl_autoload_register()
spl_autoload_register('autoload');
在 Web 服务器上发布我们的应用程序代码
此文档的范围不包含 Web 服务器的配置。但作为一个例子,以下是如何使用 Apache 2.4 进行配置。
将以下 .htaccess
文件复制到我们的应用程序根目录,将指示 Apache 将所有请求重写(透明地)传递到我们的 bootstrap.php
。
例如,如果服务器收到对 http://my.domain.com/directory/do/something
的请求,它将重写为 /directory/bootstrap.php?route=/do/something
。
注意: route
是 RESTfulService 框架内部使用的参数。该服务捕获该参数,并通过 Request
对象将其提供给所有请求处理器。
public function Handle(Request $request, array &$serviceData, string ...$routeParameters) : Response
{
error_log($request->route); // Prints the received route into the php log file
}
./.htaccess
# If you are using Apache 2.4, and do not have access to the config files, you
# may copy this .htaccess file to the root of your directory.
#
# It is required that administrators of the server permit loading .htaccess
# file from you application's directory. This is typically achieved by adding:
# AllowOverride FileInfo
# in the Directory configuration.
# Modify /directory/ to match with the path (relative to the root) of your
# server. Note that the trailing slash is important!
RewriteBase /directory/
# Rewrite all requests so that they are "passed" to the bootstrap function
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^bootstrap\.php$ "bootstrap.php?route=/" [L,QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ "bootstrap.php?route=/$1" [L,DPI,QSA]
</IfModule>