第三章。创建一个 ThinkPHP 应用程序

在本教程的其余部分,我们将基于 ThinkPHP 框架完成一个天气查询的应用。使用 高德开放平台-天气查询 的接口来实现我们的功能。把查询数据缓存到 MySql 中,这样就不用每次频繁的请求第三方的接口了(有请求次数限制)

选择高德开放平台-天气查询 API 主要是因为它是免费的。当然你也可以使用其他的第三方天气查询接口,看个人喜好。
该应用是一个非常简单的 REST API 应用,主要实现两个接口。

  • GET /weather/:location_id - 从数据库中获取天气(如果存在的话),如果不存在,则从 第三方接口获取。
  • DELETE /weather/:location_id - 从数据库中删除已缓存的天气信息。再次查询该地点的天气时,将从高德的天气接口获取。


在我们进行应用编码之前,首先使用 Docker 安装并运行 ThinkPHP

安装 ThinkPHP Framework

ThinkPHP 是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。ThinkPHP从诞生以来一直秉承简洁实用的设计原则,在保持出色的性能和至简代码的同时,更注重易用性。遵循Apache2开源许可协议发布,意味着你可以免费使用ThinkPHP,甚至允许把你基于ThinkPHP开发的应用开源或商业产品发布/销售。
这就是为什么我选择它作为本教程的教学框架。我不想让你因为一个框架而放弃,但我也不想从头开始建立所有的东西,因为该教程的重点是Docker,而不是我们的PHP应用。

用Docker 创建 ThinkPHP 应用实际上比用本地配置PHP环境所需的操作少。并且为我们还需要使用 Composer,多亏了Docker,我们甚至不需要在主机上安装它。

首先打开你的终端,创建一个项目目录。

  1. $ mkdir docker-app

并进入到该目录中

  1. $ cd docker-app

