在这一部分中,我们将开始使用REST接口。 在Laravel上创建REST Api并不是很困难。 我们需要记住的是,我们正在处理EmberJS,并且我们不想从头开始编写新的适配器。 和往常一样,您可以在github上找到此部分的源代码。
从哪儿开始?
这是一个很难的问题。 Ember具有自己的工作流程和逻辑。 如果我们开始考虑这种逻辑来编写REST,那么我们将节省一些时间,我们将拥有一个不错的体系结构和一些可重用的东西。 我认为Ember的REST架构是不错的选择。 看看Ember如何期望数据。
假设我们要检索一个用户。 恩伯期望这样的事情:
{
"user": {
"firstName": "firstName",
"lastName": "lastName"
}
}
如果我们要检索用户列表,Ember会期望像这样的json:
{
"users":
[
{
"firstName": "firstPersonsName",
"lastName": "lastname"
},
{
"firstName": "secondPersonName",
"lastName": "lastName"
}
]
}
第一个需要“用户”,但是第二个需要“用户”。 第二个是复数。 恩伯也为此制定了一些规则。 如果您不自己指定复数,请使用:
Ember.Inflector.inflector.irregular('formula', 'formulae');
EmberJs将做一个假设并请求“公式”。 有时候,框架本身可以提供这些东西,但是另一方面,如果您忘记了这些细节,事情可能会失控。
在尝试使用Ember进行更深入的研究之前,警告:Ember既困难又强大。 花时间学习它是如何工作的。
例如,如果我们使事物复杂化并在对象之间添加一些关系,例如,我们说用户有一些照片。 我们将如何输出呢?
{
"user": {
"id": 1,
"name": "firstName",
"lastname": "lastname,
"photos": [1, 2, 3]
},
"photos":
[
{
"id": 1,
"title": "Lorem Ipsum"
},
{
"id": 2,
"title": "Lorem Ipsum"
}
]
}
这是一对多关系。 如果我们要求用户,他的照片也将被拉出。 我们已经在Laravel中建立了一些关系,您可以根据需要使用它们,也可以在Ember上使用这些关系。
我从Ember开始,了解该框架如何获取数据。 如果您知道如何构建结构,则会更容易。 验证和从数据库获取数据很容易,但是要构建一个可靠的REST接口和一个智能接口,这是困难的部分。
准备REST
当您开发某些东西时,样机可能会很有帮助。 即使您是一位资深的程序员,并且不喜欢使用Photoshop或Gimp,也有很好的原型制作工具。 我使用了balsamiq,我的首页原型是这样的:
让我们开始构建它。 打开/app/views/index.php
。 这用作我们的单页应用程序。 我们在本系列的第一部分中创建了该文件。
<script type="text/x-handlebars">
<!-- The navigation top-bar -->
<nav class="top-bar" data-topbar>
<ul class="title-area">
<li class="name">
<h1><a href="#">Photo Upload</a></h1>
</li>
</ul>
<section class="top-bar-section">
<!-- Left Nav Section -->
<ul class="left">
<li class="has-dropdown">
<a href="#">Categories</a>
<ul class="dropdown">
<li><a href="#">Category1</a></li>
<li><a href="#">Category2</a></li>
<li><a href="#">Category3</a></li>
<li><a href="#">Category4</a></li>
</ul>
</li>
</ul>
</section>
<div class="clearfix"></div>
</nav><!-- END Navigation -->
<!-- Content -->
<div style="margin-top: 50px;">
<!-- The content will be here -->
</div><!-- END Content -->
</script>
请允许我解释一下。 nav
标签负责导航。 具有title-area
类的ul
标签是用作徽标的文本,该徽标链接到应用程序的第一级。 我还添加了一个带有类别列表的下拉列表。 如果您想了解更多信息,请访问Foundation 5文档 。 在大多数情况下,它只是复制/粘贴操作,因此不必担心这部分。
另外,我将Foundation的网格系统用于内容区域。 这将充满所有信息,并且将在导航时更改。 所有内部更新将由Ember处理。 我们将在这里仅构建3个模板。 一个给用户,一个给单张照片,一个给登陆页面。
您是否注意到我们所有的代码都在script标签内? Ember使用车把作为其模板语言。 text/x-handlebars
的类型是一种特殊的脚本。 如果您使用Ember和Handlebars已有一段时间,则可能使用了模板名称。 我在此未指定它们,因为此模板将用作所有应用程序的容器。 如果您未指定名称,Ember会将其用作应用程序模板。
资源控制器
当我开发这个非常简单的应用程序时,我发现在开发REST Apis时资源控制器会派上用场。 这就是REST体系结构的重点–一切都是资源。 所有资源都可以应用HTTP动词:GET,POST,DELETE,PUT(更新)。 并非所有动词都是必需的。
php artisan controller:make PhotoController --except=create,edit
这就是我们通过Artisan创建资源控制器的方式。 选项--except
从此控制器中--except
了这两种方法。 我们不需要create
和edit
方法。 create
方法处理创建该资源的图形界面。 当我们制作一个单页应用程序时,在Ember之外创建视图是不明智的。
为类别创建另一个资源控制器。 如您所见,此控制器中仅show
和index
方法可用。 我认为显示一个单独的类别并检索所有类别就足够了。
php artisan controller:make CategoryController --only=show,index
另一个控制器是图像控制器。 如果已有图像控制器,为什么要使用图像控制器? 因为我们需要一个端点来提供图像。 Dropbox保存我们的图像,但是我们无法从外部访问它们。 如果要公开文件夹,则必须付费。 那是第一个原因。 第二个原因是我不希望每张图片都公开。 简而言之,此控制器将从Dropbox抓取图像并将其提供给客户端。
php artisan controller:make ImagesController --only=show
最后但并非最不重要的是UserController:
php artisan controller:make UserController --only=show,index
路线
现在我们有了控制器,我们需要将那些控制器与其相关的路由链接起来。 让我们更新/app/routes.php
。 首先,使用Route::group
它们分组在url名称空间中。
Route::group(array('prefix' => 'api/v1'), function()
{
});
在这里,我们指定了一个前缀,即名称空间。 可以按以下方式访问该组中的所有内容:
example.com/api/v1
另外,我们可以在该组内指定过滤器。 例如,您可以添加Auth::onceBasic('username')
过滤器或创建一个过滤器并将其添加到该组中。 您也可以使用其他身份验证。
在该组中添加三个控制器。 PhotoController,UserController和CategoryController。
Route::group(array('prefix' => 'api/v1'), function()
{
Route::resource('photos', 'PhotoController');
Route::resource('users', 'UserController');
Route::resource('categories', 'CategoryController');
});
在该组之外添加ImagesController。 我认为该控制器不需要名称空间–图像是图像,为它们提供名称空间毫无意义。
Route::resource('files', 'ImagesController');
最后,/ /app/routes.php
文件应如下所示:
Route::get('/', function()
{
return View::make('index');
});
Route::group(array('prefix' => 'api/v1'), function()
{
Route::resource('photos', 'PhotoController');
Route::resource('users', 'UserController');
Route::resource('categories', 'CategoryController');
});
Route::resource('files', 'ImagesController');
请注意,由于Ember的要求,资源名称为复数形式。
填补那些控制器
现在我们可以开始构建一些东西了。 在这里,我将不介绍REST的全部内容,因为它很难解释所有事情-要更深入地了解更多内容,请参阅本系列 。 让我们从照片控制器开始。
index()
方法应从数据库返回最新照片。 在这里,我们可以做一些分页,但是我不希望事情变得太复杂。 如果对评论有足够的兴趣,我们将在以后的文章中更新此应用程序。
public function index()
{
try{
$statusCode = 200;
$response = [
'photos' => []
];
$photos = Photo::all()->take(9);
foreach($photos as $photo){
$response['photos'][] = [
'id' => $photo->id,
'user_id' => $photo->user_id,
'url' => $photo->url,
'title' => $photo->title,
'description' => $photo->description,
'category' => $photo->category,
];
}
}catch (Exception $e){
$statusCode = 400;
}finally{
return Response::json($response, $statusCode);
}
}
让我解释一下。 我将所有内容插入try
, catch
并finally
阻止。 如果出了什么问题,请返回带有状态代码的其他json。
$photos = Photo::all()->take(9);
这将从数据库中获取9张照片。 然后,拍摄每张照片并以格式化的阵列显示,稍后将转换为json格式。
如果一切顺利,或者如果Eloquent没有抛出异常,则显示正确的输出。 如果要显示特定的状态代码,请捕获Eloquent可能引发的每个异常,并显示正确的状态代码。
现在让我们填充show()
方法。 同样,我们想检索有关具有给定ID的照片的所有信息。
public function show($id)
{
try{
$photo = Photo::find($id);
$statusCode = 200;
$response = [ "photo" => [
'id' => (int) $id,
'user_id' => (int) $photo->user_id,
'title' => $photo->title,
'url' => $photo->url,
'category' => (int) $photo->category,
'description' => $photo->description
]];
}catch(Exception $e){
$response = [
"error" => "File doesn`t exists"
];
$statusCode = 404;
}finally{
return Response::json($response, $statusCode);
}
}
在构建自己的应用程序时,请不要忘记在用户输入中添加验证。
UserController的逻辑几乎相同。 这次,我们将请求用户Model。
public function index()
{
try{
$response = [
'users' => []
];
$statusCode = 200;
$users = User::all()->take(9);
foreach($users as $user){
$response['users'][] = [
'id' => $user->id,
'username' => $user->username,
'lastname' => $user->lastname,
'name' => $user->username
];
}
}catch (Exception $e){
$statusCode = 404;
}finally{
return Response::json($response, $statusCode);
}
}
一切几乎都是相同的,只是Model和字段发生了变化。 输出json。 show
方法将如下所示:
public function show($id)
{
try{
$response = [
'user' => []
];
$statusCode = 200;
$user = User::find($id);
$response = [
'id' => $user->id,
'name' => $user->name,
'lastname' => $user->lastname,
'username' => $user->username
];
}catch (Exception $e){
$statusCode = 404;
}finally{
return Response::json($response, $statusCode);
}
}
此函数检索具有给定ID的用户。
我们要处理的最后一个控制器是ImagesController。 逻辑很简单,就像从文件系统中获取图像并将其提供服务。 当您保存文件并使用本地文件系统或服务器文件系统进行检索时,这很简单。 不幸的是,您无法将文件保存到Heroku,因此我们将使用Dropbox并从此端点提供这些文件。
导入Dropbox Client和Flysystem适配器。 如果我们的环境是本地的,那么我们将使用带本地适配器的flysystem; 如果环境是生产环境,则使用Dropbox适配器。 将Flysystem类分配到此控制器内部的私有变量中。
if(App::environment() === "local"){
$this->filesystem = new Filesystem(new Adapter( public_path() . '/images/'));
}else{
$client = new Client(Config::get('dropbox.token'), Config::get('dropbox.appName'));
$this->filesystem = new Filesystem(new Dropbox($client, '/images/'));
}
show
方法将提供该文件, destroy
方法将从文件系统中删除该文件。 通过使用此库,我们将抽象级别放入了我们的应用程序。
public function show($name)
{
try{
$file = $this->filesystem->read($name); // Read the file
}catch (Exception $e){
return Response::json("{}", 404); // Return a 404 status code in a error case
}
$response = Response::make($file, 200); // If everything goes ok then return that file and 200 status code
return $response;
}
destroy()
函数非常简单。 只需使用delete方法并传递要删除的文件名来选择该文件。 如果找不到该文件,则返回404。
public function destroy($name)
{
try{
$this->filesystem->delete($name);
return Response::json("{}", 200);
}catch (\Dropbox\Exception $e){
return Response::json("{}", 404);
}
}
最后,ImageController应该看起来像这样:
/* /app/controllers/ImagesController.php */
use Dropbox\Client;
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local as Adapter;
use League\Flysystem\Adapter\Dropbox;
class ImagesController extends \BaseController {
private $filesystem;
public function __construct(){
if(App::environment() === "local"){
$this->filesystem = new Filesystem(new Adapter( public_path() . '/images/'));
}else{
$client = new Client(Config::get('dropbox.token'), Config::get('dropbox.appName'));
$this->filesystem = new Filesystem(new Dropbox($client, '/images/'));
}
}
public function show($name)
{
try{
$file = $this->filesystem->read($name);
}catch (Exception $e){
return Response::json("{}", 404);
}
$response = Response::make($file, 200);
return $response;
}
public function destroy($name)
{
try{
$this->filesystem->delete($name);
return Response::json("{}", 200);
}catch (\Dropbox\Exception $e){
return Response::json("{}", 404);
}
}
}
我们提供的格式是HTML。 好吧,那有点奇怪。 我们想提供图片,而不是HTML。 但是,这不是问题,因为浏览器会查找文件格式并识别如何使用该文件。
继续尝试创建CategoryController。 我把它留给您练习。
测试Api
我必须承认,我爱上了PhpStorm,并且为了测试Rest API,我使用了一个称为Rest Client的工具。 这是一个图形界面,可以简化测试。 如果需要,您还可以从终端使用CURL。 让我们做一些测试:
curl http://localhost:8000/api/v1/users
这是返回的内容:
使用PhpStorm的Rest Client,我在json中得到了相同的结果。
如果我想以更好的格式查看结果,我只需按一下工具左侧的js图标,Rest Client就可以给我一个更好的表示。
您还可以测试其他动词,例如删除和发布。 继续进行尽可能多的测试。 您还可以使用其他客户端进行测试:Rest Console和Postman是其中两个。 第一个仅在Chrome上可用,第二个Postman在Chrome和Firefox上均可用。 邮递员似乎更简单,更友好。 继续尝试一下。
结论
Laravel简化了使用资源控制器构建REST Apis的工作。 我们看到了如何使用Ember约定来构建接口。 Ember选择了一个好的界面,并且通过遵循该逻辑,您可以轻松地在其他平台上重用代码。
在这一部分中,我将更多的精力放在概念上,并且没有做太多的编码。 填充所有方法并添加验证可能会不必要地扩展此帖子,因为该帖子已经足够长且需要很长时间。 开发时,应始终验证输入。 不要忘记它,并进行测试,测试和测试。 测试应该是您最好的朋友。
在本系列的最后一部分中,我们将全部内容整合到一个功能全面的实时应用程序中。
From: https://www.sitepoint.com/build-rest-resources-laravel/