六、用 Lumen 照亮 RESTful Web 服务

到目前为止,我们已经在核心 PHP 中创建了一个非常基本的 RESTful web 服务,并发现了设计和安全方面的缺陷。我们也看到,为了让事情变得更好,我们不需要从头开始创造一切。事实上,使用经过时间测试的开源代码更有意义,可以基于更干净的代码构建更好的 web 服务。

在上一章中,我们看到 Composer 是 PHP 项目的依赖项管理器。在本章中,我们将使用一个开源微框架来编写 RESTful web 服务。我们将在一个开放源码的微框架中做同样的工作,该框架正在积极开发中,经过时间测试,并且在 PHP 社区中广为人知。使用框架而不是少数组件的原因是,一个合适的框架可以使我们的代码具有良好的结构,并且它附带一些基本的必需组件。我们选择的微框架是 Lumen,它是全堆栈框架 Laravel 的微框架版本。

以下是我们打算在本章中介绍的内容:

  • 引入腔
    • 流明提供了什么
    • Lumen 与 Laravel 有什么共同之处
    • Lumen 与 Laravel 有何不同
  • 安装和配置
  • 数据库迁移
  • 在内腔中写入 RESTAPI
    • 路线
    • 控制器
    • 剩余资源
    • 雄辩的(模型)
      • 关系
  • 用户访问和基于令牌的身份验证和会话
  • API 版本控制
  • 利率限制
  • 用户的数据库种子
  • 为 RESTAPI 使用 Lumen 包
  • 回顾基于流明的 restapi
  • 需要加密
  • 不同的 SSL 选项
  • 摘要和更多资源

引入腔

Lumen 是全堆叠框架 Laravel 的微框架版本。在 PHP 社区中,Laravel 是一个非常有名的框架。因此,通过使用 Lumen,我们总是可以将我们的项目转换为 Laravel,并在必要时开始使用它的完整堆栈功能。

为什么是微观框架?

每件事都有代价。我们选择了微框架而不是全堆栈框架,因为尽管全堆栈框架提供了更多的功能,但要拥有这些功能,它必须加载更多的内容。因此,为了提供更多功能,与微框架相比,全栈框架必须在性能上有所妥协。另一方面,微框架放弃了一些构建 web 服务所不需要的特性,如视图等,这使得它变得更快。

为什么是流明?

Lumen 不是 PHP 社区中唯一的微框架。那么为什么是流明呢?这主要有三个原因:

  • Lumen 是 Laravel 的微观框架,因此只要稍加努力,我们就可以将其转换为 Laravel,并利用其完整的堆栈功能。
  • 由于 Lumen 是 Laravel 的一个微观框架,它与 Laravel 一样拥有强大的社区支持。一个好的社区总是一个非常重要的因素。同时,Lumen 能够使用与 Laravel 相同的许多封装。
  • 除了与 Laravel 的关系外,Lumen 在性能方面也非常好。基于性能,其他替代微框架可以是 Slim 和 Selex。

流明提供了什么

正如我们所知,Lumen 是 Laravel 的微框架版本,它提供了 Laravel 提供的许多功能。例如,它是一个 MVC 框架。然而,了解流明和拉腊维尔有什么共同点以及流明没有什么不同点是很好的。这将让我们很好地了解流明为我们准备了什么。

Lumen 与 Laravel 有什么共同之处

在这里,我并没有说 Laravel 和 Lumen 之间的相似之处,因为 Lumen 并不是一个完全不同的框架。我说了它们的共同点,因为它们有共同的包和组件:这意味着它们在许多情况下共享相同的代码库。

实际上,流明是一种小的,最小的拉维。它只是删除了一些组件,并将不同的组件用于某些任务,如路由。但是,您始终可以在同一安装中打开许多组件。有时,您甚至不需要为此在配置中编写一些代码。相反,您只需转到配置文件,取消注释一些代码行,它就会开始使用这些组件。

事实上,流明有相同的版本。例如,如果有 5.4 版本的 Laravel,Lumen 将具有相同的版本。所以,这不是两件不同的事情。他们有很多相似之处。Lumen 只是为了性能而丢弃一些不需要的东西。然而,如果您只想将为 Lumen 编写的应用程序代码转换为 Laravel,您可以简单地将该代码放在 Laravel 安装中,它应该可以正常工作。不需要对应用程序代码进行重大更改。

Lumen 与 Laravel 有何不同

由于 Lumen 是为微服务和 API 构建的,因此与前端相关的组件(如长生不老药、身份验证引导、会话和视图等)并不随 Lumen 一起提供,但如果需要,这些组件可以在以后包括在内:在这方面非常灵活。

在管腔中,路径是不同的。事实上,它不使用 Symfony 路由器;相反,它使用了一个不同的路由器,速度更快,但功能更少。这是因为流明牺牲了速度的特性。类似地,没有类似于 Laravel 的单独配置文件。相反,一些配置在.env文件中完成,而其他与注册提供者或别名等相关的配置在bootstrap/app.php文件中完成,可能是为了避免为了速度而加载不同的文件。

Lumen 和 Laravel 都有很多软件包,其中很多都适用于两者。尽管如此,仍有一些软件包主要是为 Laravel 设计的,在没有一些改动的情况下不能使用 Lumen。因此,如果您打算安装一个软件包,请确保它支持流明。对于 Laravel,大多数软件包都适用于 Laravel,因为 Laravel 更受欢迎,并且大多数软件包都是为 Laravel 构建的。

内腔到底提供了什么

您可能认为这就是 Lumen 和 Laravel 之间的区别,但是 Lumen 到底为我们提供了什么,以便我们可以构建 API?我们将对此进行研究,但不详细,因为中的流明文件 https://lumen.laravel.com/docs/5.4 就是为了这个目的。文档中未涉及的是如何使用 Lumen 创建 RESTful web 服务,以及可以使用哪些包来简化工作和生活。我们将对此进行调查。

首先,我们将讨论流明的功能,以便了解流明的不同组成部分和工作原理。

良好的结构

