hyperf框架的入门到精通

前言

我跳槽了,去了一家电商。
人家都是docker开发,用的框架是hyperf,然后入职第一天,搭了一天环境。
这就是学习的动力,开始吧!加油打工人!!!

数据库配置 (读写分离)

  • Coroutine\MySQL
    协程MySQL客户端,但是人家不推荐使用了,现在推荐使用的是Swoole\Runtime::enableCoroutine+PDO或Mysqli方式,即一键协程化原生PHP的MySQL客户端
  • hyperf/db-connection组件
    基于hyperf/pool实现数据库连接池并对模型进行了新的抽象,以它作为桥梁,hyperf才能把数据库组件及事件组件接进来

composer require hyperf/db-connection
其实composer.json已经有
“hyper/db-connection”:“XXX”,
所以可以忽略这条

database配置在哪?
在config/autoluad/databases.php

如何配置读写分离?

普通

return [
	'dafault'=>[
		'driver'=>env('DB_DRIVER','mysql'),
		'host'=>env('DB_HOST','localhost'),
		'database'=>env("DB_DATABASE",'hyperf'),
		'port'=>env("DB_PORT",'3306'),
		....
		]
]

读写分离
主要就三个参数 read write sticky

return [
	'dafault'=>[
		'driver'=>env('DB_DRIVER','mysql'),
		'read'=>[
			'host'=>['192.168.1.1'],
		],
		'write'=>[
			'host'=>['192.168.1.2'],
		],
		'sticky'=>true,
		'database'=>env("DB_DATABASE",'hyperf'),
		'port'=>env("DB_PORT",'3306'),
		....
		]
]

.env文件里面放一些 数据库的配置

DB_DRIVER =mysql
DB_HOST =127.0.0.1
DB_PORT=3306
DB_DATABASE=hyperf
DB_USERNAME=root

创建表

php bin/hyperf.php gen:model 表名

增加

获取请求的值和响应 hyperf提供了
Hyperf\HttpServer\Contract\RequestInterface和Hyperf\HttpServer\Contract\ResponseInterface

public function save(RequestInterface $request)
{
	$user = $request->query('username');
	$res = Db::table('user')->insert(
		['username'=>$username]
	);
	return [
	'res'=>$res
	];
}

删除

public function del(RequestInterface $request)
{
	$id=(int)$request->query('id');
	$res = Db::table('user')->where('id','=',$id)->delete();
	return [
	'res'=>$res
	];
}

查询

ps:alt + ctrl 点鼠标 能把缺少的包引入
Query查询类
use Hyperf\DbConnection\Db

查单条

public function getOne()
{
	$user = Db::select("select * from `user` where id=?",[1]);//返回array
	//或者
	User::query()->where('id',1)->get()->toarray();
	return $user;
}

查多条

public function getList()
{
	$users = Db::select("select * from `users` where id>=? and id<=?",[0,100])
	return $users;
}

路由

注解路由

顾名思义就是注解里面有,路由的信息,代码说一下

/**
* @return array
* @GetMapping(path="index3",methods="get,post")
*/
public function index3()
{
	return '123';
}

会生成一条域名/index/index3

ps:有时候,有些代码是灰的,用ctrl 鼠标点一下,建议把什么包引入,点完就发现有个包引入了use Hyperf\HttpServer\Annotation\RequestMapping;

注解路由首先是控制器的名 然后是方法名

<?php
declare(strict_types=1);

namespace App\Controller;

use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;

/**
 * @Controller()
 */
class UserController
{
    // Hyperf 会自动为此方法生成一个 /user/index 的路由,允许通过 GET 或 POST 方式请求
    /**
     * @RequestMapping(path="index", methods="get,post")
     */
    public function index(RequestInterface $request)
    {
        // 从请求中获得 id 参数
        $id = $request->input('id', 1);
        return (string)$id;
    }
}

route文件的路由

get post head首先了解一下子
config/routes.php文件配置

//意思是只能通过get获取 域名/index 访问到app/controller/IndexController 的index方法   这是方法一
Router::get('/index','App\Controller\IndexController::index');
//还可以用@
Router::get('/index','App\Controller\IndexController@index');
//还可以用数组方式
Router::get('/index',[App\Controller\IndexController::class,'index']);

//注册任意HTTP METHOD路由
Router::addRoute(['GET','POST','HEAD'],'/','App\Controller\IndexController@index')
//闭包定义路由
Router::get('/hello',function (){
    return 'hello';
});
//还有一种方式偷懒方法  路由组
Router::addGroup('/user/',function(){
    Router::get('index',[App\Controller\UsersController::class,'index']);
    Router::post('info',[App\Controller\UsersController::class,'info']);
});

//注册与方法名一致的HTTP METHOD的路由
Router::get($uri,$callback);
Router::post($uri,$callback);
Router::put($uri,$callback);
Router::patch($uri,$callback);
Router::delete($uri,$callback);
Router::head($uri,$callback);

//注解路由 注意三个要点
//1.不要在routes.php文件中增加相关配置
//2.use Hyper\HttpServer\Annotation\AutoController;
//3.添加以下的注释  
/**
* @AutoController()
*/
//访问 是通过 域名+/控制器名/+方法名访问
//如果不想用控制器名的话用下面的方法
/**
* @AutoController(prefix="user")
*/
//此时访问 是通过 域名+/user/+方法名 访问

