rest laravel_使用Laravel构建REST资源

rest laravel

In this part, we will begin to work with the REST interface. Creating a REST Api on Laravel isn’t very difficult. All we need to keep in mind is that we’re dealing with EmberJS and that we don’t want to write a new adapter from scratch. As usual, you can find the source code for this part on github.

在这一部分中,我们将开始使用REST接口。 在Laravel上创建REST Api并不是很困难。 我们需要记住的是,我们正在处理EmberJS,并且我们不想从头开始编写新的适配器。 和往常一样,您可以在github上找到此部分的源代码。

从哪儿开始? (Where to start?)

That is a hard question. Ember has its own workflow and logic. If we begin to write our REST with that logic in mind we will save some time, we’ll have a nice architecture and something reusable. I think Ember has made a good choice with their REST architecture. Take a look at how Ember expects the data.

这是一个很难的问题。 Ember具有自己的工作流程和逻辑。 如果我们开始考虑这种逻辑来编写REST,我们将节省一些时间,我们将拥有一个不错的体系结构和一些可重用的东西。 我认为Ember的REST架构是不错的选择。 看看Ember如何期望数据。

Let’s assume that we want to retrieve a user. Ember expects something like this:

假设我们要检索一个用户。 恩伯期望这样的事情:

{
      "user": {
        "firstName": "firstName",
        "lastName": "lastName"
      }
    }

If we want to retrieve a list of users, Ember would expect a json like this:

如果我们要检索用户列表,Ember会期望像这样的json:

{
      "users": 
      [
          {
            "firstName": "firstPersonsName",
            "lastName": "lastname"
          },
          {
            "firstName": "secondPersonName",
            "lastName": "lastName"
          }
      ]
    }