管腔具有良好的结构。由于它源于遵循 MVC(模型-视图-控制器)模式的 Laravel,Lumen 还具有模型和控制器层。它没有视图层,因为它不需要视图:它用于 web 服务。如果你不知道 MCV 是什么,就认为它只是一个架构模式,职责分布在三层。模型是一个数据库层,有时也用作业务逻辑层(我们将在后面的章节中研究模型中应该包含的内容)。视图层用于模板相关的内容。在从模型和渲染视图获取数据时,控制器可以被视为处理请求的层。对于内腔,只有模型层和控制器层。

内腔为我们提供了一个良好的结构,所以我们不需要自己制作。事实上,Laravel 不仅提供了 MVC 结构,还提供了服务容器,很好地解决了依赖关系。Lumen 和 Laravel 的结构远不止一种设计模式,但它很好地利用了不同的设计模式。所以,让我们看看 Lumen 还提供了什么,并研究服务容器和许多其他主题。

独立配置

第 4 章回顾设计缺陷和安全威胁中,我们看到配置应该与实现分开,所以 Lumen 为我们做到了这一点。它有单独的配置文件。事实上,它有一个单独的.env文件,在不同的环境中可能会有所不同。除了.env文件之外,还有一个配置文件,用于存储与不同包相关的配置,如包注册或别名等。

Please note that you probably don't see the .env file on Mac or Linux at first because it starts from the dot, so it will be hidden. You will need to make hidden files shown, and then you will see the .env file there.

路由器

流明具有更好的路由功能。它不仅让您知道哪个控制器应该为哪些 URL 提供服务,还让您知道哪个控制器应该为哪个 HTTP 方法提供哪些 URL 以及控制器的哪个方法。事实上,我们在 RESTful 约定中使用的大多数 HTTP 方法都可以在 Lumen 中指定。

在为我们的博客示例创建 RESTfulWeb 服务时,我们还将看到代码示例。

中间产品

中间件是可以在控制器提供请求之前或之后出现的东西。许多任务可以在中间件中执行,如身份验证中间件、验证中间件等。

有一些附带流明本身的中间件,而我们也可以编写自己的中间件来满足我们的目的。

服务容器和依赖注入

服务容器是为依赖项注入和依赖项解析提供的工具。开发人员只需告诉应该在何处注入哪个类,服务容器就会解析并注入该依赖关系。

如果类的对象是通过应用程序服务容器创建的,而不是通过应用程序中的new关键字创建的,则依赖项注入可用于解析类的任何依赖项。

例如,Lumen 服务容器用于解析所有 Lumen 控制器。因此,如果他们需要任何依赖关系,服务容器负责解决该问题。为了更好的理解,请考虑下面的例子:

class PostController extends Post
{
    public function __construct(Post $post){
        //do something with $post
    }
}

在前面的示例中,我刚刚提到了简单的Controller类,其中Post类被注入PostController构造函数中。如果我们已经有了另一个我们想要注入的对象,而不是实际的Post对象,我们也可以这样做。

在解决依赖关系之前,您可以在任何地方使用以下代码来完成此操作:

$ourCustomerPost = new OurCustomPost();
$this->app->instance("\Post", $ourCustomerPost);

所以现在,如果Post将是类的构造函数或方法中暗示的类型,那么OurCustomPost类的对象将被注入其中。这是因为$this->app->instance("\Post", $ourCustomerPost)告诉服务容器,如果有人请求\Post的实例,那么就给他们$ourCustomerPost

注意,除了控制器解析之外,如果希望服务容器注入依赖项,我们还可以通过以下方式创建对象:

$postController = $this->app->make('PostController');

所以这里,PostController的解析方式与管腔本身解析控制器的方式相同。请注意,我们之所以使用术语流明,是因为我们谈论的是流明,但流明和拉维中的大多数内容都是相同的。

不要担心,如果这听起来有点压倒性,一旦你开始使用 Lumen 或 Laravel 并在其中进行实际工作,你就会开始理解这一点。

HTTP 响应

Lumen 内置支持发送不同类型的响应、HTTP 状态码和响应头。这是我们之前讨论过的重要问题。对于 web 服务来说,它甚至更为重要,因为 web 服务是由机器以人工方式使用的。机器应该能够知道响应类型和状态代码。这不仅有助于判断是否存在错误或成功,而且有助于判断发生了何种类型的错误。您可以在上更详细地了解这一点 https://lumen.laravel.com/docs/5.4/responses

验证

Lumen 还支持验证;不仅支持验证,还支持您可以开始使用的内置验证规则。但是,如果您需要某些字段的自定义验证逻辑,您也可以编写它。我们将在创建 RESTful web 服务时研究这一点。

雄辩的 ORM

Lumen 附带了一个名为 Eloquent 的 ORM 工具。为了便于理解,您可以将其视为与数据库相关的高级库,通过这些高级库可以在不基于关系的情况下获取大量细节的情况下获得数据。我们将很快在使用它时详细研究它。

数据库迁移和种子设定

现在,开发人员并不总是应该使用 SQL 或数据库工具创建数据库。代码中应该有一些东西可以在版本控制系统下运行,团队中的每个开发人员都可以在他/她的系统或服务器上运行。这就是现在所谓的迁移。编写迁移的另一个好处是它不适用于一个特定的数据库。同样的迁移可以在 MySQL 和 PostgreSQL 上进行。迁移涉及数据库中的结构更改。

迁移用于创建或修改数据库表,或创建不同的约束或索引。同样,播种机也可以在数据库中插入数据。

单元测试

单元测试对于确保代码质量也是非常重要的,Lumen 也为此提供了支持。我们不会在本章中编写测试,但会在后面的一章中编写。

请注意,我们并没有看到 Lumen 附带的每一件事情,我们只是看到了一些组件,为了在 Lumen 中创建 RESTful web 服务,我们可能需要了解这些组件。有关流明的更多详细信息,您只需查阅其文档:https://lumen.laravel.com/docs/5.4

安装管腔

要安装 Lumen,如果已安装 composer,则只需运行以下命令:

composer create-project --prefer-dist laravel/lumen blog

这将创建一个名为blog的目录,其中包含 Lumen 安装。如果您发现任何困难,请参阅此处的流明安装文档:https://lumen.laravel.com/docs/5.4