//如果是增加了一层user目录 ,注解是下面这样写的,那怎么访问呢
/**
* @AutoController()
*/
//此时访问是 域名+/user/+/控制器名/+方法名

//ps:plugins 安装php annotations的扩展  就有自动引入的功能了 方便极了


//还有种注解形式配置路由,@Controller 需要搭配 @RequestMapping,@GetMapping,@PostMapping,@PutMapping 等注释一起使用

/**
* @Controller(prefix="index")
*/
class IndexController extends AbstractController
{
	/**
	* @RequestMapping(path="index", methods={"get","post"})
	*/
	public function index()
	{
		return 1;
	}
}
//地址访问 域名+/index/+index

//有个小技巧 如果遇到一种情况,不想用注解里面的index,可以将
@RequestMapping(path="/over",methods={"get","post"})

//地址访问 域名+/over

//@GetMapping(path="/over")  get访问 域名+/over

在Controller目录下新建IndexController控制器

<?php
namespace App\Controller;


class IndexController extends AbstractController
{

    public function index()
    {
        return 'hi';
    }


}

UsersController.php

<?php
namespace App\Controller;


class UsersController extends AbstractController
{


    public function index()
    {
        return 'index';
    }


    public function info()
    {
        return 'info';
    }


}

hyperf的依赖注入

hyperf里面用到了ioC(控制反转) DI(依赖注入)
容器将依赖注入对象,对象实例化通过容器自动得到外部依赖

由hyperf/di组件提供功能支持

注入方式

通过构造方法注入
namespace App\Service
class userService
{
	public function getInfoById(int $id)
	{
		return (new Info())->fill($id);
	}
}


如果其他的类想要用引入这个类的话,应该怎么办呢?

namespace App\Controller;
use App\Service\UserService;
class IndexController
{
	/**
	* @var UserService
	*/
	private $userService;
	//通过构造函数的参数上声明参数类型完成自动注入
	public function __construct(UserService $userService)
	{
		$this->userService = $userService;
	}
	
	public function index()
	{
		//此时可以直接使用了
		return $this->userService->getInfoById(1);
	}
}
通过@inject注解注入
namespace App\Controller;
use App\Service\UserService;
use Hyperf\Di\Annotation\Inject; //这个要引入
class IndexController
{
	/**
	* 通过'@Inject' 注释注入由 '@var' 注解声明的属性类型对象
	*
	* @Inject               
	* @var UserService
	*/
	private $userService;
	public function index()
	{
		//直接使用
		return $this->userService->getInfoById(1);
	}
}

注入类型

简单对象注入
抽象对象注入
namespace App\Service;
interface UserServiceInterface
{
	public function getInfoById(int $id);
}

class UserService implements UserServiceInterface
{
	public function getInfoById(int $id)
	{
		return (new Info()->fill($id));
	}
}

还没完,要在对应的位置进行接口类与实现类的关系绑定

use App\Service\UserServiceInterface;
use App\Service\UserService;
//在config/dependencies.php内完成关系配置
return [
	'dependencies'=>[
	UserServiceInterface::class=>UserService::class
	],
];

controller 怎么使用?

namespace App\Controller;
use App\Service\UserServiceInterface;
use Hyperf\Di\Annotation\Inject;
class IndexController
{
	/**
	* @Inject
	* @var UserServiceInterface
	*/
	private $userService;
	public function index()
	{
		return $this->userService->getInfoById(1);
	}
}
工厂对象注入
namespace App\Service;
class UserService implements UserServiceInterface
{
	/**
	* @var bool
	*/
	private $enableCache;
	public function __construct(bool $enableCache)
	{
		//接收值并储存于类属性中
		$this->enableCache = $enableCache;
	}
	public function getInfoById(int $id)
	{
		return (new Info()->fill($id));
	}
}

新建一个工厂类

namespace App\Service;
use Hyperf\Contract\ConfigInterface;
use Psr\Container\ContainerInterface;
class UserServiceFactory
{
	//实现一个 __invoke()方法来完成对象的生产
	//方法参数会自动注入一个当前的容器实例
	public function __invoke(ContainerInterface $container)
	{
		$config = $container->get(ConfigInterface::class)
	};
	//我们假设对应的配置的key 为cache.enable
	$enableCache = $config->get('cache.enable',false);
	//make(string $name,array $paramters=[])方法
	//等同于 new,使用make()方法时为了允许AOP的介入 而直接new会导致AOP无法正常介入流程
	return make(UserService::class,compact('enableCache'));
}

定义一个工厂类,于__invoke()方法内实现对象的创建并返回
make()函数创建短声明周期对象
ps: compact 函数是取 enableCache的变量的值 就是。

array('enableCache'=>$enableCache); 
//compact 就是取的$enableCache;

这还没完,别忘了在配置里面加

use App\Service\UserServiceInterface;
use App\Service\UserServiceFactory;
//config/dependencies.php
return [
	'dependencies'=>[
UserServiceInterface::class=>UserServiceFactory::class
	],
];

还可以注入容器本身

直接注入 Psr\Container\ContainerInterface
通过Hyperf\Utils\ApplicationContext::getContainer()获得
注意DI容器创建出来的对象是个单例,是个长生命周期的对象,通过$container->make()方法或者make()函数创建短生命周期对象

自定义注解

namespace App\Annotation;
use Hyper\Di\Annotation\AnnotationInterface;