The first one requires “user”, but the second one requires “users”. The second one is plural. Ember put in some rules for that too. If you don`t specify the plural yourself by using:

第一个需要“用户”,但是第二个需要“用户”。 第二个是复数。 恩伯也为此制定了一些规则。 如果您不自己指定复数,请使用:

Ember.Inflector.inflector.irregular('formula', 'formulae');

EmberJs will make an assumption and request “formulas”. Sometimes, it’s nice that the framework itself provides such things, but on the other hand things can get out of control if you forget these details.

EmberJs会做一个假设并请求“公式”。 有时,框架本身可以提供这些东西很好,但是另一方面,如果您忘记了这些细节,事情可能会失控。

Before venturing deeper with Ember, a warning: Ember is difficult and powerful. Take the time to learn how it works.

在尝试使用Ember进行更深入的研究之前,警告:Ember既困难又强大。 花时间去学习它是如何工作的。

If we complicate things a little bit and put some relations between the objects, for example we say that the user has some photos. How would we output that?

如果我们使事情复杂一点,并在对象之间建立一些关系,例如,我们说用户有一些照片。 我们将如何输出呢?

{
      "user": {
        "id": 1,
        "name": "firstName",
        "lastname": "lastname,
        "photos": [1, 2, 3]
      },
    
      "photos": 
      [
          {
            "id": 1,
            "title": "Lorem Ipsum"
          },
          {
            "id": 2,
            "title": "Lorem Ipsum"
          }
      ]
    }

This is a one-to-many relation. If we request a user, his photos will be pulled too. We have already set up some relations in Laravel, you can use them if you want and consume those relations on Ember, too.

这是一对多关系。 如果我们要求用户,他的照片也将被拉出。 我们已经在Laravel中建立了一些关系,您可以根据需要使用它们,也可以在Ember上使用这些关系。

I started with Ember to see how this framework wants the data. It’s easier if you know how to build the structure. The validation and getting the data from database is easy, but building a solid REST interface and a smart one, that is the hard part.

我从Ember开始,以了解该框架如何获取数据。 如果您知道如何构建结构,则会更容易。 验证和从数据库获取数据很容易,但是要构建一个可靠的REST接口和一个智能接口,这是困难的部分。

准备REST (Preparing for the REST)

When you develop something, a mockup can be very helpful. Even if you are a guru programmer and you hate dealing with Photoshop or Gimp, there are good tools for prototyping. I used balsamiq and my front-page prototype was this:

当您开发某些东西时,样机可能会很有帮助。 即使您是一位资深的程序员,并且您不喜欢与Photoshop或Gimp打交道,也有很好的原型制作工具。 我使用了balsamiq,我的首页原型是这样的:

Prototyping with Balsamiq

Lets start building it. Open /app/views/index.php . This serves as our single page app. We created this file in the first part of this series.

让我们开始构建它。 打开/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>

Allow me to explain this. The nav tag is responsible for the navigation. The ul tag with the class title-area is text that is used as a logo which links to the first level of the application. I have also added a drop-down with a list of categories. Head to the Foundation 5 docs if you want to learn more. Most of the time it’s just copy/paste operations, so don’t worry about this part.

请允许我解释一下。 nav标签负责导航。 具有title-area类的ul标签是用作徽标的文本,该徽标链接到应用程序的第一级。 我还添加了一个带有类别列表的下拉列表。 如果您想了解更多信息,请访问Foundation 5文档 。 在大多数情况下,它只是复制/粘贴操作,因此不必担心这部分。

Also, I used Foundation’s grid system for the content area. This would be filled with all the information and will be changed while navigating. All the internal updates will be handled by Ember. We are going to build only 3 templates here. One for users, one for a single photo and one for the landing page.

另外,我将Foundation的网格系统用于内容区域。 这将充满所有信息,并且将在导航时更改。 所有内部更新将由Ember处理。 我们将在这里仅构建3个模板。 一个给用户,一个给单张照片,一个给登陆页面。

Have you noticed that all of our code is inside a script tag? Ember uses handlebars as its template language. The type of text/x-handlebars is a special type of script. If you have used Ember and Handlebars for a while, you probably used template names. I don’t specify them in this one because this template will be used as the container for all of the application. If you don’t specify a name, Ember uses it as the Application Template.

您是否注意到我们所有的代码都在script标记内? Ember使用车把作为其模板语言。 text/x-handlebars的类型是一种特殊的脚本。 如果您使用Ember和Handlebars已有一段时间,则可能使用了模板名称。 我在此未指定它们,因为此模板将用作所有应用程序的容器。 如果您未指定名称,Ember会将其用作应用程序模板。

资源控制器 (The Resource controllers)

As I developed this very simple app, I found that Resource Controllers come in handy when developing REST Apis. That is the point of the REST architecture – everything is a resource. All resources can have an HTTP verb applied: GET, POST, DELETE, PUT (update). Not all verbs are required.

当我开发这个非常简单的应用程序时,我发现在开发REST Apis时资源控制器会派上用场。 这就是REST体系结构的重点–一切都是资源。 所有资源都可以应用HTTP动词:GET,POST,DELETE,PUT(更新)。 并非所有动词都是必需的。

php artisan controller:make PhotoController --except=create,edit

This is how we create a Resource Controller via Artisan. The option --except leaves out these two methods from this controller. We don’t need the create and edit methods. The create method deals with the graphical interface of creating that resource. As we are making a one page app, it isn’t wise to create a view outside Ember.

这就是我们通过Artisan创建资源控制器的方式。 选项--except从此控制器中--except了这两种方法。 我们不需要createedit方法。 create方法处理创建该资源的图形界面。 当我们制作一个单页应用程序时,在Ember之外创建视图是不明智的。

Create another Resource Controller for categories. As you can see, only show and index methods are available in this controller. I think that showing an individual category and retrieving all categories is enough.

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

php artisan controller:make CategoryController --only=show,index

Another Controller is the Images Controller. Why images controller if we already have one? Because we need an end point to serve the images. Dropbox holds our images, but we cannot access them from the outside. If you want to make a folder public, you have to pay. That’s the first reason. The second reason is that I don’t want every image to be public. In a nutshell, this controller will grab the image from Dropbox and serve it to the client.

另一个控制器是图像控制器。 如果已经有图像控制器,为什么要使用图像控制器? 因为我们需要一个端点来提供图像。 Dropbox保存我们的图像,但是我们无法从外部访问它们。 如果要使文件夹公开,则必须付费。 那是第一个原因。 第二个原因是我不希望每张图片都公开。 简而言之,该控制器将从Dropbox抓取图像并将其提供给客户端。

php artisan controller:make ImagesController --only=show

And the last but not least is the UserController:

最后但并非最不重要的是UserController:

php artisan controller:make UserController --only=show,index

路线 (The Route)

Now that we have the Controllers, we need to link those controllers with their related routes. Let’s update /app/routes.php. First, group them inside a url namespace by using Route::group.

现在我们有了控制器,我们需要将这些控制器与其相关的路由链接起来。 让我们更新/app/routes.php 。 首先,使用Route::group它们分组在url名称空间中。

Route::group(array('prefix' => 'api/v1'), function()
    {
        
    
    });

Here we specified a prefix, the namespace. Everything that is inside this group can be accessed like this:

在这里,我们指定了一个前缀,即名称空间。 可以按以下方式访问该组中的所有内容:

example.com/api/v1

Also, we can specify filters inside that group. For example, you can add a Auth::onceBasic('username') filter or create one and add it in this group. You can use other authentications, too.

另外,我们可以在该组内指定过滤器。 例如,您可以添加Auth::onceBasic('username')过滤器或创建一个过滤器并将其添加到该组中。 您也可以使用其他身份验证。

Add three controllers inside that group. PhotoController, UserController and CategoryController.

在该组中添加三个控制器。 PhotoController,UserController和CategoryController。

Route::group(array('prefix' => 'api/v1'), function()
    {
        Route::resource('photos', 'PhotoController');
        Route::resource('users', 'UserController');
        Route::resource('categories', 'CategoryController');
    });

Add the ImagesController outside that group. I don’t think that this controller needs a namespace – images are images and there is no point in giving them a namespace.

在该组之外添加ImagesController。 我认为该控制器不需要名称空间–图像是图像,为它们提供名称空间是没有意义的。

Route::resource('files', 'ImagesController');

In the end, the /app/routes.php file should look like this:

最后,/ /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');

Notice the resource names are plural, due to Ember’s requirement.

注意,由于Ember的要求,资源名称为复数形式。

填充那些控制器 (Filling those controllers)

Now we can start building something. I’m not going to cover all about REST here because its very difficult to explain all the things – to find out more in greater depth, see this series. Let’s start with the photo controller.

现在我们可以开始构建一些东西了。 在这里,我将不介绍REST的全部内容,因为它很难解释所有事情-要更深入地了解更多内容,请参阅本系列 。 让我们从照片控制器开始。

The index() method should return the newest photos from the database. Here, we could do some pagination but I don’t want things to get too complex. If there’ll be enough interest in the comments, we’ll update this application in a future article.

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

	}

Let me explain this. I inserted everything inside a try, catch and finally block. If something goes wrong, return a different json with a status code.

让我解释一下。 我将所有内容插入trycatchfinally阻止。 如果出现问题,请返回带有状态代码的其他json。

$photos = Photo::all()->take(9);

This grabs 9 photos from the database. Then, take every photo and display it in a formatted array that will be converted to json format later on.

这将从数据库中获取9张照片。 然后,拍摄每张照片并以格式化的数组显示,稍后将转换为json格式。

If everything goes well or if an Exception isn’t thrown by Eloquent, this displays the right output. If you want to display a specific status code, catch every Exception that can be thrown by Eloquent and display the right status code.

如果一切顺利,或者如果Eloquent没有抛出异常,则显示正确的输出。 如果要显示特定的状态代码,请捕获Eloquent可能引发的每个异常,并显示正确的状态代码。

Let’s fill the show() method now. Again, we want to retrieve all the information about the photo with the given id.

现在让我们填充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);
        }

	}

When building your own app, don’t forget to add validation to user input.

在构建自己的应用程序时,请不要忘记在用户输入中添加验证。

The logic for the UserController is almost the same. This time, we will request the user Model.

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

Everything is almost identical, only the Model and the fields change. The output json. The show method will look like this:

一切几乎都是相同的,只是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);
        }

	}

This function retrieves a user with the given id.

此函数检索具有给定ID的用户。

The last controller we are dealing with is the ImagesController. The logic is as simple as grabbing the images from the filesystem and serving them. It’s simple when you are saving files and retrieving with the local filesystem or the server filesystem. Unfortunately, you can’t save files to Heroku, so you we’ll use Dropbox and serve those files from this end-point.

我们要处理的最后一个控制器是ImagesController。 逻辑很简单,例如从文件系统中获取图像并提供服务。 当您保存文件并使用本地文件系统或服务器文件系统进行检索时,这很简单。 不幸的是,您无法将文件保存到Heroku,因此我们将使用Dropbox并从此端点提供这些文件。

Import the Dropbox Client and the Flysystem adapter. If our environment is local, then we will use flysystem with the local adapter; if the environment is production, then use the Dropbox adapter. Assign the Flysystem class into a private variable inside this controller.

导入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/'));

    }

The show method will serve that file and the destroy method will delete that file from the filesystem. By using this library we put a level of abstraction into our app.

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;

	}

The destroy() function is very simple. Just select that file by using the delete method and passing the name of the file to be deleted. If the file is not found then return a 404.

destroy()函数非常简单。 只需使用delete方法并传递要删除的文件名来选择该文件。 如果找不到该文件,则返回404。

public function destroy($name)
	{
		try{
            $this->filesystem->delete($name);
            return Response::json("{}", 200);
        }catch (\Dropbox\Exception $e){
            return Response::json("{}", 404);
        }
	}

In the end, the ImageController should look something like this:

最后,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);
            }
    	}
    
    
    }

The format we served is HTML. Ok, that’s a bit weird. We wanted to serve images, not HTML. However, that’s not a problem because the browser looks for the file format and recognizes how to use that file.

我们提供的格式是HTML。 好吧,那有点奇怪。 我们想提供图片,而不是HTML。 但是,这不是问题,因为浏览器会查找文件格式并识别如何使用该文件。

Go ahead and try to create the CategoryController. I left it out as an exercise for you.

继续尝试创建CategoryController。 我把它留给您练习。

测试Api (Testing the Api)

I have to admit, I’m in love with PhpStorm, and for testing the Rest APIs I use a tool called Rest Client. It’s a graphical interface which simplifies the testing. You can also use CURL from the terminal if you want. Let’s make some tests:

我必须承认,我爱上了PhpStorm,并且为了测试Rest API,我使用了一个称为Rest Client的工具。 这是一个图形界面,可以简化测试。 如果需要,您还可以从终端使用CURL。 让我们做一些测试:

curl http://localhost:8000/api/v1/users

And this is what’s returned:

这是返回的内容:

Using Curl for testing Rest Apis

With PhpStorm’s Rest Client, I get the same result in json.

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

Testing Rest Apis with Rest Client of PhpStorm

And if I want to see the results in a better format, I can simply press the js icon on the left side of the tool and the Rest Client gives me a nicer representation.

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

The Json file from the Rest Client of PhpStorm

You can also test other verbs like delete and post. Go ahead and test as much as you can. There are other clients that you can use for testing: Rest Console and Postman are two of them. The first is available only on Chrome, and the second one, Postman, is available on both Chrome and Firefox. Postman seems simpler and more user friendly. Go ahead and give them a try.

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

结论 (Conclusion)

Laravel simplifies our work for building REST Apis with Resource Controllers. We saw how the interface should be built by using Ember conventions. Ember has choosen a good interface and by sticking to that logic, you can reuse your code on other platforms easily.

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

In this part, I focused more on the concepts and didn’t do too much coding. Filling all the methods and adding validation would have unnecessarily extended this post, when it’s already long enough and in a long series. When developing, you should always validate input. Don’t forget it, and test, test, test. Testing should be your best friend.

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

In the final installment of this series, we’ll put it all together into a fully functioning live application.

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

翻译自: https://www.sitepoint.com/build-rest-resources-laravel/

rest laravel

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值