我建议在安装之后,您去看看这个名为 blog 的 Lumen 项目的目录结构,因为当我们执行不同的任务时,它会更有意义。

配置

如果您查看我们安装 Lumen 的安装目录,在我们的例子中是blog,您将看到一个.env文件。Lumen 将配置保存在.env文件中。您可以看到有一个选项APP_KEY=,如果在.env文件中还没有设置,请设置它。这只需要设置为长度为 32 个字符的随机字符串。

As .env file starts with a dot, in Linux or Mac, this file may be hidden. In order to see this file, you need to see hidden files as well.

然后,要运行 Lumen,只需使用以下命令:

php -S localhost:8000 -t public

如您所见,我们正在使用一个 PHP 内置服务器,并在我们的项目中给出了public目录的路径。这是因为入口点是public/index.php。然后,在上 http://localhost:8000/ ,您应该看到Lumen (5.4.6) (Laravel Components 5.4.*)

如果您看到错误Class 'Memcached' not found,这意味着您没有安装 Memcached,Lumen 正试图在某个地方使用它。如果您不需要 Memcached,只需转到.env文件并更改CACHE_DRIVER=file

现在我们已经安装并配置了 Lumen,我们将为 Lumen 中的博客示例创建相同的 RESTful web 服务。

您应该做的另一件事是取消bootstrap/app.php中的注释。

//$app->withFacades();

//$app->withEloquent();

如前所述,由于这些功能不可用,流明可以更快。但是我们没有注释它,因为我们还需要利用流明的一些功能。那么,这两条线到底有什么作用呢?第一个可以使用正面。我们启用它是因为我们需要一些需要 Facade 的包。第二种方法可以使用 Laravel 和 Lumen 附带的雄辩 ORM。出于性能考虑,默认情况下不会启用 Eloquent。然而,雄辩是一个非常重要的组成部分,我们不应该回避,即使是为了表现,除非表现对我们来说至关重要,并且因为雄辩而放缓。在我看来,我们不应该为了性能而牺牲清晰度,除非它是至关重要的。

设置数据库

我们需要为博客建立数据库。事实上,我们已经在第 3 章中设置了这个,创建了 Restful 端点。我们也可以在这里使用这个数据库。事实上,我们将有相同的数据库结构,所以我们可以很容易地使用相同的数据库,但这是不推荐的。在 Lumen 中,我们使用迁移创建 DB 结构。它不是强制性的,但很有用,所以您可以编写一次迁移,并使用它在任何地方创建 DB 结构。SQL 文件可以实现这一目的,但迁移的好处在于它可以跨不同的 RDBMS 工作。因此,手动创建一个 DB,并将其命名为blog。现在,我们将为结构编写迁移。

编写迁移

要在 Lumen 中创建迁移文件,我们可以使用blog目录中的以下命令创建迁移文件:

php artisan make:migration create_users_table

您将看到类似的内容:

Created Migration: 2017_06_23_180043_create_users_table

并在/blog/database/migrations目录中创建一个同名文件。在这个文件中,我们可以为 Users 表编写迁移代码。如果您打开文件并查看它,其中有两种方法:up()down()up()方法在必须运行迁移时执行,down()方法在必须回滚迁移时执行。

以下是此用户表创建迁移文件的内容:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateUsersTable extends Migration {

   /**
    * Run the migrations.
    *
    * @return void
    */
   public function up()
   {
      Schema::create('users', function(Blueprint $table)
      {
         $table->integer('id', true);
         $table->string('name', 100);
         $table->string('email', 50)->unique('email_unique');
         $table->string('password', 100);

         $table->timestamps();
 });
 }

 /**
 * Reverse the migrations.
 *
 * @return void
 */
 public function down()
 {
     Schema::drop('users');
 }

}

up()方法中,我们在传递函数时调用了 create 方法。该函数具有添加字段的代码。如果您想了解更多关于通过迁移创建字段和表的信息,可以查看https://laravel.com/docs/5.4/migrations#tables

但是,在运行从数据库生成迁移的命令之前,您应该转到您的.env文件并添加您的 DB 名称和凭据。要运行迁移,请运行以下命令:

php artisan migrate

这将运行迁移,并将创建两个表:一个迁移表和一个用户表。用户表是根据前面提到的代码创建的,而迁移表是由 Laravel/Lumen 创建的,因为它保存了运行的迁移记录。此表是第一次创建的,每次运行迁移时都会有更多数据。

Please note that before running migration you should have MySQL or some other database installed and configured in the .env file. Otherwise, if there is no database installed or set up, then migration will not work.

现在,您可以用同样的方法创建 POST 和 comments 表创建迁移文件。以下是 posts 和 comments 表创建迁移文件的内容。

发布迁移文件内容:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreatePostsTable extends Migration {

   /**
    * Run the migrations.
    *
    * @return void
    */
   public function up()
   {
      Schema::create('posts', function(Blueprint $table)
      {
         $table->integer('id', true);
         $table->string('title', 100);
         $table->enum('status', array('draft','published'))->default('draft');
         $table->text('content', 65535);
         $table->integer('user_id')->index('user_id_foreign');

         $table->timestamps();
      });
   }

   /**
    * Reverse the migrations.
    *
    * @return void
    */
   public function down()
   {
      Schema::drop('posts');
   }

}

下面是注释表创建迁移文件:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateCommentsTable extends Migration {

   /**
    * Run the migrations.
    *
    * @return void
    */
   public function up()
   {
      Schema::create('comments', function(Blueprint $table)
      {
         $table->integer('id', true);
         $table->string('comment', 250);
         $table->integer('post_id')->index('post_id');
         $table->integer('user_id')->index('user_id');

         $table->timestamps();
      });
   }

   /**
    * Reverse the migrations.
    *
    * @return void
    */
   public function down()
   {
      Schema::drop('comments');
   }

}

拥有前两个文件后,再次运行以下命令:

php artisan migrate