/**
* @Annotation
* @Target({"CLASS","METHOD"})
*/
class Foo implements AnnotationInterface
{
	public function collectClass(string $className):void
	{
	
	}
	public function collectMethod(string $className, $string $target):void
	{
	
	}
	public function collectProperty(string $className, $string $target):void
	{
	
	}
}

//其实不用这么写  因为AbstractAnnotation 已经帮我们做了,用下面的写法

namespace App\Annotation;
use Hyperf\Di\Annotation\AbstractAnnotation;
/**
* @Annotation
* @Target({"CLASS","METHOD"})
*/
class Foo extends AbstracAnnotation
{
	/**
	* 假如想接收一下属性
	* @var string
	*/
	public $bar;

	/**
	* @var string
	*/
	public $bab;
	
	public function __construct($value = null)
	{
		var_dump($value);
		parent::__construct($value);
		//如果bab是一个可选参数用下面的写法,控制器文件注解那里@Foo(bar="123", bab="321") 换成 @Foo(123)
		$this->bindMainProperty('bar',$value);
	}

	public function collectClass(string $className):void
	{
		parent::collectClass($className);
	}
	
	
}


怎么用?

namespace App\Controller;

use App\Annotation\Foo;
use Hyperf\HttpServer\Annotation\AutoController;

/**
* @AutoController()
* @Foo(bar="123", bab="321")
*/
class IndexController
{
	public function index()
	{
		var_dump(AnnotationCollector::getClassByAnnotation(Foo::class));
		return 1;
	}
}

//打印结果是个数组




AOP是什么

  • 面向切面编程,通过预编译方式或者运行期动态代理实现程序功能的统一维护的一种技术
  • AOP是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果

每个方法的前后就是切面。
Hyperf的AOP是基于DI实现的
必须使用的是Hyperf的DI组件,即hyperd/di
必须通过DI创建的对象才能使AOP生效,直接new不行
必须当代理类缓存文件不存在时才会重新生成代理类

AOP的应用场景

  • 参数校验,日志,无侵入埋点,安全控制,性能统计,事务处理,异常处理,缓存,无侵入监控,资源池,连接池管理等
    例子

  • 假设有一个IndexController类

  • 通过AOP对该类切入

  • 创建一个Aspect类,并在$classes属性定义该类的index方法

class IndexController
{
	public function index(RequestInterface $request)
	{
		return 'hello';
	}
}
namespace App\Aspect;
class FooAspect extends AbstractAspect
{
	//要切入的类,可以多个,也可以通过::标识到具体的某个方法,通过*可以模糊匹配
	public $classes = [
		SomeClass::class,
		'App\IndexController::index',
	];
}

Aspect 实际通过DI得到的类为下图的一个代理类,储存于runtime/container/proxy
是一个继承原类的子类,原类方法会被重写

class IndexController_sdhafkjshdladjla8374 extends IndexController
{
	use \Hyper\Di\Aop\ProxyTrait;
	public function index(RequesInterface $request)
	{
		$__function__ = __FUNCTION__;
		$__method__ = __METHOD__;
		return self::__proxyCall(IndexController::class,__FUNCTION__,self::getParamsMap(IndexController::class,__FUNCTION__,func_get_args(),function(RequestInterface $request) use($__function__,$__method__){
		});
	}
}

基于AOP的功能

  • @Cacheable(通过这个注解定义在任意类的方法上,可以对,对应方法的结果缓存) @CachePut(更新缓存) @CacheEvict(移除缓存)
  • @Task 在任意类的任意方法上定义Task注解 ,表明在调用方法时,将任务分配到task work里面执行,意思是假如有同步阻塞的代码,要在协程里面运行,就能用task
  • @RateLimit 限流的注解
  • @CircuitBreaker 熔断器
  • 调用链追踪的无侵入埋点

完整例子

对目标indexController.php前后添加业务逻辑

namespace App\Controller;
use Hyper\HttpServer\Annotation\AutoController;

/**
* @AutoController()
*/
class IndexController
{
	public function index()
	{
		return 2;
	}
}

1.首先在app下面新建一个文件夹,Aspect
2.新建一个方法indexAspect.php

namespace App\Aspect;
use Hyperf\Di\Aop\AbstractAspect;    //这里
use Hyperf\Di\Aop\ProcessdingJoinPoint;  //这里

/**
* @Aspect()       //这里
*/
class IndexAspect extends AbstractAspect
{
	//定义要切入的类   这里
	public $classes = [
		IndexController::class.'::'.'index',
	];
	public function process(ProceedingJoinPoint $proceedingJoinPoint)
	{
		//to do 目标类之前 可以添加点逻辑
		var_dump('before');
		$rusult = $proceedingJoinPoint::process();
		//to do 在目标类之后,可以添加逻辑
		var_dump('after');
		return 'before'.$result.'after';
	}
}

注意:此时如果将indexController.php 中返回值改成3,发现返回值还是before2after
是因为有代理类缓存, 将app/runtime/container 删除
命令:rm -rf runtime runtime/container && php bin/hyperf.php start

例子2

namespace App\Aspect;
use Hyperf\Di\Aop\AbstractAspect;    //这里
use Hyperf\Di\Aop\ProcessdingJoinPoint;  //这里

/**
* @Aspect()       //这里
*/
class IndexAspect extends AbstractAspect
{
	//定义要切入的类   这里
	public $annotations = [
		Foo::class,
	];
	public function process(ProceedingJoinPoint $proceedingJoinPoint)
	{
		$result = $proceedingJoinPoint->process();
		$bar = 0;
		$foo = $proceedingJoinPoint->getAnnotationMetadata()->class[Foo::class];//这里可以换成根据不同的类获取
		$bar = $foo->bar;
	
		return $result+$bar;
	}
}

namespace App\Controller;
use App\Annotation\Foo;
use Hyperf\HttpServer\Annotation\AutoController;
/**
* @AutoContraller()
* @Foo(bar="5")
*/
class IndexController
{
	public function index()
	{
		return 3;
	}
}

如何接收参数,保证数据不会混淆

namespace App\Controller;
use Hyper\HttpServer\Annotation\AutoController;
use Hyper\HttpServer\Contract\RequestInterface;

/**
* @AutoController()
*/
class CoController
{
	private $foo = 1;
	
