言午月月鸟
编程,带娃以及思考人生
首页
编程
带娃
思考人生
编程画图秀
hyperf 学习小记 - 3 精简入门实践教程
dingusxp
11912
## 摘要 | 条目 | 说明 | |--|--| |内容说明|可以快速套用熟悉的MVC模式进入实战的最简引导教程| |上手难度|中低| |花费时间|边看边练,1小时左右| ## 练习目标 可以试着以最原始的练习题目 —— 实现一个留言板功能 —— 为小目标去学习,将下面的知识串联起来。 ## 常用组件/功能 ### 配置 此处仅介绍主要需要关注的文件: **环境变量 .env** 默认没有,请手动拷贝 .env.example 生成。并注意:.env 文件 **不要** 提交到代码库中。 在此文件中配置的内容,在其它配置中可以通过 ```env('[KEY_NAME]', '[DEFAULT]')``` 读取。 注意: - 除了数个保留字(true/false/empty/null)外,所读取到的值均为 string 类型。 - 不要在非配置文件中,直接使用 env 函数读取环境变量! **config目录** 框架依赖关键配置,关注: ``` config/config.php # 设置应用名等基本信息 config/autoload/server.php # 配置server,如调整IP和端口 config/autoload/databases.php # 数据库配置 config/autoload/redis.php # redis 配置 ``` 说明: 系统需要适应不同环境的配置,建议都在 .env 中设置,然后在配置里适配。可参考默认配置文件: config.php / databases.php / redis.php。 config 目录下的文件(可自定义添加)都会在框架初始化时扫描完毕。使用时通过 ```config('[KEY_PATH]', '[DEFAULT]')``` 函数进行读取。 需要注意的是,autoload 目录下的 key 的访问路径,要加上文件名,即:[文件名].[中间节点名].[终节点名]。 如 config/autoload/client.php 内容为: ``` ['timeout' => 10]]; 。 ``` 可以通过 ```config('client.request.timeout')``` 获取到 timeout (值为 10)。 而如果该文件放在 config/client.php ,则不需要文件名前缀,即使用 ```config('request.timeout')``` 获取。 ### 路由 编辑文件 config/routes.php ``` request 访问,是一个 PSR-7 标准的组件。 常用的主要方法: ``` // 参数相关 // 获取指定请求参数(POST + GET)。 $this->request->input('[PARAM NAME]', '[DEFAULT]') // 获取所有参数 $this->request->all() // 补充: // 1. 对于数组类参数,可以通过 '[KEY_PATH]' 的方式访问(与 config 类似) // 2. 如果 请求体为 json,且 http 头指定了 content-type: application/json,将自动展开为数组 // 只从Query参数获取(等同于 $_GET) $this->request->query('[PARAM NAME]', '[DEFAULT]') // 从 cookie 中获取(等同于 $_COOKIE) $this->request->cookie('[PARAM NAME]', '[DEFAULT]') // 获取所有 cookie 参数 $this->request->getCookieParams() // 获取上传文件对象(Hyperf\HttpMessage\Upload\UploadedFile / null) $this->request->file('[PARAM NAME]') // 获取路由中配置了模式匹配的参数 $this->request->route('[KEY]', '[DEFAULT]') // URL相关。以 http://127.0.0.1/user/123?from=ad1 为例 $this->request->path() // /user/123 $this->request->url() // http://127.0.0.1/user/123 $this->request->fullUrl() // http://127.0.0.1/user/123?from=ad1 // 请求相关 // 请求方法,如 GET/POST/HEAD/PUT $this->request->getMethod() // 判断是否是 XX 类型请求 $this->request->isMethod('[METHOD]') ``` 更多内容可以参考官方文档: [基础功能 - 请求](https://hyperf.wiki/2.0/#/zh-cn/request) #### response 对象 通过 $this->response 访问,是一个 PSR-7 标准的组件。 常用的主要方法: ``` // 返回数据格式化为 json $this->response->json($data) // 返回数据格式化为 xml $this->response->xml($data) // 返回数据格式化为 原始文本(text/plain) $this->response->raw($data) // 重定向 // 可设置第二个状态码参数,默认值为 302 $this->response->redirect('/anotherUrl') // 设置 cookie $cookie = new Cookie('key', 'value'); $this->response->withCookie($cookie) // 下载文件 $this->response->download($localPath, $downloadFileName); ``` **注意:** 其中设置 cookie 和 设置 header 使用了 withXXX 方法。以 with 开头的方法,将返回新的 Response 对象,不会干扰本身(公共)实例。 更多内容可以参考官方文档: [基础功能 - 请求](https://hyperf.wiki/2.0/#/zh-cn/response) #### container 对象 容器,即 ApplicationContext::getContainer() ,用来加载各种类的实例。 ``` // 加载自己写的 Service 类实例 // 注意得到的是全局单例实例 $service = $this->container->get(\App\Service\Demo::class); // 加载 redis 协程实例 $redis = $this->container->get(Hyperf\Redis\Redis::class); ``` 关于容器更多内容可以阅读官方文档: [核心架构 - 依赖注入](https://hyperf.wiki/2.0/#/zh-cn/di) ### 使用视图(可选) 现在一般都前后端分离了,较少情况需要写 模板,不使用的可以跳过。 hyperf 也没有内置 view 组件,需要安装: ``` # 安装 视图支持 composer require hyperf/view # 安装 视图引擎 composer require duncan3dc/blade # 安装 task 机制支持 composer require hyperf/task ``` #### 配置 view 编辑 config/autoload/view.php ``` BladeEngine::class, // 不填写则默认为 Task 模式,推荐使用 Task 模式 'mode' => Mode::TASK, 'config' => [ // 若下列文件夹不存在请自行创建 'view_path' => BASE_PATH . '/storage/view/', 'cache_path' => BASE_PATH . '/runtime/view/', ], ]; ``` #### 配置 task 编辑 config/autoload/server.php ,参考增加或编辑如下配置项 ``` return [ // 这里省略了其它不相关的配置项 'settings' => [ // Task Worker 数量,根据您的服务器配置而配置适当的数量 'task_worker_num' => 8, // `Task` 主要处理无法协程化的方法,推荐设为 `false`,避免协程下出现数据混淆的情况 'task_enable_coroutine' => false, ], 'callbacks' => [ // Task callbacks SwooleEvent::ON_TASK => [Hyperf\Framework\Bootstrap\TaskCallback::class, 'onTask'], SwooleEvent::ON_FINISH => [Hyperf\Framework\Bootstrap\FinishCallback::class, 'onFinish'], ], ]; ``` #### 使用 1. 在配置的模板目录添加自己的模板文件,如 storage/view/index.php; 2. 在 Controller 中使用视图渲染,示例如下: ``` public function index() { $username = trim($this->request->input('username', "guest")); return $render->render('index', ['username' => $username]); } ``` ### 使用数据库 #### 安装 安装 db-connection 组件,以便使用 hyperf/database(基于 illuminate/database) ``` composer require hyperf/db-connection ``` #### 配置 编辑 config/autoload/database.php ``` return [ // 如果有多库配置,同级新建一个 key,内容参考 default 'default' => [ 'driver' => env('DB_DRIVER', 'mysql'), // 考虑后续扩展性,建议配置读写分离 // 如果确定不需要,将下面读写分离配置部分改为直接设置 host 和 port 两个键 // === 读写分离配置 START === // 'read' => [ 'host' => ['192.168.1.1'], ], 'write' => [ 'host' => ['196.168.1.2'], ], // sticky 参数的意思是:如果一个请求周期里,触发了写操作,后续读也走写库,避免写完立刻读取读不到的情况 'sticky' => true, // === 读写分离配置 END === // 'database' => env('DB_DATABASE', 'hyperf'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'charset' => env('DB_CHARSET', 'utf8'), 'collation' => env('DB_COLLATION', 'utf8_unicode_ci'), 'prefix' => env('DB_PREFIX', ''), 'pool' => [ 'min_connections' => 1, 'max_connections' => 10, 'connect_timeout' => 10.0, 'wait_timeout' => 3.0, 'heartbeat' => -1, 'max_idle_time' => (float) env('DB_MAX_IDLE_TIME', 60), ], 'options' => [ // 框架默认配置 PDO::ATTR_CASE => PDO::CASE_NATURAL, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, PDO::ATTR_STRINGIFY_FETCHES => false, // 如果使用的为非原生 MySQL 或云厂商提供的 DB 如从库/分析型实例等不支持 MySQL prepare 协议的, 将此项设置为 true PDO::ATTR_EMULATE_PREPARES => false, ], ], ]; ``` #### 使用 Db ``` use Hyperf\DbConnection\Db; // 使用 default 配置 Db::select('SELECT * FROM user;'); Db::connection('default')->select('SELECT * FROM user;'); // 使用 test 配置 (假设已添加了一个与 default 同级的 test 的配置) Db::connection('test')->select('SELECT * FROM user;'); // 查: 类似 pdo prepare 方式设置参数; 结果集中是 stdClass 对象 $users = Db::select('SELECT * FROM `user` WHERE gender = ?', [1]); foreach($users as $user){ echo $user->name; } // 增:返回是否成功 bool $inserted = Db::insert('INSERT INTO user (id, name) VALUES (?, ?)', [1, 'Hyperf']); // 改:返回受影响的行数 int $affected = Db::update('UPDATE user set name = ? WHERE id = ?', ['John', 1]); // 删:返回受影响的行数 int $affected = Db::delete('DELETE FROM user WHERE id = ?', [1]); // 事物:闭包函数中出现异常则自动回滚;全部成功则自动提交 Db::transaction(function () { Db::table('user')->update(['votes' => 1]); Db::table('posts')->delete(); }); // 手动事物:务必谨慎! Db::beginTransaction(); try{ // Do something...(这里千万别随便 return。。。) Db::commit(); } catch(\Throwable $ex){ Db::rollBack(); } ``` 更多直接使用 Db 查询的用法参考: [数据库模型 - 查询构造器](https://hyperf.wiki/2.0/#/zh-cn/db/querybuilder) #### 使用模型(可选) 衍生自 Eloquent ORM,有脚手架可以辅助生成 Model 类。 配置好 config/autoload/databases.php (或者 .env 文件),数据库建好表,运行: ``` /php bin/hyperf.php gen:model [TABLE_NAME] ##### 说明:示例的建表语句 ##### CREATE TABLE `guestbook` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', `user_name` varchar(64) NOT NULL DEFAULT '' COMMENT 'guest name', `message` varchar(1024) NOT NULL DEFAULT '' COMMENT 'message', `created_at` int(10) unsigned NOT NULL DEFAULT '0', `updated_at` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `created_at` (`created_at`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='guestbook list'; ``` 将在 app/Model 目录自动创建好同名类。 基本增删改查命令: ``` use \App\Model\Guestbook; // 【新增】 $guestbook = new Guestbook(); $guestbook->username = $userName; $guestbook->message = $message; $guestbook->save(); // 【删除】 // 注意:使用 delete 方法时必须建立在某些查询条件基础之上, // 无 where 条件会导致删除整个数据表 Guestbook::query()->where('id', 1)->delete(); // 知道主键时,可以直接指定删除(参数可以为数组,批量删除) Guestbook::destroy(1); // 【修改】 // 通过模型保存 $guestbook = Guestbook::query()->where('id', 1)->first(); $guestbook->username = '张三'; $guestbook->save(); // 批量修改 Guestbook::query()->where('username', 'guest')->update(['username' => '匿名']); // 【查询】 // 获取最新的留言列表,按创建时间倒序 GuestbookModel::query()->orderBy('created_at', 'desc')->take(10)->offset(0)->get(); // 获取留言总数 GuestbookModel::query()->count(); ``` 更多用法请参考: [数据库模型 - 模型](https://hyperf.wiki/2.0/#/zh-cn/db/model) 以及 [laravel - Eloquent](https://laravel.com/docs/5.8/eloquent) ### 使用 redis (可选) #### 安装 ``` composer require hyperf/redis ``` #### 配置 编辑 config/autoload/redis.php ``` return [ // 如果有多库配置,同级新建一个 key,内容参考 default 'default' => [ 'host' => env('REDIS_HOST', 'localhost'), 'auth' => env('REDIS_AUTH', ''), 'port' => (int) env('REDIS_PORT', 6379), 'db' => (int) env('REDIS_DB', 0), // 集群配置 // 'cluster' => [ // 'enable' => (bool) env('REDIS_CLUSTER_ENABLE', false), // 'name' => null, // 'seeds' => [], // ], 'pool' => [ 'min_connections' => 1, 'max_connections' => 10, 'connect_timeout' => 10.0, 'wait_timeout' => 3.0, 'heartbeat' => -1, 'max_idle_time' => (float) env('REDIS_MAX_IDLE_TIME', 60), ], ], ]; ``` #### 使用 ``` use Hyperf\Utils\ApplicationContext; // 获取当前容器 $container = ApplicationContext::getContainer(); // 获取协程客户端 $redis = $container->get(Hyperf\Redis\Redis::class); // 测试 get // 更多方法 同 Redis 实例 $result = $redis->get('demo-key'); ``` ### 代码分层 建议新建一层 Service 做主要的业务逻辑处理,或者进一步细分也可以。 按照自己的实际情况定好规范和边界,在 app 目录下创建目录,按照文件访问规则命名就行。 ## 启动/停止 不像 cgi 模式下,改了直接刷浏览器就可以;hyperf 项目每次更新代码后都需要停止+启动。 直接把两个命令合起来搞一个 restart.sh 放在项目目录吧。 ``` # 进入项目目录 cd `dirname $0` # 停止 # 直接 kill if [ -f ./runtime/hyperf.pid ] then kill -9 `cat ./runtime/hyperf.pid` fi # 启动 php bin/hyperf.php start > ./runtime/run.log 2>&1 & # 成功启动后,默认为 deamon 模式,将常驻运行; # 主进程名为 config/config.php 中配置的 [app_name].Master,如 skeleton.Master ``` ### 代码修改自动生效 开发环境可以考虑使用 [fileboy](https://gitee.com/dengsgo/fileboy) 来监视文件修改,并自动执行 restart.sh 脚本重启。可以像 CGI 模式一样,修改完直接浏览器查看效果。 ## 思想转变 CGI 方式下,代码的生命周期(几乎)都仅在请求级别运行;现在则是在自己创建的 server 之上运行。 全局资源共享能带来更高的性能。 但能力越大,责任越大。记住: - 系统稳定性要求更高。代码要更加严谨,原先即使 fatal 错误也只影响1个请求,现在则可能搞挂整个服务; - 不要使用有阻塞作用的方法,如 sleep/usleep、原生 curl 库; - 框架中(通过注入)提供的对象(大多)是全局单例。不要随便修改属性,会影响到其它请求。 一些常用使用小技巧: - 使用 Hyperf\Utils\Context 的 get/set 方法共享请求级别的数据; - 很多对象提供了创建新实例的方法,如 Response 的 withXXX 方法,在需要进行请求的个性化设置时可用对应方法创建新实例再处理。 ## 代码参考 如果实现上有问题或者懒得敲代码,可以直接参考: https://gitee.com/dingusxp/hydemo ## 文档参考 [hyperf 2.0 官方文档](https://hyperf.wiki/2.0/)
粤ICP备19051469号-1
Copyright©dingusxp.com - All Rights Reserved
Template by
OS Templates