前面的命令将只执行尚未执行的新迁移文件。这样,数据库中就有了这三个表。由于我们也将进行这些迁移,所以只需再次运行迁移并在 DB 中使用此模式就很容易了。您可能会想,“编写迁移有什么好处?”。好的方面是迁移使得在任何 RDBMS 上部署它变得更容易,因为代码是 Laravel 迁移代码,而不是 SQL 代码。而且,在代码中包含这样的东西总是比较容易的,这样多个开发人员就可以获得彼此的迁移并动态运行它们。

如果您还记得的话,我们还做了一些索引和外键约束。因此,以下是我们如何以迁移的方式做到这一点。

与以前一样,使用命令创建新的迁移文件:

php artisan make:migration add_foreign_keys_to_comments_table

这将为注释表索引创建迁移文件。让我们将内容添加到此文件:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class AddForeignKeysToCommentsTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('comments', function(Blueprint $table)
        {
            $table->foreign('post_id', 'post_id_comment_foreign')->references('id')->on('posts')->onUpdate('RESTRICT')->onDelete('RESTRICT');
            $table->foreign('post_id', 'post_id_foreign')->references('id')->on('posts')->onUpdate('RESTRICT')->onDelete('RESTRICT');
            $table->foreign('user_id', 'user_id_comment_foreign')->references('id')->on('users')->onUpdate('RESTRICT')->onDelete('RESTRICT');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('comments', function(Blueprint $table)
        {
            $table->dropForeign('post_id_comment_foreign');
            $table->dropForeign('post_id_foreign');
            $table->dropForeign('user_id_comment_foreign');
        });
    }

}

同样,为 POST 索引创建一个迁移文件。以下是该文件的内容:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class AddForeignKeysToPostsTable extends Migration {

   /**
    * Run the migrations.
    *
    * @return void
    */
   public function up()
   {
      Schema::table('posts', function(Blueprint $table)
      {
         $table->foreign('user_id', 'user_id_foreign')->references('id')->on('users')->onUpdate('RESTRICT')->onDelete('RESTRICT');
      });
   }

   /**
    * Reverse the migrations.
    *
    * @return void
    */
   public function down()
   {
      Schema::table('posts', function(Blueprint $table)
      {
         $table->dropForeign('user_id_foreign');
      });
   }

}

在前面的索引文件代码中,有一些代码有点复杂,需要我们注意:

 $table->foreign('user_id', 'user_id_foreign')->references('id')->on('users')->onUpdate('RESTRICT')->onDelete('RESTRICT');

这里,foreign()方法接受字段名和索引名。然后,references()方法接受父表中的外键字段名,on()方法参数是被引用的表名(在本例中,它是用户表)。然后,剩下的两种方法onUpdate()onDelete()分别告诉用户更新和删除时要做什么。如果您对迁移语法不太熟悉,那也可以;您只需要查看 Lumen/Laravel 迁移文档。事实上,我建议您暂停片刻,看看与迁移相关的文档:https://laravel.com/docs/5.4/migrations

现在,为了使这些迁移在数据库中有效,我们需要再次运行迁移,以便可以执行新的迁移,并且可以在数据库中反映更改。因此,请运行:

php artisan migrate

这样,我们就完成了迁移。我们现在可以通过种子在这些表中插入一些数据,但我们还不需要它,所以现在跳过编写种子。

编写 RESTful web 服务端点

现在,是时候开始编写我们在、RESTful Web 服务、介绍和动机中讨论过的端点了,并在第 3 章中用普通 PHP 编写了创建 RESTful 端点。那我们就这么做吧。

由于它有一个控制器和模型层,我们将从控制器层开始编写 API,该层将服务于不同的端点。对于第一个控制器,我们要写的是PostController

写入第一个控制器

从技术上讲,这不是第一个控制器,因为 Lumen 附带了 2 个控制器,您可以在/<our blog project path>/app/Http/Controllers/目录中找到。但这是我们将要编写的第一个控制器。在 Laravel(Lumen 的老大哥)中,我们不需要去创建控制器,因为有用于此的命令,但对于 Lumen,这些命令不可用。由于这些命令不是强制性的,但非常方便,因此最好让这些命令可用。

要使用 Lumen 没有的额外功能(其中一些已经在 Laravel 中提供),我们需要安装一个软件包。现在我们需要安装的软件包是flipbox/lumen-generator。有关此软件包的更多信息,请访问https://packagist.org/packages/flipbox/lumen-generator

正如我们在上一章中所看到的,我们通过 composer 安装软件包,所以让我们安装它:

composer require --dev flipbox/lumen-generator

你可以看到我在那里加了一个--dev旗。我这样做是为了避免在生产中使用它,因为这样它将被添加到composer.json中的require --dev部分。

无论如何,一旦安装好,您可以在bootstrap/app.php中注册它的ServiceProvider

if ($app->environment() !== 'production') {
    $app->register(Flipbox\LumenGenerator\LumenGeneratorServiceProvider::class);
}

现在,您可以看到我们有更多可用的命令。您可以通过运行以下命令查看它:

php artisan migrate

那么,让我们用命令创建一个控制器。注意,我们安装它不仅仅是为了创建控制器,但当您使用它时,它会非常方便。无论如何,让我们使用以下命令创建一个控制器:

php artisan make:controller PostController --resource

它将在app/Http/Controllers/PostController.php处创建一个控制器。此命令不仅将创建PostController,还将添加与 REST 资源相关的方法。打开一个文件并查看它。

以下是它生成的内容:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}

生成这些方法是因为我们添加了标记--resource。如果您想知道我是从哪里知道这个标志的,因为它没有列在软件包页面上,我是从的 Laravel 控制器文档中得到的 https://laravel.com/docs/5.4/controllers#resource-控制器。但是,由于这些命令是由于第三方软件包而起作用的,因此 Laravel 文档和这些命令的实际行为可能会有所不同,但由于这些命令是为了复制 Lumen 的 Laravel 命令而执行的,它们很可能非常相似。

无论如何,我们有PostController和方法。让我们逐一实现这些方法。

但是,请注意,在 Lumen 和 Laravel 中,与其他 PHP MVC 框架不同,每个 URL 都应该在路由中被告知,否则就无法访问。路由是一种唯一的入口点,不像CodeIgniter这样的框架,路由是可选的。在流明中,路由是强制性的。换句话说,控制器的每种方法都只能通过路由访问。