	public function get()
	{
		return $this->foo;
	}
	
	public function index(Requestinterface $request)
	{
		$foo = $request->input('foo');
		$this->foo = $foo;
		return $this->foo;
	}
}
//这样取有个不好的地方,就是有一个地方改了,所有的地方的foo获取的值都变了

那怎么处理?

namespace App\Controller;
use Hyper\HttpServer\Annotation\AutoController;
use Hyper\HttpServer\Contract\RequestInterface;
use Hyper\Utils\Context;
/**
* @AutoController()
*/
class CoController
{
	private $foo = 1;
	
	public function get()
	{
		return Context::get('foo','null');
	}
	
	public function index(Requestinterface $request)
	{
		$foo = $request->input('foo');
		Context::set('foo',$foo);
		return Context::get('foo');
	}
}

如果不想这么写。还可以通过魔术方法__get __set获取

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Utils\Context;
/**
* @AutoController()
* @property int $foo     //用于消除foo的下划线 
*/
class CoController
{
	
	public function get()
	{
		return $this->foo;
	}
	
	public function index(Requestinterface $request)
	{
		$foo = $request->input('foo');
		$this->foo = $foo;
		return $this->foo;
	}
	public function __get($name)
	{
		return Context::get(__CLASS__.":".$name);
	}
	public function __set($name)
	{
		return Context::set(__CLASS.':'.$name,$value);
	}
}

为了不影响接收到的参数混淆,因为他是单例,所以要把值放到上下文。

Hyperf里的协程三种方式

1.根据hyper\Utils\Coroutine

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;

/**
* @AutoController()
*/
class CoController
{
	public function test()
	{
		Coroutine::create(function(){
			sleep(1);
			var_dump(1);
		});
	}
}

2.swoole提供的全局

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;

/**
* @AutoController()
*/
class CoController
{
	public function test()
	{
		go(function(){
			sleep(1);
			var_dump(1);
		});
	}
}

3.还有一种方式

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;

/**
* @AutoController()
*/
class CoController
{
	/**
	* @Inject()
	* @var \Hyperf\Guzzle\ClientFactory
	*/
	private $clientFactory;

	public function sleep(Requestinterface $request)
	{
		$seconds = $request->query('seconds',1);
		sleep($seconds);
		return $seconds;
	}

	public function test()
	{
		//如果不使用协程
		//$client = $this->clientFactory->create();
		//$client->get('127.0.0.1:9501/co/sleep?seconds=2');
		//$client->$this->clientFactory->create();
		//$client->get('127.0.0.1:9501/co/sleep?seconds=2');
		//return 1;
		//此时耗时4s 输出1
		
		//使用协程
		$channel = new Channel();
		var_dump(1);
		co(function()use($channel){
			//sleep(1);
		
			$client = $this->clientFactory->create();
			var_dump(2);
			$client->get('127.0.0.1:9501/co/sleep?seconds=2');
			var_dump(3);
			$channel->push(123);
		});
		var_dump(4);
		co(function()use($channel){
			//sleep(1);
		
			$client = $this->clientFactory->create();
			var_dump(5);
			$client->get('127.0.0.1:9501/co/sleep?seconds=2');
			var_dump(6);
			$channel->push(321);
		});
		var_dump(7);
		//拿到协程里面push的值
		$result[] = $channel->pop();
		var_dump(8);
		$result[] = $channel->pop();
	
		return $result;
	}
}
//1  2  4  5  7  3  8  6

下面用WaitGroup实现

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;

/**
* @AutoController()
*/
class CoController
{
	/**
	* @Inject()
	* @var \Hyperf\Guzzle\ClientFactory
	*/
	private $clientFactory;

	public function sleep(Requestinterface $request)
	{
		$seconds = $request->query('seconds',1);
		sleep($seconds);
		return $seconds;
	}

	public function test()
	{
		//使用协程
		$wg = new WaitGroup();//new 一个实例
		$result = [];
		$wg->add(2);//几个协程就add几个  调用add方法创建协程
		co(function()use($wg, &$result){
			$client = $this->clientFactory->create();
			$client->get('127.0.0.1:9501/co/sleep?seconds=2');
			$result[] = 123;
			$wg->done();//调用done方法,意思是协程执行完毕了,给add的计数器减1
		});
		
		co(function()use($wg, &$result){
		
			$client = $this->clientFactory->create();
			$client->get('127.0.0.1:9501/co/sleep?seconds=2');
			$result[] = 321;
			$wg->done();
		});
		$wg->wait();//等待 add的计数器变为0
		return $result;
	}
}

这种写法还是不简洁,Hyperf提供了一种写法

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;

/**
* @AutoController()
*/
class CoController
{
	/**
	* @Inject()
	* @var \Hyperf\Guzzle\ClientFactory
	*/
	private $clientFactory;

