slim框架
直到几年前,使用框架开发PHP应用程序还是例外,而不是规则。 如今, CakePHP , Symfony , CodeIgniter和Zend Framework等框架已广泛用于PHP应用程序开发,每个框架都提供了独特的功能来简化和简化应用程序开发。
完整的框架通常是过大的。 考虑考虑对Web应用程序进行原型设计,创建快速且肮脏的CRUD前端或部署基本的REST API。 您可以使用传统框架来完成所有这些任务,但是学习和使用它所花费的时间和精力通常超过了收益。 现在考虑微型框架,这些微型框架可实现快速的Web应用程序开发和原型设计,而无需性能开销和成熟框架的学习曲线。
在本文中,我向您介绍Slim,这是一个PHP微框架,旨在快速开发Web应用程序和API。 不要被这个名字所迷惑:Slim带有一个复杂的URL路由器,并支持页面模板,flash消息,加密的cookie和中间件。 它也非常易于理解和使用,并且附带了出色的文档和热情的开发人员社区。
本文将引导您完成使用Slim构建简单的REST API的过程。 除了说明如何使用Slim的URL路由器实现四种基本的REST方法之外,它还演示了Slim的独特功能如何使添加API身份验证和多格式支持等高级功能变得容易。
首先,刷新对REST的理解,也称为代表性状态转移。 REST与SOAP的不同之处在于它基于资源和操作,而不是基于方法和数据类型。 资源只是URL,它引用要在其上执行操作的对象或实体(例如,/ users或/ photos),并且操作是四个HTTP动词之一:
为了更好地理解这一点,请考虑一个简单的示例。 假设您有一个文件共享应用程序,并且需要API方法供开发人员将新文件远程添加到应用程序数据存储中,或从应用程序数据存储中检索现有文件。 在REST方法下,您将公开URL端点(例如/ files),并检查用于访问此URL的HTTP方法以了解所需的操作。 例如,您将HTTP数据包POST到/ files以创建一个新文件,或向GET / files发送一个请求以检索可用文件的列表。
这种方法更容易理解,因为它将现有的HTTP动词映射到CRUD操作。 它还消耗更少的资源,因为不需要正式定义请求/响应头的数据类型。
URL请求的典型REST约定及其含义是:
Slim的URL路由器可帮助开发人员“将资源URI映射到特定HTTP请求方法的回调函数”,例如GET,POST,PUT和DELETE。 定义新的REST API非常简单,只需定义这些回调方法并用适当的代码填充它们即可。 这正是您在本文其余部分中将要做的。
在开始实施REST API之前,让我们回顾一些注意事项。 在整篇文章中,我假设您有一个正常的开发环境(Apache,PHP和MySQL),并且您熟悉SQL和XML的基础知识。 我还假定您的Apache Web服务器已配置为支持虚拟主机,URL重写以及PUT和DELETE请求。
REST API旨在与资源一起使用。 在这种情况下,资源是文章,每篇文章都有标题,URL,日期和唯一标识符。 这些资源存储在MySQL数据库中,本文开发的示例REST API允许开发人员使用常规REST约定检索,添加,删除和更新这些文章。 本文的大部分内容假设JSON请求和响应主体。 有关如何处理XML请求和响应的更多信息,请参阅本文后面的支持多响应格式 。
切换到Web服务器的文档根目录(在Linux®上通常为/ usr / local / apache / htdocs,在Windows®上通常为c:\ Program Files \ Apache \ htdocs),并为应用程序创建一个新的子目录。 将此目录命名为slim /。
shell> cd /usr/local/apache/htdocs
shell> mkdir slim
本文通篇将此目录称为$ SLIM_ROOT。
接下来,您需要添加Slim。 如果您使用的是PHP依赖性管理器Composer,只需在$ SLIM_ROOT / composer.json中创建一个文件,内容如下:
{
"require": {
"slim/slim": "2.*"
}
}
使用Composer通过以下命令安装Slim:
shell> php composer.phar install
要加载Slim,请将此行添加到应用程序的“ index.php”文件中:
如果不使用Composer,则可以手动下载Slim框架,并将下载档案中的Slim目录提取到PHP包含路径中的目录中,或者提取到$ SLIM_ROOT / Slim中。
下一步是初始化应用程序数据库。 创建一个名为“ articles”的新MySQL表来保存商品记录:
CREATE TABLE IF NOT EXISTS `articles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` text NOT NULL,
`url` text NOT NULL,
`date` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
给该表添加一些入门记录:
INSERT INTO `articles` (`id`, `title`, `url`, `date`) VALUES
(1, 'Search and integrate Google+ activity streams with PHP applications',
'http://www.ibm.com/developerworks/xml/library/x-googleplusphp/index.html', '2012-07-10');
INSERT INTO `articles` (`id`, `title`, `url`, `date`) VALUES
(2, 'Getting Started with Zend Server CE',
'http://devzone.zend.com/1389/getting-started-with-zend-server-ce/', '2009-03-02');
要与该MySQL数据库进行交互,您还应该下载RedBeanPHP,这是一种占用资源少的ORM库。 该库以单个文件形式提供,您可以轻松地将其包含在PHP脚本中。 切记将文件复制到PHP包含路径中的目录或$ SLIM_ROOT / RedBean中。
为了使访问应用程序更加容易,请定义一个新的虚拟主机并将其设置为工作目录。 建议您执行此可选步骤,特别是当您在拥有多个正在进行的应用程序的开发计算机上工作时,因为它会创建目标部署环境的更紧密的副本。 Slim还带有一个“ .htaccess”文件,使您可以使用漂亮的URL删除“ index.php”前缀。
要为应用程序设置命名的虚拟主机,请打开Apache配置文件(httpd.conf或httpd-vhosts.conf)并向其中添加以下行:
NameVirtualHost 127.0.0.1
DocumentRoot "/usr/local/apache/htdocs/slim"
ServerName example.localhost
这些行定义了一个新的虚拟主机http://example.localhost/,其文档根目录与$ SLIM_ROOT相对应。 重新启动Web服务器以激活这些新设置。 请注意,您可能需要更新网络的本地DNS服务器以告知其有关新主机的信息。
完成此任务后,将浏览器指向http://example.localhost/,您将看到类似图1的内容 。
Slim的工作原理是,只需通过调用相应的方法(用于GET请求的get(),用于POST请求的post()等)并将URL路由作为匹配的第一个参数传递,即可为HTTP方法和端点定义路由器回调。方法。 该方法的最后一个参数是一个函数,它指定当路由与传入请求匹配时要采取的操作。
典型的REST API支持两种类型的GET请求,一种用于资源列表(GET / articles),另一种用于特定资源(GET / articles / 123)。 让我们开始编写代码来处理第一种情况:用所有可用文章记录的列表响应GET / articles请求。
使用清单1中的代码更新$ SLIM / index.php文件:
get('/articles', function () use ($app) {
// query database for all articles
$articles = R::find('articles');
// send response header for JSON content type
$app->response()->header('Content-Type', 'application/json');
// return JSON-encoded response body with query results
echo json_encode(R::exportAll($articles));
});
// run
$app->run();
清单1加载了Slim和RedBean类。 它还会注册Slim自动加载器,以确保根据需要加载其他Slim类(请记住,如果您使用Composer,则不需要Slim或注册其自动加载器)。 接下来,RedBeanPHP的R::setup()
方法通过向其传递适当的凭据来打开与MySQL数据库的连接,其R::freeze()
方法针对任何RedBean传播的更改锁定数据库模式。 最后,初始化一个新的Slim应用程序对象; 该对象用作定义路由器回调的主要控制点。
下一步是为HTTP方法和端点定义路由器回调。 清单1调用了应用程序对象的get()
方法,并将URL路由'/ articles作为第一个参数传递给该对象。 作为最后一个参数传递的匿名函数使用RedBeanPHP的R::find()
方法从“文章”数据库表中检索所有记录,并使用R::exportAll()
方法将它们转换为PHP数组,然后将该数组返回到调用方作为JSON编码的响应主体。 Slim的响应对象还公开了header()
方法,您可以使用该方法设置任何响应头。 在清单1中 , header()
方法设置“ Content-Type”标头,以便客户端知道将其解释为JSON响应。
图2显示了对此端点的请求结果。
请注意,您可以使用$app->contentType()
帮助方法而不是header()
方法来直接访问请求的内容类型,或将Slim的响应对象视为数组(它实现ArrayAccess接口)并设置标头。
同样,您可以通过添加URL路由“ / articles /:id”的回调来处理对特定资源的GET请求。 清单2说明了这一点。
get('/articles/:id', function ($id) use ($app) {
try {
// query database for single article
$article = R::findOne('articles', 'id=?', array($id));
if ($article) {
// if found, return JSON response
$app->response()->header('Content-Type', 'application/json');
echo json_encode(R::exportAll($article));
} else {
// else throw exception
throw new ResourceNotFoundException();
}
} catch (ResourceNotFoundException $e) {
// return 404 server error
$app->response()->status(404);
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
});
// run
$app->run();
Slim可以自动从URL中提取路由参数。 清单2说明了这一点,因为它从GET请求中提取了:id参数,并将其传递给回调函数。 在该函数内, R::findOne()
方法检索相应的资源记录。 该记录作为JSON编码的响应主体返回给客户端。 如果提供的标识符无效并且找不到匹配的资源,则回调函数将引发自定义ResourceNotFoundException。 然后,try- catch块将其转换为404 Not Found服务器响应。
图3显示成功请求的结果。
图4显示了不成功的结果。
Slim还支持路由条件,允许开发人员为路由参数指定正则表达式。 如果不满足这些参数,则不会执行路由回调。 您可以按路由指定条件,如代码所示:
get('/articles/:id', function ($id) use ($app) {
// callback code
})->conditions(array('id' => '([0-9]{1,}'));
或者,您可以使用setDefaultConditions()
方法全局指定条件,如下所示:
'[0-9]{1,}',
));
Slim应用程序还公开了notFound()
方法,可用于为无法进行路线匹配的情况定义自定义代码。 此方法是引发自定义异常并手动设置404服务器响应代码的替代方法。
处理POST请求要多一些。 通常的REST约定是POST请求创建一个新资源,请求主体包含该资源的所有必要输入(在本例中为作者和标题)。 回调函数需要解码请求主体,将其转换为RedBean对象,然后将其保存到数据库,然后返回新创建资源的JSON编码表示。
清单3显示了处理POST请求的代码。
post('/articles', function () use ($app) {
try {
// get and decode JSON request body
$request = $app->request();
$body = $request->getBody();
$input = json_decode($body);
// store article record
$article = R::dispense('articles');
$article->title = (string)$input->title;
$article->url = (string)$input->url;
$article->date = (string)$input->date;
$id = R::store($article);
// return JSON-encoded response body
$app->response()->header('Content-Type', 'application/json');
echo json_encode(R::exportAll($article));
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
.});
// run
$app->run();
在清单3中 ,回调函数使用请求对象的getBody()检索请求的主体(假定为JSON编码的数据包),然后使用json_decode()函数将其转换为PHP对象。 接下来,使用PHP对象中的属性初始化一个新的RedBeanPHP文章对象,并使用R::store()
方法将其存储在数据库中。 然后将新对象导出到数组,并以JSON数据包的形式返回给客户端。
图5显示了示例POST请求和响应。
PUT请求用于指示对现有资源的修改,并因此在请求字符串中包含资源标识符。 成功的PUT表示现有资源已被PUT请求主体中指定的资源替换。 对成功的PUT的响应可以是状态代码200(OK)(响应主体包含更新资源的表示),也可以是状态代码204(无内容),其中响应主体为空。
清单4显示了处理PUT请求的代码。
put('/articles/:id', function ($id) use ($app) {
try {
// get and decode JSON request body
$request = $app->request();
$body = $request->getBody();
$input = json_decode($body);
// query database for single article
$article = R::findOne('articles', 'id=?', array($id));
// store modified article
// return JSON-encoded response body
if ($article) {
$article->title = (string)$input->title;
$article->url = (string)$input->url;
$article->date = (string)$input->date;
R::store($article);
$app->response()->header('Content-Type', 'application/json');
echo json_encode(R::exportAll($article));
} else {
throw new ResourceNotFoundException();
}
} catch (ResourceNotFoundException $e) {
$app->response()->status(404);
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
});
// run
$app->run();
在清单4中 ,作为URL路由的一部分提供的标识符用于从数据库中检索作为RedBeanPHP对象的相应资源。 使用json_decode()函数将请求的主体(假定为JSON编码的数据包)转换为PHP对象,并且其属性用于覆盖RedBeanPHP对象的属性。 然后,将修改后的对象保存回数据库,并用200服务器响应代码将JSON表示返回给调用方。 如果提供的标识符与现有资源不匹配,则会引发自定义异常,并将404服务器错误响应发送回客户端。
图6显示了一个示例PUT请求和响应。
您还可以编写一个回调来处理DELETE请求,这些请求将从数据存储中删除指定的资源。 清单5显示了必要的代码。
delete('/articles/:id', function ($id) use ($app) {
try {
// query database for article
$request = $app->request();
$article = R::findOne('articles', 'id=?', array($id));
// delete article
if ($article) {
R::trash($article);
$app->response()->status(204);
} else {
throw new ResourceNotFoundException();
}
} catch (ResourceNotFoundException $e) {
$app->response()->status(404);
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
});
// run
$app->run();
与清单4一样 , 清单5使用URL中的资源标识符作为对象从数据库中检索相应的资源,然后使用R::trash()
方法将其永久删除。 对成功的DELETE请求的响应可以是响应正文中的状态为200(确定)或响应正文为空的204(无内容)。 清单5完成了后者。
图7显示了示例DELETE请求和API响应。
除了灵活的路由,Slim还支持所谓的“路由中间件”。 用最简单的术语来说,中间件是一个或多个自定义函数,它们在相应路由的回调函数之前被调用。 中间件使对请求执行定制处理成为可能。 正如Ephemera在“机架中间件用例示例”中的博客文章所述,中间件“可以操纵请求,响应,它可以完全停止处理,或者做完全不相关的事情,例如日志记录。”
为了说明Slim的中间件体系结构是如何工作的,请考虑一个常见的API要求:身份验证。 使用中间件,可以通过在允许处理API请求之前将其通过自定义身份验证方法传递来确保对API请求进行正确的身份验证。
清单6展示了一个简单的身份验证功能,并将此功能作为中间件添加到GET请求处理程序中:
halt(401);
}
}
function validateUserKey($uid, $key) {
// insert your (hopefully complex) validation routine here
}
// handle GET requests for /articles
$app->get('/articles', 'authenticate', function () use ($app) {
// query database for all articles
$articles = R::find('articles');
// send response header for JSON content type
$app->response()->header('Content-Type', 'application/json');
// return JSON-encoded response body with query results
echo json_encode(R::exportAll($articles));
});
// run
$app->run();
清单6中的示例authenticate()函数从GET路由处理程序中作为中间件被引用,并且它自动接收路由对象作为参数。 它还可以使用Slim::getInstance()
方法访问Slim应用程序对象。
现在,当用户请求URL“ / articles”时,将在处理请求之前调用authenticate()函数。 该函数使用自定义验证例程对请求进行身份验证,并且仅在验证例程返回true时才允许进行进一步处理。 如果验证失败,authenticate()函数将停止处理并向客户端发送401 Authorization Required响应。
清单7提供了一个更具体的示例,说明如何通过检查具有用户标识符“ demo”和API密钥“ demo”的cookie来实现authenticate()
方法。 这些Cookie是通过调用新的API方法/demo
,该方法允许客户端在五分钟的时间内临时访问API。
getEncryptedCookie('uid');
$key = $app->getEncryptedCookie('key');
if (validateUserKey($uid, $key) === false) {
$app->halt(401);
}
}
function validateUserKey($uid, $key) {
// insert your (hopefully more complex) validation routine here
if ($uid == 'demo' && $key == 'demo') {
return true;
} else {
return false;
}
}
// handle GET requests for /articles
$app->get('/articles', 'authenticate', function () use ($app) {
// query database for all articles
$articles = R::find('articles');
// send response header for JSON content type
$app->response()->header('Content-Type', 'application/json');
// return JSON-encoded response body with query results
echo json_encode(R::exportAll($articles));
});
// generates a temporary API key using cookies
// call this first to gain access to protected API methods
$app->get('/demo', function () use ($app) {
try {
$app->setEncryptedCookie('uid', 'demo', '5 minutes');
$app->setEncryptedCookie('key', 'demo', '5 minutes');
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
});
// run
$app->run();
在这种情况下,客户端首先向API端点“ / demo”发送请求,API端点使用有效期为5分钟的“ demo”凭据设置加密的cookie。这些cookie将自动伴随对同一主机的任何后续请求。 )中间件函数(附加到“ / articles” URL端点的GET处理程序中)检查每个传入的GET请求以获取这些cookie。然后,它使用validateUserKey()函数来验证用户的凭据(在此示例中,只需检查一下Cookie过期后,validateUserKey()函数将返回false,并且authenticate()中间件将终止请求,并禁止访问带有401服务器错误的“ / article” URL端点。
图8显示了未经身份验证的GET请求上的服务器错误。
随附的代码档案库使用相同的中间件来验证POST,PUT和DELETE请求。 出于明显的原因,该系统并不是特别安全,因此绝对不能在生产环境中使用。 本文仅出于说明目的将其包括在内。
如本例所示,路由中间件可方便地执行请求预处理。 它也可以用于其他任务,例如请求过滤或日志记录。
前面的部分说明了如何设置简单的基于JSON的REST API。 XML也是一种流行的数据交换格式,通常需要在REST API中支持XML请求和响应主体。
Slim提供对请求标头的完全访问权限。 满足此要求的最简单方法之一是使用“ Content-Type”请求标头来确定事务中使用的数据格式。 考虑清单8 ,它根据“ Content-Type”标头修改了GET请求的回调函数,以返回XML或JSON响应主体:
get('/articles', function () use ($app) {
try {
// query database for articles
$articles = R::find('articles');
// check request content type
// format and return response body in specified format
$mediaType = $app->request()->getMediaType();
if ($mediaType == 'application/xml') {
$app->response()->header('Content-Type', 'application/xml');
$xml = new SimpleXMLElement(' ');
$result = R::exportAll($articles);
foreach ($result as $r) {
$item = $xml->addChild('item');
$item->addChild('id', $r['id']);
$item->addChild('title', $r['title']);
$item->addChild('url', $r['url']);
$item->addChild('date', $r['date']);
}
echo $xml->asXml();
} else if (($mediaType == 'application/json')) {
$app->response()->header('Content-Type', 'application/json');
echo json_encode(R::exportAll($articles));
}
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
});
// run
$app->run();
在清单8中 ,请求对象的getMediaType()
方法用于检索请求的内容类型。
图9说明了对GET请求的XML响应:
为什么停在那里? 清单9还添加了对POST请求的XML支持:
post('articles', function () use ($app) {
try {
// check request content type
// decode request body in JSON or XML format
$request = $app->request();
$mediaType = $request->getMediaType();
$body = $request->getBody();
if ($mediaType == 'application/xml') {
$input = simplexml_load_string($body);
} elseif ($mediaType == 'application/json') {
$input = json_decode($body);
}
// create and store article record
$article = R::dispense('articles');
$article->title = (string)$input->title;
$article->url = (string)$input->url;
$article->date = (string)$input->date;
$id = R::store($article);
// return JSON/XML response
if ($mediaType == 'application/xml') {
$app->response()->header('Content-Type', 'application/xml');
$xml = new SimpleXMLElement(' ');
$result = R::exportAll($article);
foreach ($result as $r) {
$item = $xml->addChild('item');
$item->addChild('id', $r['id']);
$item->addChild('title', $r['title']);
$item->addChild('url', $r['url']);
$item->addChild('date', $r['date']);
}
echo $xml->asXml();
} elseif ($mediaType == 'application/json') {
$app->response()->header('Content-Type', 'application/json');
echo json_encode(R::exportAll($article));
}
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
});
// run
$app->run();
在清单9中 ,根据内容类型是“ application / json”还是“ application / xml”,使用json_decode()或simplexml_load_string()将请求主体转换为PHP对象。 然后将对象保存到数据库,并将资源的JSON或XML表示返回给客户端。 图10说明了XML POST请求和响应:
Slim为构建REST API提供了功能强大且可扩展的框架,使应用程序开发人员可以轻松地允许第三方使用直观的架构模式访问应用程序功能。 Slim的URL匹配和路由功能,再加上其轻量级的API和对各种HTTP方法的支持,使其成为快速API原型设计和实现的理想选择。
请参阅下载以获取本文中实现的所有代码,以及一个简单的基于jQuery的测试脚本,您可以使用它们在示例API上执行GET,POST,PUT和DELETE请求。 我建议您获取代码,开始使用它,然后尝试向其中添加新内容。 我保证您不会破坏任何东西,它肯定会增加您的学习。
翻译自: https://www.ibm.com/developerworks/opensource/library/x-slim-rest/index.html
slim框架