使用Laravel构建REST资源

在这一部分中,我们将开始使用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,我的首页原型是这样的:

Prototyping with 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了这两种方法。 我们不需要createedit方法。 create方法处理创建该资源的图形界面。 当我们制作一个单页应用程序时,在Ember之外创建视图是不明智的。

为类别创建另一个资源控制器。 如您所见,此控制器中仅showindex方法可用。 我认为显示一个单独的类别并检索所有类别就足够了。

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

	}

让我解释一下。 我将所有内容插入trycatchfinally阻止。 如果出了什么问题,请返回带有状态代码的其他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

这是返回的内容:

Using Curl for testing Rest Apis

使用PhpStorm的Rest Client,我在json中得到了相同的结果。

Testing Rest Apis with Rest Client of PhpStorm

如果我想以更好的格式查看结果,我只需按一下工具左侧的js图标,Rest Client就可以给我一个更好的表示。

The Json file from the Rest Client of PhpStorm

您还可以测试其他动词,例如删除和发布。 继续进行尽可能多的测试。 您还可以使用其他客户端进行测试:Rest Console和Postman是其中两个。 第一个仅在Chrome上可用,第二个Postman在Chrome和Firefox上均可用。 邮递员似乎更简单,更友好。 继续尝试一下。

结论

Laravel简化了使用资源控制器构建REST Apis的工作。 我们看到了如何使用Ember约定来构建接口。 Ember选择了一个好的界面,并且通过遵循该逻辑,您可以轻松地在其他平台上重用代码。

在这一部分中,我将更多的精力放在概念上,并且没有做太多的编码。 填充所有方法并添加验证可能会不必要地扩展此帖子,因为该帖子已经足够长且需要很长时间。 开发时,应始终验证输入。 不要忘记它,并进行测试,测试和测试。 测试应该是您最好的朋友。

在本系列的最后一部分中,我们将全部内容整合到一个功能全面的实时应用程序中。

From: https://www.sitepoint.com/build-rest-resources-laravel/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值