	public function sleep(Requestinterface $request)
	{
		$seconds = $request->query('seconds',1);
		sleep($seconds);
		return $seconds;
	}

	public function test()
	{
		$parallel = new Parallel();
		$parallel->add(funcion(){
			$client = $this->clientFactory->create();
			$client->get('127.0.0.1:9501/co/sleep?seconds=2');
			return 123;
		},'key1');
		$parallel->add(funcion(){
			$client = $this->clientFactory->create();
			$client->get('127.0.0.1:9501/co/sleep?seconds=2');
			return 321;
		},'key2');

		$result = $parallel -> wait();//wait会返回一个结果
		return $result;
	}
}
//返回结果
//{'key1':123,'key2':321}
//如果不想要键,把key1 key2删掉

如果还是嫌麻烦 ,还有一个parallel全局函数的方法

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;

/**
* @AutoController()
*/
class CoController
{
	/**
	* @Inject()
	* @var \Hyperf\Guzzle\ClientFactory
	*/
	private $clientFactory;

	public function sleep(Requestinterface $request)
	{
		$seconds = $request->query('seconds',1);
		sleep($seconds);
		return $seconds;
	}

	public function test()
	{
		$result = parallel([
			funcion(){
			$client = $this->clientFactory->create();
			$client->get('127.0.0.1:9501/co/sleep?seconds=2');
			return 123;
			},
			funcion(){
			$client = $this->clientFactory->create();
			$client->get('127.0.0.1:9501/co/sleep?seconds=2');
			return 321;
			}
		]);
		
		return $result;
	}
}


hyperf 配置文件的三种获取

第一种通过依赖注入

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Di\Annotation\Inject;
/**
* @AutoController()
*/
class ConfigController
{
	/**
	* @Inject()
	* @var \Hyperf\Contract\ConfigInterface
	*/
	private $config;

	public function inject()
	{
		return $this->config->get('foo.bar',123);//第一个参数这个就是配置文件的key,第二个参数是默认值,如果没有配置的话,就是123,有的话就是321
	}
}

config/config.php


return[
	'foo'=>[
		'bar' => 321;
	],
];

也可以不改配置文件,在config/autoload 文件夹 增加一个foo.php文件

return [
	'bar'=>1234,
];

第二种 通过value 取的

namespace App\Controller;
use Hyperf\Config\Annotation\Value;
use Hyper\HttpServer\Annotation\AutoController;

/**
* @AutoController()
*/
class ConfigController
{

	/**
	* @Value("foo.bar")
	*/
	private $bar;
	
	public function value()
	{
		return $this->bar;
	}
}

第三种形式


namespace App\Controller;
use Hyperf\Config\Annotation\Value;
use Hyper\HttpServer\Annotation\AutoController;

/**
* @AutoController()
*/
class ConfigController
{
	
	public function config()
	{
		return config('foo.bar',123);
	}
}

hyperf中间件

中间件主要适用于编织从请求到响应的整个流程,通过对多个中间件的组织,使数据的流动按照我们预定的方式进行,中间件的本质是一个洋葱模型。
在这里插入图片描述

就是请求来了到1到2到3然后到2到1 到响应
命令 php bin/hyperf.php gen:middleware FooMiddleware
php bin/hyperf.php gen:middleware BarMiddleware
php bin/hyperf.php gen:middleware BazMiddleware

然后。Middleware就会产生三个文件 BarMiddleware.php ,BazMiddleware.php,FooMiddleware.php

在indexController.php 演示一遍

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Annotation\Middlewares;
use Hyperf\Middleware\FooMiddleware;
use Hyperf\Middleware\BarMiddleware;
use Hyperf\Middleware\BazMiddleware;
/**
* @AutoController()
* @Middlewares(
* 	@Middlewares(BarMiddleware::class),
*   @Middlewares(BazMiddleware::class)
* )
*/
class IndexController
{
	//如果想要只是index方法 多使用一个FooMiddleware一个中间件   那加下面的注解
	/**
	* @middleware(FooMiddleware::class)	
	*/
	public function index()
	{
		return 'index';
	}
}





//如果FooMiddleware是全局的中间件,那就在config/autoload/middlewares.php 里增加


use App\Middleware\FooMiddleware;

return [
	'http'=>[
		FooMiddleware::class,
	],
];
//这里的http 要和 config/autoload/server.php  里面的匹配
//执行顺序 先全局 后类注解 之后是方法上的注解
//如果用中间件用注解来表示的话,那此时路由也必须用注解路由  @AutoController

值在中间件能传递,但是在controller是取不到值的,这是因为controller的值是通过上下文获取到,怎么改写呢?

FooMiddleware.php


class FooMiddleware
{