所以在继续PostController之前,让我们为后端点添加路由,否则PostController将没有用处。

内腔路径

在 Lumen 中,默认情况下,路由位于/routes/web.php。我说默认是因为这个路径可以更改。不管怎样,去routes/web.php看看吧。您将看到它自己返回一个响应,而不是指向任何控制器。所以,您应该知道,它是返回响应还是使用控制器返回响应取决于路由。然而,请注意,只有在不涉及太多逻辑的情况下,从路由关闭返回响应才有意义。在我们的例子中,我们将主要使用控制器。

以下是添加第一条路线时我们的路线的外观:

<?php

/*
|----------------------------------------------------------------
| Application Routes
|----------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/

$app->get('/', function () use ($app) {
    return $app->version();
});
 $app->get('/api/posts', [
    'uses' => 'PostController@index',
    'as' => 'list_posts'
]);

粗体代码是我们编写的。这里,get in$app->get()用于指定 HTTP 方法。可以是$app->post(),但我们使用$app->get()指定接受GET方法。然后,此方法中有两个参数,您可以在前面的代码中看到。第一个是路由模式,第二个参数是一个关联数组,uses键中包含控制器和方法,as键中包含路由名称:表示域或项目 URL 之后,如果api/posts/是 URL,则应通过PostControllerindex()方法提供服务。虽然路由名称就在那里,但如果您想在代码中按名称指定路由 URL,那么它很有用。

现在,为了检查我们的路线是否正确,并从控制器的索引方法获得响应,让我们在PostController的索引方法中添加一些内容。以下是我们为测试我们的路线而添加的内容:

public function index()
{
 return ['response' =>
        [
            'id' => 1,
            'title' => 'Some Post',
            'body' => 'Here is post body'
        ] ];
}

现在,尝试运行此代码。首先,您需要使用 PHP 内置服务器。转到整个代码所在的blog目录并运行:

php -S localhost:8000 -t public

然后,在浏览器中点击:http://localhost:8000/api/posts,您将看到以下响应:

{"response":{"id":1,"title":"Some Post","body":"Here is post body"}}

如您所见,我们的路由通过PostControllerindex()方法工作和服务,如果您返回一个数组,Lumen 将其转换为 JSON 并作为 JSON 返回。

要进一步查看某些 URL 映射到特定控制器的特定方法的路由列表,只需运行:

php artisan route:list

您将看到路由的详细信息,告诉您哪个 URL 模式与哪段代码关联。

剩余资源

这是路线的一个非常基本的例子,通过PostController方法提供服务。但是,如果你看一下PostController,它还有 4 种方法,我们还需要服务 4 个端点,正如我们在、RESTful Web 服务、介绍和动机中讨论的,并在第 3 章创建 RESTful 端点中实现。所以,我们需要对其他 4 种方法的管腔做同样的事情。要使用这 4 种方法映射 4 个端点,我们不需要再使用 4 条路由。我们可以简单地添加一个基于资源的路由,它将基于 REST 的 URL 模式映射到PostController中的所有方法。

当我们通过命令行创建PostController时,它创建了一个资源控制器,这意味着它有必要的方法来服务 RESTful 端点。所以在routes/web.php文件中,我们应该简单地用资源路由替换之前编写的代码。现在,通过在 routes 文件中使用以下语句,我们应该能够将所有 RESTful 端点映射到PostController方法:

$app->resource('api/posts', 'PostController');

不幸的是,这条资源路线在 Laravel 中可用,但在 Lumen 中不可用。Lumen 使用不同的路由器以获得更好的性能。然而,这种资源方法也非常方便,如果我们还有 4-5 个 RESTful 资源,我们可以在 4-5 个语句而不是 16-20 个语句中映射它们的所有端点。所以,这里有一个小技巧,可以让这个资源路由在 Lumen 中可用。可以将此自定义方法添加到同一路由文件中。

function resource($uri, $controller)
{
    //$verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'];

    global $app;

    $app->get($uri, $controller.'@index');
    $app->post($uri, $controller.'@store');

    $app->get($uri.'/{id}', $controller.'@show');
    $app->put($uri.'/{id}', $controller.'@update');
    $app->patch($uri.'/{id}', $controller.'@update');

    $app->delete($uri.'/{id}', $controller.'@destroy');
} 

因此,总体而言,我们的路由文件如下所示:

<?php

/*
|----------------------------------------------------------------
| Application Routes
|----------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/

 function resource($uri, $controller)
{
    //$verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'];

    global $app;

    $app->get($uri, $controller.'@index');
    $app->post($uri, $controller.'@store');

    $app->get($uri.'/{id}', $controller.'@show');
    $app->put($uri.'/{id}', $controller.'@update');
    $app->patch($uri.'/{id}', $controller.'@update');

    $app->delete($uri.'/{id}', $controller.'@destroy'); }

$app->get('/', function () use ($app) {
    return $app->version();
});

resource('api/posts', 'PostController');

粗体代码是我们添加的。因此,正如您所看到的,我们已经在 routes 文件中定义了一次resource()函数,并且我们可以将其用于所有 REST 资源路由。然后在最后一行,我们使用资源函数将所有的api/posts端点映射到PostController各自的方法。

现在您可以通过点击http://localhost:8000/api/posts来测试它。我们现在无法测试其他端点,因为我们没有在PostController其他方法中编写任何代码。但是,您可以使用以下命令查看存在哪些路由:

php artisan route:list

在我们的例子中,此命令将在命令行上产生类似的结果:

在这里,我们可以看到这正是基于我们在第 1 章RESTful Web 服务、介绍和动机中讨论的 RESTful 资源约定将路径映射到PostController方法。现在,对于 POST 端点,我们已经完成了路由。现在,我们需要在控制器中添加代码,以便它可以向数据库添加数据以及从数据库获取数据。下一步是创建一个模型层,并在控制器中使用它,然后返回正确的响应。

雄辩的 ORM(模型层)

雄辩是一种带有拉维和内腔的 ORM。它负责与数据库相关的操作以及数据库关系。ORM对象关系映射基本上是用数据库中的关系(表)映射对象。不仅如此,基于关系,您可以在另一个表的关系的基础上获取一个表的数据,而无需进行低级细节。这不仅节省了我们的时间,而且使我们的代码更干净。

创建模型

我们现在要创建模型。模型层与数据库相关,因此我们还将提及其中的数据库关系。让我们为所有三个表创建模型。型号名称为UserPostComment,与userspostscomments表格相关。

我们不需要创建用户模型,它带有流明。为了创建 Post 和 Comment 模型,让我们运行下面的命令,该命令通过使用flipbox/lumen-generator包变得可用。运行以下命令以创建模型:

这将在app目录中创建一个Post模型:

php artisan make:model Post

这将在app目录中创建Comment模型:

php artisan make:model Comment

如果您查看这些模型文件,您会发现这些是由雄辩的模型继承的类;因此,这些模型都是基于雄辩的模型,具有雄辩模型的特点。

注意,根据雄辩的约定,如果模型的名称是 Post,则表的名称将是 Post,即模型名称的复数形式。同样,对于 Comment 模型,它将是 comments 表。如果我们的表名不同,我们可以覆盖它,但我们不这样做,因为在我们的例子中,我们的表名和模型名是根据相同的约定。

Eloquent 是一个需要讨论的大话题,但是我们将使用它来制作 API,所以我将讨论 Eloquent 在服务于我们的目的时的用法。我认为这是有意义的,因为很多细节已经在 Eloquent 的文档中了,所以关于 Eloquent 的更多细节,请参考这里的 Eloquent 文档:https://laravel.com/docs/5.4/eloquent

雄辩的关系

在模型层,尤其是从 ORM 继承时,有两件重要的事情:

  • 我们应该有模型,这样我们可以通过它们访问数据
  • 我们应该指定关系,以便充分利用 ORM 的功能

为了只访问数据而不编写查询,我们还可以使用查询生成器。但是,关系的优势在于,它只随 ORM 的使用而来。因此,让我们指定所有模型的关系。

首先,让我们指定用户的关系。由于用户可以有很多帖子,用户可以有很多评论,用户模型将与帖子和评论模型都有hasMany关系。这是指定关系后用户模型的外观:

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Laravel\Lumen\Auth\Authorizable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;

class User extends Model implements AuthenticatableContract, AuthorizableContract
{
    use Authenticatable, Authorizable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email',
    ];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [
        'password',
    ];
 public function posts(){
        return $this->hasMany('App\Post');
    }

    public function comments(){
        return $this->hasMany('App\Comment'); }
}

我们唯一添加到用户模型中的是这两个粗体的方法,posts()comments()指定了关系。基于这些方法,我们可以访问用户的帖子和评论数据。这两种方法都告诉我们,用户与PostComment模型都有很多关系。

现在,让我们在Post模型中添加一个关系。由于帖子可以有很多评论,Post模式与Comment模式有很多关系。同时,Post模型与用户模型有许多反向关系,该反向关系为belongsTo关系。这里是添加关系信息后的Post型号代码。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
 public function comments(){
        return $this->hasMany('App\Comment');
    }

    public function user(){
        return $this->belongsTo('App\User'); }
}

如您所见,我们已经指定了 Post 与UserComment模型的关系。现在,这里是有关系的Comment模型。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
 public function post(){
        return $this->belongsTo("App\Post");
    }

    public function user(){
        return $this->belongsTo("App\User"); }
}

如您所见,对于PostUser模型,注释都具有belongsTo关系,这是hasMany()的逆关系。

所以,现在我们有了特定的关系。现在是实施PostController方法的时候了。

控制器实现

让我们首先在PostController``index()方法中添加适当的代码,以返回实际数据。但是为了看到响应中的数据,最好在 users、posts 和 comments 表中插入一些虚拟数据。更好的方法是为此编写种子。但是,如果您不想研究如何编写种子,那么现在可以手动插入种子。

以下是index()方法的一个实现:

public function index(\App\Post $post)
{
    return $post->paginate(20);
}

这里,paginate(20)意味着它将返回一个分页结果,限制为 20。如您所见,我们使用依赖项注入来获取Post对象。这是我们在本章中已经讨论过的内容。

类似地,我们将在这里实现PostController其他方法。这就是PostController代码的样子:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController extends Controller
{

 public function __construct(\App\Post $post)
    {
        $this->post = $post; }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
 return $this->post->paginate(20);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
 $input = $request->all();
        $this->post->create($input);

        return [
            'data' => $input ];
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        return $this->post->find($id);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
 $input = $request->all();
        $this->post->where('id', $id)->update($input);

        return $this->post->find($id);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
 $post = $this->post->destroy($id);

        return ['message' => 'deleted successfully', 'post_id' => $post];
    }
}

如您所见,我们使用 Post 模型并使用其方法执行不同的操作。Lumen 的变量和函数名更容易理解正在发生的事情,但如果您想知道可以使用哪些雄辩的方法,请查看这些方法检查雄辩:https://laravel.com/docs/5.4/eloquent

如果您在那里找不到任何雄辩的方法文档,请注意,我们使用的许多函数都是查询生成器。因此,请参见查询生成器文档,请访问https://laravel.com/docs/5.4/queries

由于CommentController的实现方式类似,所以我建议您自己实现CommentController,因为当您自己实现时,您实际上会学习。

我们错过了什么?

我们完成了为 RESTful 资源端点提供服务的控制器吗?实际上没有,我们错过了很多东西。我们刚刚创建了基本的 RESTfulWeb 服务,它可以让您了解如何使用 Lumen 实现它,但我们错过了很多东西。那么,让我们看看它们,然后一个接一个地做。

验证和阴性案例?

首先,我们只处理积极的案例:这意味着我们没有考虑如果请求不符合我们的假设会发生什么。如果用户使用错误的方法发送数据怎么办?如果用户传递的 ID 不存在记录怎么办?

简言之,我们还没有处理所有这些,但 Lumen 已经在为我们处理一些事情。

如果您尝试使用POST方法点击端点 URL,http://localhost:8000/api/posts/1,则该方法无效。在这些 URL 上,我们只能发送带有GETPUTPATCH的请求。使用GET会触发PostController``show()方法,PUTPATCH会触发update()方法。但是POST方法不应该被允许。事实上,如果您尝试使用POST方法在这些 URL 上发送请求,它将不起作用,并且您也将得到一个Method Not Allowed错误,就像它应该得到的一样。因此,通过定义一次我们的路径,Lumen 将自行处理此类错误。

同样,Lumen 将使错误的 URL 或错误的 HTTP 方法和 URL 组合无效。

除此之外,我们不会让它处理每一个案件,但让我们看看我们必须处理的重要事情;没有这些东西我们的工作就无法完成。

那么,让我们看看PostController的每种方法在验证或缺失用例方面遗漏了什么。

/使用 GET 方法的 api/posts

以下是/api/posts端点的响应(在我的例子中,DB 中只有一条记录):

{
   "current_page":1,
   "data":[
      {
         "id":1,
         "title":"test",
         "status":"draft",
         "content":"test post",
         "user_id":2
      }
   ],
   "from":1,
   "last_page":1,
   "next_page_url":null,
   "path":"http:\/\/localhost:8000\/api\/posts",
   "per_page":20,
   "prev_page_url":null,
   "to":1,
   "total":1
}

如果您还记得我们在第 1 章RESTful Web 服务、介绍和动机中看到的反应,您会看到我们正在获取我们讨论的大部分信息,但我们以不同的格式获取。虽然这很好,因为它提供了足够的信息,但下面是我们如何使其与我们的决定相似。

以下是index()方法将变成什么:

public function index()
{
 $posts = $this->post->paginate(20);
    $data = $posts['data'];

    $response = [
        'data' => $data,
        'total_count' => $posts['total'],
        'limit' => $posts['per_page'],
        'pagination' => [
            'next_page' => $posts['next_page_url'],
            'current_page' => $posts['current_page']
        ]
    ];

    return $response;
}

最重要的是,响应根级别的所有信息都不干净。我们应该消除那些对清晰性有害的东西,因为如果程序员需要花更多的时间来理解某些东西,那么程序员的工作效率就会受到影响。我们应该在一个单独的属性下包含分页相关的信息,该属性可以是分页或元,这样程序员就可以很容易地看到数据和其他属性。

我们做了,但我们是手工做的。现在,让我们结束它。在下一章中,我们将看到这有什么问题,为什么我们手动调用它,以及我们可以做些什么。

/使用 POST 方法的 api/POST

这将触发PostController::store()方法。我们错过的是验证。事实上,Lumen 为我们提供了验证支持以及一些内置的验证规则。流明验证与 Laravel 非常相似,但存在一些差异。我建议您查看一下 Laravel 的验证文档,位于https://laravel.com/docs/5.4/validation 和 Lumen 与 Laravel 的验证差异:https://lumen.laravel.com/docs/5.4/validation

在这里,我们在store()中添加了验证,所以请看添加验证后的代码,然后我们将讨论它:

public function store(Request $request)
{
    $input = $request->all();

 $validationRules = [
        'content' => 'required|min:1',
        'title' => 'required|min:1',
        'status' => 'required|in:draft,published',
        'user_id' => 'required|exists:users,id'
    ];

    $validator = \Validator::make($input, $validationRules);
    if ($validator->fails()) {
        return new \Illuminate\Http\JsonResponse(
            [
                'errors' => $validator->errors()
            ], \Illuminate\Http\Response::HTTP_BAD_REQUEST
        );
 }

 $post = $this->post->create($input);

 return [
 'data' => $post
 ];
}

我们在这里做 3 件事:

  1. 首先,我们设置了以下验证规则:
    1. 对于contenttitle,这些字段是必需的,且长度至少为 1 个字符。
    2. status为必填项,数据库中设置为 ENUM,可发布或草稿。
    3. user_id为必填项,应存在于users表的id字段中。
  2. 然后,我们根据验证规则和输入创建了一个验证器对象,并检查验证器是否失败。否则,我们将继续进行。
  3. 如果验证器失败,它将手动返回一个错误。它返回的错误描述与从验证器获得的错误描述相同,同时手动返回相应的响应代码。这就是我们使用\Illuminate\Http\JsonResponse的原因。第一个参数是响应主体,第二个参数是响应代码。我们可以在\Illuminate\Http\Response中使用一个常量,而不是编写 400 个错误代码。所以我们不需要记住响应代码,阅读我们代码的人也不需要知道 400 状态代码是什么。

Please note that Error code, response code, and HTTP code represent the same thing. So don't get confused if you see them, they are used interchangeably in this book.

/带有 GET 方法的 api/posts/1

这将通过show($id)方法提供。在我们的 show 方法中,我们只是获取记录并返回,但是如果传入 URL 的show()方法中的 ID 不正确,或者记录表明 ID 不存在,该怎么办?因此,如果找不到具有该 ID 的 post,我们只需进行检查以确保返回 404 错误。我们的show()方法代码如下所示:

public function show($id)
{
    $post = $this->post->find($id);
 if(!$post) {
        abort(404);
    }

    return $post;
}

abort()方法将停止执行,并向其传递错误代码。在这种情况下,它只会给出一个 404NotFound 错误。

/api/posts/1 和补丁/放置方法

将通过update()方法提供服务。同样,它基于提供的 ID,所以我们需要检查该 ID 是否有效。下面是我们将要做的:

public function update(Request $request, $id)
{
    $input = $request->all();

    $post = $this->post->find($id);

    if(!$post) {
        abort(404);
    }

    $post->fill($input);
    $post->save();

    return $post;
}

在这里,我们使用了模型的fill()方法,它将在$input 中为 Post 模型分配字段和值,然后使用save()方法保存它。在 Laravel 文档中,您可以看到使用 Elounce 以不同的方式进行插入和更新,这在不同的地方非常方便:https://laravel.com/docs/5.4/eloquent#inserting-以及更新型号

Sometimes, you will see a Laravel documentation link instead of Lumen. It is because Lumen is mostly using the same code as Laravel. The documentation for all those components are mostly written in Laravel's documentation and it isn't replicated in Lumen documentation, so Lumen documentation is good where Lumen is different from Laravel.

这就是我们在update()方法中要做的。

/带 DELETE 方法的 api/posts/1

删除操作将由destroy($id)提供。同样,这取决于来自 API 用户的 ID,因此我们需要进行类似的检查,就像我们对update()show()进行检查一样。下面是它的外观:

public function destroy($id)
{
 $post = $this->post->find($id);

    if(!$post) {
        abort(404);
    }

    $post->delete();

    return ['message' => 'deleted successfully', 'post_id' => $id];
}

这样,我们的PostController将如下所示:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;

class PostController extends Controller
{

    public function __construct(\App\Post $post)
    {
        $this->post = $post;
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $posts = $this->post->paginate(20);
        $data = $posts['data'];

        $response = [
            'data' => $data,
            'total_count' => $posts['total'],
            'limit' => $posts['per_page'],
            'pagination' => [
                'next_page' => $posts['next_page_url'],
                'current_page' => $posts['current_page']
            ]
        ];

        return $response;
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $input = $request->all();

        $validationRules = [
            'content' => 'required|min:1',
            'title' => 'required|min:1',
            'status' => 'required|in:draft,published',
            'user_id' => 'required|exists:users,id'
        ];

        $validator = \Validator::make($input, $validationRules);
        if ($validator->fails()) {
            return new JsonResponse(
                [
                    'errors' => $validator->errors()
                ], Response::HTTP_BAD_REQUEST
            );
        }

        $post = $this->post->create($input);

        return [
            'data' => $post
        ];
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $post = $this->post->find($id);

        if(!$post) {
            abort(404);
        }

        return $post;
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $input = $request->all();

        $post = $this->post->find($id);

        if(!$post) {
            abort(404);
        }

        $post->fill($input);
        $post->save();

        return $post;
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $post = $this->post->find($id);

        if(!$post) {
            abort(404);
        }

        $post->delete();

        return ['message' => 'deleted successfully', 'post_id' => $id];
    }
}

我们现在已经完成了返回正确的响应代码和验证等工作。

用户身份验证

到目前为止,我们缺少的另一件事是用户身份验证。我们在输入中传递user_id,这是错误的。我们这样做是因为我们没有用户身份验证。因此,我们需要一个身份验证机制。然而,除了身份验证之外,我们还需要一个令牌生成机制。事实上,我们还需要刷新令牌。虽然我们可以自己完成这项工作,但我们将安装另一个外部软件包。

在本章末尾开始用户身份验证没有多大意义,因此我们将在下一章中讨论用户身份验证,因为与此相关的内容不同。

其他缺失元素

我们目前缺少的其他内容如下:

  • API 版本控制
  • 速率限制或节流
  • 需要加密
  • 转换或序列化 (这是为了避免在控制器内部形成硬码手动返回格式)

在下一章中,我们将讨论用户身份验证和前面提到的元素,并将进行一些其他改进。

注释资源实现

我将 CommentEndpoints 实现留给您,因为它与 PostEndpoints 实现非常相似。然而,由于 comment 的两个路径不同于其他路径,为了让您了解需要实现什么,我将告诉您将在routes文件中添加什么,以便您可以相应地实现CommentController。以下是routes/web.php文件:

<?php

/*
|----------------------------------------------------------------
| Application Routes
|----------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/

function resource($uri, $controller, $except = [])
{
    //$verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'];

    global $app;

 if(!in_array('index', $except)){
        $app->get($uri, $controller.'@index');
 }

 if(!in_array('store', $except)) {        $app->post($uri, $controller . '@store'); }

    if(!in_array('show', $except)) {        $app->get($uri . '/{id}', $controller . '@show'); };

    if(!in_array('udpate', $except)) {
        $app->put($uri . '/{id}', $controller . '@update');
        $app->patch($uri . '/{id}', $controller . '@update'); }

    if(!in_array('destroy', $except)) {        $app->delete($uri . '/{id}', $controller . '@destroy'); }
}

$app->get('/', function () use ($app) {
    return $app->version();
});

resource('api/posts', 'PostController');
resource('api/comments', 'CommentController', ['store','index']);

$app->post('api/posts/{id}/comments', $controller . '@store');
$app->get('api/posts/{id}/comments', $controller . '@index');

如您所见,我们在resource()中添加了$except数组作为可选的第三个参数,这样,如果我们不想为资源生成某些特定的路由,我们可以在$except数组中传递它。

CommentController中,代码将与PostController非常相似,除了store()index()之外,post_id将是第一个参数并将被使用。

总结

到目前为止,我们已经在一个名为 Lumen 的微框架中创建了 RESTfulWeb 服务端点。我们创建了迁移、模型和路由。我实现了PostController但将CommentController实现留给了您。因此,我们可以看到,我们在第 3 章中讨论的与实现相关的许多问题创建 Restful 端点已经因为使用了框架而得到了解决。我们能够很容易地解决许多其他问题。因此,使用正确的框架和包,我们能够更快地工作。

我们还发现了一些缺失的元素,包括用户身份验证。我们将在下一章中解决这些问题。在下一章中,我们还将从代码方面改进我们的工作。

In this chapter, we were mostly working with Lumen. We looked at it but we were trying to proceed to make our API and so we were not able to see each and every part of Lumen and its code in detail. So, it is a good idea to see Lumen's documentation: https://lumen.laravel.com/docs/5.4/validation.

For better understanding, you should look at Laravel's documentation, as some common components are explained mostly in Laravel's documentation: https://laravel.com/docs/5.4.

Other than the documentation of Laravel and Lumen, it is a very good idea and is recommended to go to http://laracasts.com/ and see videos on Laravel. Don't worry if you don't find much stuff on Lumen, it is very similar to Laravel. Other than a few changes, they are pretty much same. To understand Laravel and/or Lumen, Lara casts is an excellent resource and is very popular in the Laravel community. Lara casts are mostly by Jeffrey Way. I learned a lot from him and hope that you will learn as well. It will not only teach you Laravel but also teach you how to develop something, and how you should do development.