现在使用[官方Composer Docker镜像](https://hub.docker.com/_/composer/)安装 ThinkPHP 。

  1. $ docker run --rm --interactive --tty -v E:/workplace/docker-app/:/app composer:latest create-project topthink/think weather-app

如果你查看weather-app/目录,你会看到 ThinkPHP 6 的项目目录,如下所示:

  1. dir
  2. E:\workplace\docker-app\weather-app 的目录
  3. 2022/02/11 16:31 <DIR> .
  4. 2022/02/11 16:30 <DIR> ..
  5. 2021/12/16 21:06 231 .example.env
  6. 2021/12/16 21:06 34 .gitignore
  7. 2021/12/16 21:06 2,038 .travis.yml
  8. 2021/12/16 21:06 <DIR> app
  9. 2021/12/16 21:06 1,094 composer.json
  10. 2022/02/11 16:31 36,134 composer.lock
  11. 2021/12/16 21:06 <DIR> config
  12. 2021/12/16 21:06 <DIR> extend
  13. 2021/12/16 21:06 1,822 LICENSE.txt
  14. 2021/12/16 21:06 <DIR> public
  15. 2021/12/16 21:06 1,459 README.md
  16. 2021/12/16 21:06 <DIR> route
  17. 2021/12/16 21:06 <DIR> runtime
  18. 2021/12/16 21:06 180 think
  19. 2022/02/11 16:31 <DIR> vendor
  20. 2021/12/16 21:06 <DIR> view

创建 ThinkPHP 应用的过程中的命令梳理


我们的 docker 运行命令与第二章中的命令相似,但我们使用了不同的镜像。我们没有使用运行hello.php 脚本的 PHP 镜像,而是使用了一个 Composer 镜像。让我们来看看有什么变化。

  • composer:latest - 这表明我们在这个容器中使用的镜像。如果需要,你可以指定Composer的特定版本;只要查看Docker Hub上支持的镜像标签列表。
  • create-project topthink/think weather-app - 这是实际将 ThinkPHP 6 安装到容器工作目录中的命令。因为该工作目录是我们主机目录的一个卷,所以 composer 安装的文件现在在主机和容器中都存在。在这一点上,你的应用程序实际上没有做任何事情,但 ThinkPHP 6 已经被安装了,我们现在对 composer 在 Docker 中如何与 PHP 一起工作有了一些了解。

新建路由与 Controller (逻辑处理) 文件

项目创建完成后,我们需要添加几个路由 URL 和 Controller 文件 。让我们打开 weather-app 目录下的 app/controller , 然后新建 Weather.php 文件,内容如下:

  1. <?php
  2. namespace app\controller;
  3. use app\BaseController;
  4. class Weather extends BaseController
  5. {
  6. public function get()
  7. {
  8. // todo
  9. echo "location_id is ".$this->request->param('location_id');
  10. }
  11. public function delete()
  12. {
  13. // todo
  14. echo "location_id is ".$this->request->param('location_id');
  15. }
  16. }

然后打开 weather-app 目录下的 app/route , 在 app.php 文件中追加如下内容:

  1. Route::get('/weather/:location_id','weather/get');
  2. Route::delete('/weather/:location_id','weather/delete');

运行我们的应用程序

现在我们可以在 Docker 容器中运行我们的应用程序,只是为了验证我们的程序是否运行正常,因为我们只添加了两个路由 URL。打开命令行,运行。

  1. $ docker run --privileged=true --rm -p 38000:80 -v E:/workplace/docker-app/:/var/www/html php:apache

现在,在浏览器中打开 http://localhost:38000/weather-app/public/index.php/weather/1,你应该看到一个空页面,上面有以下文字:

  1. location_id is 1


那么恭喜你,你刚刚已经成功地在 Docker 中运行了你的第一个 ThinkPHP 应用程序。

运行 ThinkPHP 应用的命令梳理


这次我们使用的docker run命令与我们用来运行 hello.php 脚本 和composer create-project ...的两个命令不同。原因是这次我们想获得包含 Apache 的最新版本的PHP,这样我们就可以为我们的 Web 应用提供服务。让我们更详细地了解新增的命令部分。

  • -p 38000:80 - 这里我们在容器和主机系统之间定义了端口映射。这个命令说Docker应该把容器上的80号端口映射到我们主机上的38000号端口。你可以在你的主机上选择任何有效的端口,但使用一个高的端口(10000以上)可能是一个好主意,因为许多低的端口被保留给大多数标准机器上的内置进程。对于容器,你必须*使用80端口,因为那是Docker镜像暴露的端口,Apache在上面运行。
  • —privileged=true - 容器内是否使用 root 权限
  • -v E:/workplace/docker-app/:/var/www/html - 这一次,我们将代码从主机挂载到容器的/var/www/html目录。原因是,PHP的Apache镜像是从这个目录中提供代码的。这是我们之前运行的PHP脚本之间的一个微妙但关键的区别。请务必仔细阅读官方PHP Docker镜像文档以避免遗漏这样的事情。
  • php:apache - 该镜像是官方的 PHP Apache 容器。它包含PHP和Apache(一种流行的Web服务器)在同一个容器中,允许你快速提供代码用于本地开发。当然你也可以选择是使用php:fpm镜像 链接一个nginx的镜像,这些我不会在本书中介绍,需要你自己去了解。
  • 最后,你会注意到,这次在镜像名称后面没有命令。这可能会让新的Docker用户感到困惑,但在默认情况下,即使你没有指定命令,大多数镜像也会运行一些命令。在PHP Apache镜像的例子中,默认的命令是运行一个shell脚本来启动Apache。这个脚本正是我们想要的,所以没有理由去改变它。
    在这一点上,你的终端将显示任何进来的Apache请求,所以当你加载第一个URL时,你可能看到类似的东西。
  1. 172.17.0.1 - - [11/Feb/2022:09:14:02 +0000] "GET /weather-app/public/index.php/weather/1 HTTP/1.1" 200 245 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43"

你可以通过向终端发送一个 “中断 “信号来停止和退出终端。在 windows 上,这可以通过按 Ctrl 和按c来实现。

更多Docker运行选项

在 Detached 模式下运行

运行你的新网络应用程序的另一个选择是在 “Detached“模式中运行容器。这意味着你在终端将不会看到来自你的容器的输出。这可以通过在我们之前的命令中添加-d标志来实现。

  1. $ docker run --privileged=true -d --rm -p 38000:80 -v E:/workplace/docker-app/:/var/www/html php:apache

停止一个容器


在分离模式下启动容器后,你的终端将显示新容器的完整ID—类似于a70d25c2a7cedae673f8ab...如果你想停止这个容器,你可以使用docker stop命令,用容器的ID告诉Docker。比如说

  1. $ docker stop a70d25c2a7cedae673f8ab

因为输入整个ID是很麻烦的,如果你愿意,Docker允许你只输入前三个或更多的字符。

  1. $ docker stop a70

命名容器

最后,我建议为你的容器命名。我们在本书后面的许多例子中都会这样做,因为用名字来记住一个容器比用随机分配的ID要容易得多,再加上ID是随机的,所以每次你运行一个新版本的容器时,它都会得到一个新的ID。只要不是已经有一个同名的容器,名字就可以多次发出来。为了给我们的新应用容器命名,我们可以用传入的--name标志重新创建它。

  1. $ docker run --privileged=true -d --rm --name=weather-app -p 38000:80 -v E:/workplace/docker-app/:/var/www/html php:apache

在使用docker run命令时,还有许多可用的选项,所以你可能想更详细地阅读文档。在我们开发其余的应用程序时,我们会涉及其中的一些选项。


添加高德天气 SDK


现在我们要引入高德的天气 SDK ,在使用该 SDK 之前你需要阅读高德开放平台-天气查询的技术文档,再添加 SDK 之前我们首先要确保所有现有的容器都停止了。

  1. $ docker ps //之后在运行 上面的命令停止容器
  2. docker stop 容器ID

这个命令将列出所有正在运行的容器。你也可以通过添加-a标志来查看停止的容器。
如果有任何容器正在运行,那么在我们继续前进之前,使用docker stop <ID>来停止它们。

添天气 SDK


执行命令添加高德天气 SDK,欢迎点赞+星。这是作者之前封装的SDK,当然你也可以自己重新封装或者用其他的天气 SDK,现在在你的终端运行:

  1. docker run --rm --interactive --tty -v E:/workplace/docker-app/weather-app:/app composer:latest require clydecn/php-amap-weather

该命令将在你的项目中装新的软件包。在这个过程中,你应该在终端看到一些类似这样的输出。

  1. Using version ^1.0 for clydecn/php-amap-weather
  2. ./composer.json has been updated
  3. Running composer update clydecn/php-amap-weather
  4. Loading composer repositories with package information
  5. Updating dependencies
  6. Lock file operations: 7 installs, 0 updates, 0 removals
  7. - Locking clydecn/php-amap-weather (v1.0.0)
  8. - Locking guzzlehttp/guzzle (6.5.5)
  9. - Locking guzzlehttp/promises (1.5.1)
  10. - Locking guzzlehttp/psr7 (1.8.3)
  11. - Locking ralouphie/getallheaders (3.0.3)
  12. - Locking symfony/polyfill-intl-idn (v1.24.0)
  13. - Locking symfony/polyfill-intl-normalizer (v1.24.0)
  14. Writing lock file
  15. Installing dependencies from lock file (including require-dev)
  16. Package operations: 7 installs, 0 updates, 0 removals
  17. - Downloading symfony/polyfill-intl-normalizer (v1.24.0)
  18. - Downloading symfony/polyfill-intl-idn (v1.24.0)
  19. - Downloading ralouphie/getallheaders (3.0.3)
  20. - Downloading guzzlehttp/psr7 (1.8.3)
  21. - Downloading guzzlehttp/promises (1.5.1)
  22. - Downloading guzzlehttp/guzzle (6.5.5)
  23. - Downloading clydecn/php-amap-weather (v1.0.0)
  24. 0/7 [>---------------------------] 0% - Downloading symfony/polyfill-intl-idn (v1.24.0)
  25. - Downloading ralouphie/getallheaders (3.0.3)
  26. - Downloading guzzlehttp/psr7 (1.8.3)
  27. - Downloading clydecn/php-amap-weather (v1.0.0)
  28. - Downloading symfony/polyfill-intl-normalizer (v1.24.0)
  29. - Downloading guzzlehttp/promises (1.5.1)
  30. - Downloading guzzlehttp/guzzle (6.5.5)
  31. - Installing symfony/polyfill-intl-normalizer (v1.24.0): Extracting archive
  32. - Installing symfony/polyfill-intl-idn (v1.24.0): Extracting archive
  33. - Installing ralouphie/getallheaders (3.0.3): Extracting archive
  34. - Installing guzzlehttp/psr7 (1.8.3): Extracting archive
  35. - Installing guzzlehttp/promises (1.5.1): Extracting archive
  36. - Installing guzzlehttp/guzzle (6.5.5): Extracting archive
  37. - Installing clydecn/php-amap-weather (v1.0.0): Extracting archive
  38. 3 package suggestions were added by new dependencies, use `composer suggest` to see details.
  39. Generating autoload files
  40. > @php think service:discover
  41. Succeed!
  42. > @php think vendor:publish
  43. File /app/config/trace.php exist!
  44. Succeed!
  45. 10 packages you are using are looking for funding.
  46. Use the `composer fund` command to find out more!

现在 SDK 已经安装完毕,可以使用了。


调用 SDK API


我们将使用刚刚添加的高德天气 SDK 来完善我们的业务逻辑,打开 controller 目录下的 Weather.php 添加以下内容:

  1. <?php
  2. namespace app\controller;
  3. use app\BaseController;
  4. use Clydecn\Amap\Weather as AmapWeather;
  5. class Weather extends BaseController
  6. {
  7. public $key;
  8. public $weather;
  9. // 初始化
  10. protected function initialize()
  11. {
  12. $this->key = "KEY"; // 高德 KEY
  13. $this->weather = new AmapWeather($this->key);
  14. }
  15. public function get()
  16. {
  17. $rid = $this->request->param('location_id','310000');
  18. // 查询当天天气
  19. $res = $this->weather->getLiveWeather($rid);
  20. return json($res,200);
  21. }
  22. public function delete()
  23. {
  24. // todo
  25. echo "location_id is ".$this->request->param('location_id');
  26. }
  27. }

解疑


我们做了一些更新—主要是对引入天气 API 初始化天气类

  • $this->weather=new AmapWeather($this->key); - 实例化天气API 类
  • $res=$this->weather->getLiveWeather('310000'); - 获取天气信息。
  • return json($res,200); - 最后,返回 Json 数据


    测试


    我们的应用程序已经初步完成了向API传递一个真实的位置ID并返回一些数据。首先,使用这个高德位置查询找到一个位置ID。我使用的是上海的ID进行测试。310000,当然你直接传 上海 也是可以的。ok,让我们再次运行Docker容器。
  1. docker run -d --privileged=true --rm --name=weather-app -p 38000:80 -v E:/workplace/docker-app/:/var/www/html php:apache

并在你的浏览器中访问正在运行的应用程序,地址是http://localhost:38000/weather-app/public/index.php/weather/310000。你应该可以看到一个JSON数据,看起来像这样。

  1. {"status":"1","count":"1","info":"OK","infocode":"10000","lives":[{"province":"上海","city":"上海市","adcode":"310000","weather":"晴","temperature":"11","winddirection":"东","windpower":"≤3","humidity":"38","reporttime":"2022-02-14 14:01:45"}]}

你的 Docker 化的 PHP 应用程序现在正从外部数据源返回真实数据,并在Apache中提供服务,但你可能会注意到,它的速度并不快(我的页面加载时间为1.92秒!)。
高德天气 API 是一个免费的服务,其他国家可能无法访问。为了解决这个问题,我们将把查询的数据保存在我们自己的 MySQL 数据库中,可以再下次访问的时候可以快速地响应。这将极大地提高性能,下个章节我们将学习如何用 Docker 将 MySql 与 PHP 应用程序相结合。