	public function process(ServerRequestInterface $request,RequestHandlerInterface $handler)
	{
		$request =Context::override(ServerRequestInterface::class,function()use($request){
		return $request->withAttribute('foo',1);
		});
		$response = $handler->handle($request);
		return $response->withBody(new SwooleStream($body.'foo'));
	}
}


hyperf异常处理(工作必用)

在hyperf里,业务代码都运行在Worker进程上,这就意味着一旦任意一个请求的业务存在没有捕获处理的异常的话,都会导致对应的worker进程被中断退出。

我们可以通过对各个server定义不同的异常处理器,一旦业务流程存在没有捕获的异常,都会被传递到已注册的异常处理器去处理

创建一个TestController.php

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;

class TestController
{
	//协程,每秒输出一个1,不停止的话,是不会中断的
	public function co()
	{
		co(function(){
			while(true){
				echo 1;
				sleep(1);
			}
		});
		return 1;
	}
	public function exception()
	{
		throw new \RuntimeException('test');
	}
}


创建一个异常类 Exception/FooException.php

namespace App\Exception;
class FooException extends

创建handler.php Exception/Handler/FooExceptionHandler.php

namespace App\Exception\Handler;
use App\Exception\FooException;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Psr\Http\Message\ResponseInterface;
use Throwable;


class FooExceptionHandler extends ExceptionHandler
{
		public function handle(Throwable $throwable, ResponseInterface $response)
		{
		$this->stopPropagation();//意味异常到这里就停止了
			return $reponse->withStatus(500)->withBody(new SwooleStream('this is the FooExceptionHandler'));
		}
		
		public function isValid(Throwable $throwable):bool
		{
			return $throwable instanceof FooException;
		}
}

然后要配置
在config/autoload/exception.php

use App\Exception\Handler\FooExceptionHandler;
return[
	'handler'=>[
		'http'=>[
			FooExceptionHandler::class,
			App\Exception\Handler\AppExceptionHandler::class,
		],
	],
];

事件机制

举个例子
创建app/service/UserService.php

namespace App\Service;
use App\Event\UserRegistered;
use Hyperf\Di\Annotation\Inject;
class UserService
{
	/**
	* @Inject()
	* @var \Psr\EventDispatcher\EventDispatcherInterface
	*/
	private $eventDispatcher;

	public function register()
	{
		//注册成功前
		$beforeUserRegister = new BeforeUserRegister();
		$this->eventDispatcher->dispatch($beforeUserRegister);
		
		//注册用户 如果是true就去注册
		//写一个监听器 php bin/hyperf.php gen:listener ValidateRegisterListener  在app/Listener生成ValidateRegisterListener.php
		if($beforeUserRegister->shouldRegister)
		{
			$userId=rand(0,9999);
		}else{
			//不注册
			return false;
		}

		
		//注册成功后
		if($userId){
		
			$this->eventDispatcher->dispatch(new UserRegistered($userId));		
			//然后把发短信发邮件,放到监听器里
			//php bin/hyperf.php gen:listener SendSmsListener
			//然后app/Listener文件就有了 SendSmsListener.php

			//$this->sendSms();
			//$this->sendEmail();
			//同步用户信息到其他系统
			//将用户注册的信息收集到用户行为分析系统
		}
		return $userId;
	}

	public function sendSms()
	{
		//发送短信
		return true;
	}
	public function sendEmail()
	{
		return true;
	}
}

随着逻辑的越来越复杂,我们需要将功能进行解耦
创建 App/Event/UserRegistered.php

namespace App\Event;
class UserRegistered
{
	/**
	* @var int
	*/
	public $userId;
	public function __construct(int $userId)
	{
		$this->userId = $userId;
	}
}

打开SendSmsListener.php

declare(strict_type=1);
namespace App\Listener;
use App\Event\UserRegistered;
use Hyperf\Event\Annotation\Listener;
use Psr\Container\ContainerInterface;
use Hyperf\Event\Contract\ListenerInterface;

/**
* @Listener(priority=10)    括号里面加权重值  格式为priority = 权重值
*/
class SendEmailListenr implements ListenerInterface
{
	/**
	* @var ContainerInterface
	*/
	private $container;

	public function __construct(ContainerInterface $container)
	{
		$this->container = $container;
	}
	
	public function listen():array
	{
		return [
			UserRegistered::class,
		];
	}
	
	/**
	* @param UserRegistered $event
	*/
	public function process(object $event)
	{
		echo '发送短信给'.$event->userId.PHP_EOL;
	}
}

创建一个EventController.php

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;

/**
* @AutoController()
*/
class EventController
{
	/**
	* @Inject()
	* @var \App\Service\UserService
	*/
	private $userService;
	public function test()
	{
		return $this->userService->register();
	}
}

//这是用户注册之后

如果想要在用户之前,附加业务逻辑
创建app/Event/BeforeUserRegister.php
就是来风控

namespace App\Event;

class BeforeUserRegister
{
	/**
	* @var bool
	*/
	public $shouldRegister = true;

}



打开ValidateRegisterListener.php

declare(strict_type=1);
namespace App\Listener;
use App\Event\UserRegistered;
use Hyperf\Event\Annotation\Listener;
use Psr\Container\ContainerInterface;
use Hyperf\Event\Contract\ListenerInterface;

public function __construct(ContainerInterface $container)
{
	$this->container = $container;
}

public function listen():array
{
	return [
		BeforeUserRegister::class,
	];
}

/**
* @param BeforeUserRegister $event
*/
public function process(object $event)
{
	$event->shouldRegister = (bool)rand(0,1);
}

当然也可以监听多个事件

declare(strict_type=1);
namespace App\Listener;
use App\Event\UserRegistered;
use Hyperf\Event\Annotation\Listener;
use Psr\Container\ContainerInterface;
use Hyperf\Event\Contract\ListenerInterface;

/**
* @Listener(priority=10)    括号里面加权重值  格式为priority = 权重值
*/
class SendEmailListenr implements ListenerInterface
{
	/**
	* @var ContainerInterface
	*/
	private $container;

