以下内容均整理自 hyperf官网
安装
swoole编译参数
./configure --enable-http2 --enable-openssl --with-openssl-dir=/usr/local/opt/openssl
php.ini关闭短标签
swoole.use_shortname = 'Off'
查看swoole信息
php --ri swoole
composer使用全局阿里云镜像
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
composer安装项目
composer create-project hyperf/hyperf-skeleton your_project_name
路由
- routes.php中编辑,类似于laravel
- 基于 Annotation 的注释路由
# 对应类名方法名 如127.0.0.1:9501/index/index
@AutoController()
# 修改前缀 127.0.0.1:9501/home/index
@AutoController(prefix="home")
# 类注释 127.0.0.1:9501/index1/index2
@Controller(prefix="index1")
# 对应的方法注释
@RequestMapping(path="index2", methods={"GET", "POST"})
# 不受Controller限制,增加/即可
# 127.0.0.1:9501/login
@RequestMapping(path="/login", methods={"GET", "POST"})
# 除了RequestMapping外还有 PostMapping、GetMapping等
IoC与DI
PSR11
IoC,即控制反转,把对象的调用权交给容器,通过容器来实现对象的装配和管理
DI,即依赖注入,对象之间的依赖关系由容器在运行期决定,由容器动态的将依赖关系注入到对象之中
DI是对IoC更完善的描述
谁依赖谁?对象实例化依赖容器
为什么要依赖?对象实例化通过容器自动得到外部依赖
谁注入谁?容器注入对象的依赖到对象中
注入了什么?注入了对象的外部依赖
Hyperf的注入
构造函数注入
@Inject注入
简单对象注入
抽象对象注入
工厂对象注入
# 简单对象注入
# 构造函数注入
public function __construct(UserService $userService)
# 注解注入
/**
* @Inject()
* @var UserService
*/
private $userService;
# 抽象对象注入
# 在dependencies文件中配置绑定关系
# 这样做的好处是,更换实现类只需要更改配置
return [
"dependencies" => [
UserServiceInterface::class => UserService::class
]
];
public function __construct(UserServiceInterface $userService)
# 工厂对象注入
return [
"dependencies" => [
UserServiceInterface::class => UserServiceFactory::class
]
];
# 工厂类实现__invoke方法
public function __invoke(ContainerInterface $container)
{
$config = $container->get(ConfigInterface::class);
$enableCache = $config->get("cache.enable", false);
// make(string $name, array $args = [])
// 效果等同于new,使用make是为了允许AOP介入
return make(UserService::class, compact('enableCache'));
}
AOP与OOP
AOP面向切面编程,通过预编译方式或运行期动态代理实现程序功能的统一维护的一种技术
AOP在字面上与OOP相似,但设计思想在目标上有着本质的差异
OOP是针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间的低耦合性的隔离效果
Hyperf中的AOP
只需要理解一个概念即可,即Aspect(切面)
Aspect可以切入任意类或任意注解类
被切入的类必须由DI管理
相对于其它框架实现的AOP功能的使用方式,Hyperf不做过细的划分(如Before、After、AfterReturning、AfterThrowing)
Hyperf仅存在环绕(Around)一种通用的形式
Hyperf的AOP是基于DI实现的
必须使用Hyperf的DI组件,即hyperf/di
必须通过DI创建的对象才能使AOP生效,直接new不行
必须当代理类缓存文件不存在时才会重新生成代理类
AOP的应用场景,参数校验、日志、无侵入埋点、安全控制、性能统计、事务处理、异常处理、缓存、无侵入监控、资源池、连接池管理等
@Cacheable、@CachePut、@CacheEvict、@Task、@RateLimit、@CircuitBreaker、调用链追踪的无侵入埋点
Runtime下有切面代理类缓存
中间件模式 PSR15
洋葱模型
php bin/hyperf.php gen:middleware TestMiddleware
通过注解使用中间件
# 使用注解定义中间件的前提是路由也是使用注解定义的
@Middlewares(
@Middleware(TestMiddleware::class),
@Middleware(Test2Middleware::class)
)
@Middleware(TestMiddleware::class)
配置文件定义全局中间件
# 这个http对应的是config/server.php中的name
return [
'http' => [
\App\Middleware\Test2Middleware::class
],
];
通过路由配置使用中间件
Router::addRoute(['GET', 'POST', 'HEAD'], '/', 'App\Controller\IndexController@index', ['middleware' => [\App\Middleware\TestMiddleware::class, \App\Middleware\Test2Middleware::class]]);
Router::addGroup("/", function () {
Router::get("", [\App\Controller\IndexController::class, 'index']);
}, ['middleware' => [\App\Middleware\Test2Middleware::class]]);
中间件中修改值同步到协程上下文
# 中间件中修改$request需要使用override
# 否则Controller中是获取不到值的
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$request = Context::override(ServerRequestInterface::class, function () use ($request) {
return $request->withAttribute("test", "haha");
});
var_dump($request->getAttribute("test"));
return $handler->handle($request);
}
生命周期
# 容器是单例,是长生命周期对象
# make方法创建的对象是短生命周期对象
# Hyperf\Utils\ApplicationContext::getContainer()
自定义异常
# 配置文件,从上往下执行,类似于中间件
return [
'handler' => [
'http' => [
\App\Exception\Handler\MyExceptionHandle::class,
App\Exception\Handler\AppExceptionHandler::class,
],
],
];
# 定义自定义异常类
namespace App\Exception;
class MyException extends \Exception
{
}
# 定义自定义异常handle
namespace App\Exception\Handler;
use App\Exception\MyException;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class MyExceptionHandle extends ExceptionHandler
{
public function handle(Throwable $throwable, ResponseInterface $response)
{
// 停止冒泡,看需求加上
$this->stopPropagation();
return $response->withStatus(500)->withBody(new SwooleStream($throwable->getMessage()));
}
public function isValid(Throwable $throwable): bool
{
return $throwable instanceof MyException;
}
}
# 自定义异常完成,需要处抛出自定义异常即可
throw new MyException("err");
事件机制 PSR14
# 定义自定义事件类
namespace App\Event;
class UserRegiesterEvent
{
/**
* @var int
*/
public $userId;
public function __construct(int $userId)
{
$this->userId = $userId;
}
}
# 生成事件监听类
php bin/hyperf.php gen:listener sendSmsListener
/**
* 多个事件是无序的,理论上事件不应该有依赖关系
* 可以通过 priority 从大到小排序
* @Listener(priority=9)
*/
class sendSmsListener implements ListenerInterface
{
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function listen(): array
{
return [
UserRegiesterEvent::class
];
}
/**
* @param UserRegiesterEvent $event
*/
public function process(object $event)
{
echo "给用户ID为{$event->userId}的用户发送消息";
return true;
}
}
# 分发事件
/**
* @Inject()
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
public function index(RequestInterface $request)
{
$userId = mt_rand(1, 999);
$this->eventDispatcher->dispatch(new UserRegiesterEvent($userId));
}
命令行脚本
生成命令类 php bin/hyperf.php gen:command testCommand
# php bin/hyperf.php test:command 名称1必填 名称2非必填
public function __construct(ContainerInterface $container)
{
$this->container = $container;
parent::__construct('test:command');
}
public function configure()
{
parent::configure();
$this->setDescription('Hyperf Demo Command');
$this->addArgument('name1', InputArgument::REQUIRED, "名称1必填");
$this->addArgument('name2', InputArgument::OPTIONAL, '名称2非必填', "名称2默认值");
}
public function handle()
{
$name1 = $this->input->getArgument("name1");
$name2 = $this->input->getArgument("name2");
$this->line("Hello {$name1} {$name2}", 'info');
}
# php bin/hyperf.php test:command --test "ha ha" --test=nihao -t "ss bb"
public function configure()
{
parent::configure();
$this->setDescription('Hyperf Demo Command');
$this->addOption("test", "t", InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, "名字" ,["test"]);
}
public function handle()
{
$name = $this->input->getOption("test");
$name = implode(",", $name);
$this->line("Hello {$name}", 'info');
}
# 一些常用的输出类型
$this->output->warning("警告");
$this->output->error("错误");
$this->output->writeln("<error>错误的背景色</error>");
$input = $this->output->ask("What's your name?", "null");
$this->output->writeln($input);
$input = $this->output->choice("安装缓存工具?", ["redis", "memcached"]);
$this->output->writeln($input);
$input = $this->output->confirm("安装redis吗?", false);
$this->output->writeln($input);
$this->output->progressStart(100);
for ($i = 0; $i < 10; ++$i) {
$this->output->progressAdvance(10);
sleep(1);
}
$this->output->progressFinish();
$this->line("finish");
发布组件配置
php bin/hyperf.php vendor:publish hyperf/foo
配置
ConfigInterface
config全局函数
@value注解
内存溢出
Hyperf框架容器中的对象都是单例,如果单次请求的数据不要设置到属性上
设置在协程上下文中 Context::set(__CLASS__ . "key_name", "内容");
协程并发
swoole channel
public function index()
{
$channel = new Channel();
co(function () use ($channel) {
$client = $this->clientFactory->create();
$client->get("127.0.0.1:9501/co/sleep?s=3");
$channel->push("111");
});
co(function () use ($channel) {
$client = $this->clientFactory->create();
$client->get("127.0.0.1:9501/co/sleep?s=2");
$channel->push("222");
});
$result[]=$channel->pop();
$result[]=$channel->pop();
return $result;
}
waitGroup
public function index()
{
$wg = new WaitGroup();
$wg->add(2);
$result = [];
co(function () use ($wg, &$result) {
$client = $this->clientFactory->create();
$response = $client->get("127.0.0.1:9501/co/sleep?s=3");
$result['a'] = $response->getBody()->getContents();
$wg->done();
});
co(function () use ($wg, &$result) {
$client = $this->clientFactory->create();
$client->get("127.0.0.1:9501/co/sleep?s=2");
$result['b'] = 2;
$wg->done();
});
$wg->wait();
return $result;
}
parallel
public function index()
{
$parallel = new Parallel();
$parallel->add(function () {
$client = $this->clientFactory->create();
$client->get("127.0.0.1:9501/co/sleep?s=3");
return 222;
}, 'after');
$parallel->add(function () {
$client = $this->clientFactory->create();
$client->get("127.0.0.1:9501/co/sleep?s=2");
return 111;
}, 'first');
$result = $parallel->wait();
// 返回值:{"first":111,"after":222}
return $result;
}
parallel全局函数
$result = \parallel(
[function () {
$client = $this->clientFactory->create();
$client->get("127.0.0.1:9501/co/sleep?s=3");
return 222;
}, function () {
$client = $this->clientFactory->create();
$client->get("127.0.0.1:9501/co/sleep?s=2");
return 111;
}]);
// 返回值:{"1":111,"0":222}
return $result;
重启回收
config/autoload/server.php配置如下
return [
...
'callbacks' => [
SwooleEvent::ON_SHUTDOWN => [App\Exception\ShutdownCallback::class, 'onShutdown']
],
...
];
App\Exception\ShutdownCallback实现相关逻辑
namespace App\Exception;
use App\Com\Log;
use Hyperf\Framework\Bootstrap\ShutdownCallback as BaseShutdownCallback;
use Swoole\Server;
class ShutdownCallback extends BaseShutdownCallback
{
public function onShutdown(Server $server)
{
// 清理相关信息
$this->clearXxx();
// 注意:在 onShutdown 中,数据库、Redis等的连接已被释放
parent::onShutdown($server);
}
protected function clearXxx()
{
Log::customLog("clear", 'clear_xxx.log');
return true;
}
}