	public function __construct(ContainerInterface $container)
	{
		$this->container = $container;
	}
	
	public function listen():array
	{
		return [
			BeforeUserRegister::class,
			UserRegistered::class,
		];
	}
	
	/**
	* @param UserRegistered $event
	*/
	public function process(object $event)
	{
		if($event instanceof BeforeUserRegister){
			echo get_class($event).$event->shouldRegister;
		}elseif($event instanceof UserRegistered){
			echo get_class($event).$event->userId;
		}
	}
}

请求和响应整理

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;
use Hyperf\HttpServer\Contract\RequestInterface;(保证数据不会在协程中来回调用)
use Hyperf\HttpServer\Request;
use Hyperf\HttpMessage\Base\Request;
use Hyperf\HttpMessage\Server\Request;
use Swoole\Http\Request;
use Swoole\Http2\Request;

use Psr\Http\Message\ResponseInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\HttpServer\Response;
use Hyperf\HttpMessage\Base\Response;
use Hyperf\HttpMessage\Server\Response;
use Swoole\Http\Response;
use Swoole\Http2\Response;

ps:phpstrom中自动开启hyperf
1.edit Configurations
2.PHP Script
3.添加入下图
在这里插入图片描述

表单验证

创建一个表单验证类
在Request文件夹下创建TestRequest.php

namespace App\Request\Test;
use Hyperf\Validation\Request\FormRequest;
class TestRequest extends FormRequest
{
	/**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     */
    public function rules(): array
    {
        return [
            'id' => 'required|int',//id必须的  并且是zheng'xing  
            'reason' => 'string',
        ];
    }
}

控制器里TestController.php

/**
     * 测试
     * @RequestMapping(methods="get,post")
     * @param  TestRequest $request
     * @return array
     */
    public function test(TestRequest $request)
    {
        try {
            $params = $request->validated();

            return ;
        } catch (\Throwable  $e) {
           
            return ;
        }
    }

hyperf用mq实现 导出功能

实现思路,点击导出,将导出任务标识,是否跨系统,当前导出状态,查询条件,mq配置信息标识落表。
添加配置,设置exchage_name,queue_name,route_key,成功后回调的地址。
在当前系统或者别的系统创建消费者,来消费,将excel上传到
云空间,成功后ack掉,并将表里的状态改成完成,且将云空间的地址落表

/**
 * @Consumer(exchange="test_export", routingKey="test_export", queue="test_export", name="TestExportConsumer", nums=1)
 */
class TestExportConsumer extends ConsumerMessage
{
    use Trace; //hyperf composer require hyperf/tracer
    //用来追踪的

    //导出扩展 为了方便
    public const EXPORT_TYPE_INVOICE = 1;//

    //导出状态
    public const COMPLETED_STATUS = 3; //导出完成
    public const EXPORT_ERROR = 5;    //导出错误

   
    /**
     * @Inject
     * @var ClientFactory
     */
    private $clientFactory;

    /**
     * 消费
     * @param $exportData
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @return string
     */
    public function consume($exportData): string
    {
        $this->putTraceId();//
        if (! $exportData) {
        	//记录日志
            return Result::ACK;
        }

        //区分导出编码,用source 标示来源
        $localExportCode = $exportData['taskCode'] . '_' . $exportData['source'];

        //回调使用的请求对象
        $client = $this->clientFactory->create();
        //这里拼接参数 回调的数据
        $options['query'] = ['导出状态' => $exportData['status'], 'url' => ''];

        try {
            //拼接数据,上传云空间 返回地址			
            $url = (self::getExportService(self::EXPORT_TYPE_INVOICE))->export($exportData['queryParams'],$localExportCode);
            $options['query']['url'] = $url;
            $options['query']['task_status'] = self::COMPLETED_STATUS;
            //记录日志 
            return Result::ACK;
        } catch (\Throwable $e) {
        //记录日志抛出异常
            return Result::NACK;
        } finally {
            //回调通知 获取mq中的回调地址(这个地址是从mq获取到的)
            $response = $client->get($exportData['回调地址'], $options);
            $responseContents = $response->getBody()
                ->getContents();
            $responseContents = json_decode($responseContents, true);

           //记录日志
        }
    }

    /**
     * 获取导出对象  1.导出
     * @param  int                              $exportType
     * @return null|TestServicesExport|mixed
     */
    private static function getExportService(int $exportType)
    {
        switch ($exportType) {
            case self::EXPORT_TYPE_INVOICE:
                return self::getTestServicesExportService();
            default:
                throw new \RuntimeException('未知的导出类型');
        }
    }

    /**
     * 导出
     * @return null|TestServicesExport|mixed
     */
    private static function getTestServicesExportService()
    {
        $testServicesExport = Context::get('testServicesExport');
        if (! $testServicesExport instanceof TestServicesExport) {
            $testServicesExport = new TestServicesExport();
            Context::set('testServicesExport', $testServicesExport);
        }
        return $testServicesExport;
    }
}

实现一个队列

假如需要在执行完某个操作后,发送短信

创建一个生产者
在app/Amqp/Producer/ 目录创建一个文件
Sms.php

<?php
declare(strict_types=1);
namespace App\Amqp\Producer;
use Hyperf\Amqp\Annotation\Producer;
use Hyperf\Amqp\Message\ProducerMessage;
use Hyperf\Amqp\Message\Type;
use Sht\Helper\Log;

/**
 * 某个功能成功后 发送提示短信队列.
 * @Producer(exchange="ms.send", routingKey="sms")
 */
class Sms extends ProducerMessage
{
    // 类型
    protected $type = 'dirct';

    /**
     * 生产.
     * @param $data
     */
    public function __construct($data)
    {
        // 记录推送日志
        // 推送消息
        $this->payload = $data;
    }
}

在service
app/modules/services/ 创建服务文件
SmsService.php

<?php

declare(strict_types=1);

namespace App\Modules\Services;
use Hyperf\Amqp\Producer;
use Hyperf\DbConnection\Db;
use Sht\Helper\Log;
use Sht\Helper\Redis;

class AccountStatementService
{
	private static function sendSms($mobile, $text)
    {
        if (empty($mobile) || empty($text)) {
            return;
        }
        $data = [
            'mobiles' => (string) $mobile,
            'text' => $text,
        ];
        // 执行推送
        di(Producer::class)->produce(new Sms($data));
    }
}

未完待续~~

  • 11
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Hyperf 是基于 Swoole 4.5+ 实现的高性能、高灵活性的 PHP 协程框架,内置协程服务器及大量常用的组件,性能较传统基于 PHP-FPM 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均基于 PSR 标准 实现,基于强大的依赖注入设计,保证了绝大部分组件或类都是可替换 与可复用的。 框架组件库除了常见的协程版的 MySQL 客户端、Redis 客户端,还为您准备了协程版的 Eloquent ORM、WebSocket 服务端及客户端、JSON RPC 服务端及客户端、GRPC 服务端及客户端、Zipkin/Jaeger (OpenTracing) 客户端、Guzzle HTTP 客户端、Elasticsearch 客户端、Consul 客户端、ETCD 客户端、AMQP 组件、NSQ 组件、Nats 组件、Apollo 配置中心、阿里云 ACM 应用配置管理、ETCD 配置中心、基于令牌桶算法的限流器、通用连接池、熔断器、Swagger 文档生成、Swoole Tracker、视图引擎、Snowflake 全局 ID 生成器 等组件,省去了自己实现对应协程版本的麻烦。 Hyperf 还提供了基于 PSR-11 的依赖注入容器、注解、AOP 面向切面编程、基于 PSR-15 的中间件、自定义进程、基于 PSR-14 的事件管理器、Redis/RabbitMQ/NSQ/Nats 消息队列、自动模型缓存、基于 PSR-16 的缓存、Crontab 秒级定时任务、Translation 国际化、Validation 验证器等非常便捷的功能,满足丰富的技术场景和业务场景,开箱即用。 框架初衷: 尽管现在基于 PHP 语言开发的框架处于一个百家争鸣的时代,但仍旧未能看到一个优雅的设计与超高性能的共存的完美框架,亦没有看到一个真正为 PHP 微服务铺路的框架,此为 Hyperf 及其团队成员的初衷,我们将持续投入并为此付出努力,也欢迎你加入我们参与开源建设。 设计理念: Hyperspeed + Flexibility = Hyperf,从名字上我们就将超高速和灵活性作为 Hyperf 的基因。 对于超高速,我们基于 Swoole 协程并在框架设计上进行大量的优化以确保超高性能的输出。 对于灵活性,我们基于 Hyperf 强大的依赖注入组件,组件均基于 PSR 标准的契约和由 Hyperf 定义的契约实现,达到框架内的绝大部分的组件或类都是可替换的。 基于以上的特点,Hyperf 将存在丰富的可能性,如实现 Web 服务,网关服务,分布式中间件,微服务架构,游戏服务器,物联网(IOT)等。 运行环境: Linux, OS X or Cygwin, WSL PHP 7.2+ Swoole 4.4+
在引用中提到了一个基于 Hyperf 框架开发的问答系统的接口开发,其中包括了全文检索功能的实现。而在引用中也提到了 Hyperf 框架可以用于开发微服务系列的应用。因此,可以利用 Hyperf 框架来实现全文检索功能。 关于 Hyperf 框架的全文检索功能,具体的实现方式可能需要根据具体的需求和使用的全文检索引擎来进行选择和配置。常见的全文检索引擎有 Elasticsearch、Sphinx、Solr 等。在 Hyperf 框架中,可以通过集成相应的全文检索引擎的组件或使用相关的扩展包来实现全文检索功能。 比如,对于 Elasticsearch,可以使用 Hyperf 的 Elasticsearch 扩展包 `hyperf/elasticsearch` 来实现全文检索功能。可以通过在配置文件中配置 Elasticsearch 的连接信息,然后在代码中使用相应的方法进行索引的创建、搜索等操作。 需要注意的是,在使用全文检索功能时,需要将需要进行全文检索的数据进行合适的索引,并配置相应的搜索条件和权重。具体的使用方法和示例可以参考 Hyperf 的文档和相应的扩展包的文档。 总结起来,要在 Hyperf 框架中实现全文检索功能,首先需要选择合适的全文检索引擎,如 Elasticsearch,并集成相应的组件或使用相关的扩展包。然后,根据具体的需求进行配置和使用相应的方法来实现全文检索功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [hyperf 实战之问答系统接口开发——概述(连载中)](https://blog.csdn.net/m_422744746/article/details/128041190)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mr.杰瑞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值