您现在的位置是:首页 >技术杂谈 >PHP之hyperf学习笔记网站首页技术杂谈

PHP之hyperf学习笔记

19岁开始学习 2025-02-20 12:01:02
简介PHP之hyperf学习笔记

Hyperf

Model,Dao,Service,Contronller

路由

  • 使用文件来配置路由,就是和laravel一样的
Router::addGroup([
    "middleware" => ["web", "auth"],
    "namespace" => "HyperfHttpServerController",
    "prefix" => "hyperf",
],function (Router $router){
    Router::get("/", "HyperfHyperfControllerIndexController@index");
});
  • 使用注解去配置路由
    #[Controller(“prefix”)]
    #[AutoController(“prefix”)]
    #[RequestMapping(path:‘index’,method:“get,post”)]
    #[GetMapping(path:“index”)]

依赖注入

  • DI依赖注入
  • IOC控制反转
  • #[Inject]
  • private UserService $user;
  • 所有的DI都是单列模式的
  • /config/dependencies.php 就是可以配置接口类和工厂类的关系
  • //接口::class => 实体类::class 这样就可以注入接口的时候就是自动转化为实体类进行注入 会去调用实体类的public function __invoke 方法
  • DI创建出来的都是单例,都是唯一的,会被所有的协程去使用

AOP

  • 被切入的类必须要是DI管理的,要使用AOP必须要切入,直接new是不行的
  • 定义一个切面 文件夹Aspect创建一个切面类 继承AbstractAspect
  • 这个是切面的函数
<?php
namespace AppAspect;

use AppServiceSomeClass;
use AppAnnotationSomeAnnotation;
use HyperfDiAnnotationAspect;
use HyperfDiAopAbstractAspect;
use HyperfDiAopProceedingJoinPoint;

#[Aspect]
class FooAspect extends AbstractAspect
{
    // 要切入的类或 Trait,可以多个,亦可通过 :: 标识到具体的某个方法,通过 * 可以模糊匹配
    //定义要切入的类 
    public array $classes = [
        SomeClass::class,
        'AppServiceSomeClass::someMethod',
        'AppServiceSomeClass::*Method',
    ];

    // 要切入的注解,具体切入的还是使用了这些注解的类,仅可切入类注解和类方法注解
    public array $annotations = [
        SomeAnnotation::class,
    ];

    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        // 切面切入后,执行对应的方法会由此来负责
        // $proceedingJoinPoint 为连接点,通过该类的 process() 方法调用原方法并获得结果
        // 在调用前进行某些处理
        $result = $proceedingJoinPoint->process();
        // 在调用后进行某些处理
        return $result;
    }
}
  • 直接使用注解配置来达到和上面一样的目的
<?php
namespace AppAspect;

use AppServiceSomeClass;
use AppAnnotationSomeAnnotation;
use HyperfDiAnnotationAspect;
use HyperfDiAopAbstractAspect;
use HyperfDiAopProceedingJoinPoint;

#[
    Aspect(
        classes: [
            SomeClass::class,
            "AppServiceSomeClass::someMethod",
            "AppServiceSomeClass::*Method"
        ],
        annotations: [
            SomeAnnotation::class
        ]
    )
]
class FooAspect extends AbstractAspect
{
    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        // 切面切入后,执行对应的方法会由此来负责
        // $proceedingJoinPoint 为连接点,通过该类的 process() 方法调用原方法并获得结果
        // 在调用前进行某些处理
        $result = $proceedingJoinPoint->process();
        // 在调用后进行某些处理
        return $result;
    }
}
  • 在aspect中获取一些需要的参数 以更灵活的实现aspect
<?php
namespace AppAspect;

use AppServiceSomeClass;
use AppAnnotationSomeAnnotation;
use HyperfDiAnnotationAspect;
use HyperfDiAopAbstractAspect;
use HyperfDiAopProceedingJoinPoint;

#[Aspect]
class FooAspect extends AbstractAspect
{
    public array $classes = [
        SomeClass::class,
        'AppServiceSomeClass::someMethod',
        'AppServiceSomeClass::*Method',
    ];

    public array $annotations = [
        SomeAnnotation::class,
    ];

    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        // 获取当前方法反射原型
        /** @var ReflectionMethod **/
        $reflect = $proceedingJoinPoint->getReflectMethod();

        // 获取调用方法时提交的参数
        $arguments = $proceedingJoinPoint->getArguments(); // array

        // 获取原类的实例并调用原类的其他方法
        $originalInstance = $proceedingJoinPoint->getInstance();
        $originalInstance->yourFunction();

        // 获取注解元数据
        /** @var HyperfDiAopAnnotationMetadata **/
        $metadata = $proceedingJoinPoint->getAnnotationMetadata();

        // 调用不受代理类影响的原方法
        $proceedingJoinPoint->processOriginalMethod();

        // 不执行原方法,做其他操作
        $result = date('YmdHis', time() - 86400);
        return $result;
    }
}
  • 代理类缓存 config/config.php scan_cacheable

注解

  • 没搞清楚使用在哪里 下次在来

事件机制

  • 这个有点像是队列的意思
  • 其实就是为了解耦
  • 定义一个事件 这个事件来了就是要做一些其他的操作 用户注册事件,用户注册完之后可能要做一些其他的事情,比如发送邮箱什么的
  • 定义一个事件
<?php
namespace AppEvent;

class UserRegistered
{
    // 建议这里定义成 public 属性,以便监听器对该属性的直接使用,或者你提供该属性的 Getter
    public $user;
    
    public function __construct($user)
    {
        $this->user = $user;    
    }
}
  • 定义一个监听器
<?php
namespace AppListener;

use AppEventUserRegistered;
use HyperfEventContractListenerInterface;

class UserRegisteredListener implements ListenerInterface
{
    public function listen(): array
    {
        // 返回一个该监听器要监听的事件数组,可以同时监听多个事件
        return [
            UserRegistered::class,
        ];
    }

    /**
     * @param UserRegistered $event
     */
    public function process(object $event): void
    {
        // 事件触发后该监听器要执行的代码写在这里,比如该示例下的发送用户注册成功短信等
        // 直接访问 $event 的 user 属性获得事件触发时传递的参数值
        // $event->user;
        
    }
}
  • 去配置监听器 让这个监听器在监听
  • config/autoload/listeners.php
<?php
return [
    AppListenerUserRegisteredListener::class,
];
  • 通过注解去实现监听器
<?php
namespace AppListener;

use AppEventUserRegistered;
use HyperfEventAnnotationListener;
use HyperfEventContractListenerInterface;

#[Listener]
class UserRegisteredListener implements ListenerInterface
{
    public function listen(): array
    {
        // 返回一个该监听器要监听的事件数组,可以同时监听多个事件
        return [
            UserRegistered::class,
        ];
    }

    /**
     * @param UserRegistered $event
     */
    public function process(object $event): void
    {
        // 事件触发后该监听器要执行的代码写在这里,比如该示例下的发送用户注册成功短信等
        // 直接访问 $event 的 user 属性获得事件触发时传递的参数值
        // $event->user;
    }
}
在通过注解注册监听器时,我们可以通过设置 priority 属性定义当前监听器的顺序,如 #[Listener(priority=1)] ,底层使用 SplPriorityQueue 结构储存,priority 数字越大优先级越高
  • 对监听器的一个使用
<?php
namespace AppService;

use HyperfDiAnnotationInject;
use PsrEventDispatcherEventDispatcherInterface;
use AppEventUserRegistered; 

class UserService
{
    #[Inject]
    private EventDispatcherInterface $eventDispatcher;
    
    public function register()
    {
        // 我们假设存在 User 这个实体
        $user = new User();
        $result = $user->save();
        // 完成账号注册的逻辑
        // 这里 dispatch(object $event) 会逐个运行监听该事件的监听器
        $this->eventDispatcher->dispatch(new UserRegistered($user));
        return $result;
    }
}

中间件

  • config/autoload/middlewares.php 全局中间件
<?php
return [
    // http 对应 config/autoload/server.php 内每个 server 的 name 属性对应的值,该配置仅应用在该 Server 中
    'http' => [
        // 数组内配置您的全局中间件,顺序根据该数组的顺序
        YourMiddleware::class
    ],
];
  • 使用注解的方式 #[Middleware(FooMiddleware::class)]
  • 多个中间件 #[Middleware([FooMiddleware::class,barMiddleware::class])]
  • 生成中间件 php ./bin/hyperf.php gen:middleware Auth/FooMiddleware
<?php

declare(strict_types=1);

namespace AppMiddlewareAuth;

use HyperfHttpServerContractRequestInterface;
use HyperfHttpServerContractResponseInterface as HttpResponse;
use PsrContainerContainerInterface;
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerMiddlewareInterface;
use PsrHttpServerRequestHandlerInterface;

class FooMiddleware implements MiddlewareInterface
{
    protected ContainerInterface $container;

    protected RequestInterface $request;

    protected HttpResponse $response;

    public function __construct(ContainerInterface $container, HttpResponse $response, RequestInterface $request)
    {
        $this->container = $container;
        $this->response = $response;
        $this->request = $request;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // 根据具体业务判断逻辑走向,这里假设用户携带的token有效
        $isValidToken = true;
        if ($isValidToken) {
            return $handler->handle($request);
        }

        return $this->response->json(
            [
                'code' => -1,
                'data' => [
                    'error' => '中间件验证token无效,阻止继续向下执行',
                ],
            ]
        );
    }
}
  • 中间件的执行顺序
// 全局中间件配置文件 middleware.php
return [
    'http' => [
        YourMiddleware::class,
        YourMiddlewareB::class => 3,
    ],
];
  • 中间件的注解方式
// 注解中间件配置
#[AutoController]
#[Middleware(FooMiddleware::class)]
#[Middleware(FooMiddlewareB::class, 3)]
#[Middlewares([FooMiddlewareC::class => 1, BarMiddlewareD::class => 4])]
class IndexController
{
    
}
  • 如果要在中间件中修改$request那么需要使用功能Context去改变
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;

// $request 和 $response 为修改后的对象
$request = HyperfContextContext::set(ServerRequestInterface::class, $request);
$response = HyperfContextContext::set(ResponseInterface::class, $response);
  • 默认情况下在处理路由的时候,找不到或者什么都是执行CoreMiddleware 可以重写这个方法
  • 跨域中间件 hyperf官网有

请求Request

  • 在hyperf中一般注入的都是RequestInterface
  • 常用方法的集合
$request->path();
$request->url();
$request->fullUrl();
$request->getMethod();
$request->all();
$request->input("name",'most');
// 存在则返回,不存在则返回 null
$name = $request->query('name');
// 存在则返回,不存在则返回默认值 Hyperf
$name = $request->query('name', 'Hyperf');
// 不传递参数则以关联数组的形式返回所有 Query 参数
$name = $request->query();
$request->has('name');
$request->cookie('name');
// 存在则返回一个 HyperfHttpMessageUploadUploadedFile 对象,不存在则返回 null
$file = $request->file('photo');
if ($request->hasFile('photo')) {
    // ...
}
if ($request->file('photo')->isValid()) {
    // ...
}
// 该路径为上传文件的临时路径
$path = $request->file('photo')->getPath();

// 由于 Swoole 上传文件的 tmp_name 并没有保持文件原名,所以这个方法已重写为获取原文件名的后缀名
$extension = $request->file('photo')->getExtension();
$file = $request->file('photo');
$file->moveTo('/foo/bar.jpg');

// 通过 isMoved(): bool 方法判断方法是否已移动
if ($file->isMoved()) {
    // ...
}

响应Responce

  • r e s p o n s e − > j s o n ( response->json( response>json(data);
  • r e s p o n s e − > x m l ( response->xml( response>xml(data);
  • $response->raw(‘Hello Hyperf.’);
  • 还有问价下载的方法具体看文档

控制器

  • 实现interface接口类

异常

<?php
namespace AppExceptionHandler;

use HyperfExceptionHandlerExceptionHandler;
use HyperfHttpMessageStreamSwooleStream;
use PsrHttpMessageResponseInterface;
use AppExceptionFooException;
use Throwable;
class FooExceptionHandler extends  ExceptionHandler
{
    public function handle(Throwable $throwable, ResponseInterface $response)
    {
        // 判断被捕获到的异常是希望被捕获的异常
        if ($throwable instanceof FooException) {
            // 格式化输出
            $data = json_encode([
                'code' => $throwable->getCode(),
                'message' => $throwable->getMessage(),
            ], JSON_UNESCAPED_UNICODE);
            // 阻止异常冒泡
            $this->stopPropagation();
            return $response->withStatus(500)->withBody(new SwooleStream($data));
        }
        // 交给下一个异常处理器
        return $response;
        // 或者不做处理直接屏蔽异常
    }
    /**
     * 判断该异常处理器是否要对该异常进行处理
     */
    public function isValid(Throwable $throwable): bool
    {
        return true;
    }
}

缓存

  • SimpleCache
$cache = $container->get(PsrSimpleCacheCacheInterface::class);
  • 这文档缓存讲的不太行

日志

  • 就这样的吧 讲的也不太行

分页器

  • 没啥好讲的 默认是自动读取page的
  • UserModel::paginate(10)

自动化测试

  • 模拟http请求
<?php
use HyperfTestingClient;
$client = make(Client::class);
$result = $client->get('/');

验证器

  • 全局验证器 在server.php 中配置
<?php
return [
    // 下面的 http 字符串对应 config/autoload/server.php 内每个 server 的 name 属性对应的值,意味着对应的中间件配置仅应用在该 Server 中
    'http' => [
        // 数组内配置您的全局中间件,顺序根据该数组的顺序
        HyperfValidationMiddlewareValidationMiddleware::class
        // 这里隐藏了其它中间件
    ],
];
  • 使用方式
  • 1.创建一个验证器
php bin/hyperf.php gen:request FooRequest
  • 2.编写规则
/**
 * 获取应用到请求的验证规则
 */
public function rules(): array
{
    return [
        'foo' => 'required|max:255',
        'bar' => 'required',
    ];
}

-3. 使用

<?php
namespace AppController;

use AppRequestFooRequest;

class IndexController
{
    public function index(FooRequest $request)
    {
        // 传入的请求通过验证...
        
        // 获取通过验证的数据...
        $validated = $request->validated();
    }
}

  • 可以添加一个自定义的异常类来处理这个文件
  • 验证规则要去看

数据库模型

  • 执行原生sql
<?php
use HyperfDbConnectionDb;
$users = Db::select('SELECT * FROM `user` WHERE gender = ?',[1]);  //  返回array 
foreach($users as $user){
    echo $user->name;
}
//执行类
<?php

use HyperfDbConnectionDb;

$inserted = Db::insert('INSERT INTO user (id, name) VALUES (?, ?)', [1, 'Hyperf']); // 返回是否成功 bool

$affected = Db::update('UPDATE user set name = ? WHERE id = ?', ['John', 1]); // 返回受影响的行数 int

$affected = Db::delete('DELETE FROM user WHERE id = ?', [1]); // 返回受影响的行数 int

$result = Db::statement("CALL pro_test(?, '?')", [1, 'your words']);  // 返回 bool  CALL pro_test(?,?) 为存储过程,属性为 MODIFIES SQL DATA
  • 事务
use HyperfDbConnectionDb;

Db::beginTransaction();
try{
    // Do something...
    Db::commit();
} catch(Throwable $ex){
    Db::rollBack();
}
  • 打印最后一条sql
// 打印最后一条 SQL 相关数据
var_dump(Arr::last(Db::getQueryLog()));
  • 获取的多个值是Collection 获取的单个值是stdClass
  • 获取第一条 ->first()
  • 获取第一列值 ->pluck(‘name’) 也可以多个 ->p
  • 获取单个值 ->value(“id”)
  • 查询指定字段 ->select(‘id’,‘name’,‘gender’)
  • Or
$users = Db::table('user')
    ->where('gender', 1)
    ->orWhere('name', 'John')
    ->get();
  • whereBetween
$users = Db::table('users')->whereBetween('votes', [1, 100])->get();

模型

  • 创建模型 php ./bin/hyper.php gen:model table_name
  • 时间戳
<?php
declare(strict_types=1);
namespace AppModel;
use HyperfDbConnectionModelModel;
class User extends Model
{
    public bool $timestamps = false;
}
  • 保护属性 f i l l a b l e 可填充, fillable 可填充, fillable可填充,guarded 保护属性
  • 配置软删除
<?php
namespace AppModel;
use HyperfDatabaseModelModel;
use HyperfDatabaseModelSoftDeletes;
class User extends Model
{
    use SoftDeletes;
}

模型关系

  • 一对一 User有一个Role
<?php
declare(strict_types=1);
namespace AppModels;
use HyperfDbConnectionModelModel;
class User extends Model
{
    public function role()
    {
    	第一个参数 one的类,User表中的外键 主键 一对一是主表需要有外键 当然谁都可以成为主表
        return $this->hasOne(Role::class, 'user_id', 'id');
    }
}
  • 一对多 User有多个Book
<?php

declare(strict_types=1);

namespace AppModels;

use HyperfDbConnectionModelModel;

class User extends Model
{
    public function books()
    {
        return $this->hasMany(Book::class, 'user_id', 'id');
    }
}
  • 一对多反向 属于关系
<?php

declare(strict_types=1);

namespace AppModels;

use HyperfDbConnectionModelModel;

class Book extends Model
{
    public function author()
    {
    	//参数永远都是外键 然后主键
        return $this->belongsTo(User::class, 'user_id', 'id');
    }
}
  • 多对多
  • 假设User表和Role
  • 前置条件 role_id user_id 中间表 role_user
<?php

namespace App;

use HyperfDbConnectionModelModel;

class User extends Model
{
    public function roles()
    {
    外表,中间表,自己的的外键,外键  记住这个顺序 基本都是按这个顺序来的
    return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');

        //return $this->belongsToMany(Role::class);
    }
}
  • 获取中间表字段
$user = User::find(1);

foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}

  • 如果需要其他字段需要在关联的时候指出
return $this->belongsToMany(Role::class)->withPivot('column1', 'column2');

访问器&修改器

  • 访问器
<?php

namespace App;

use HyperfDbConnectionModelModel;

class User extends Model
{
    /**
     * 获取用户的姓名.
     *
     * @param  string  $value
     * @return string
     */
    public function getFirstNameAttribute($value)
    {
        return ucfirst($value);
    }
}
  • 修改器
<?php

namespace App;

use HyperfDbConnectionModelModel;

class User extends Model
{
    /**
     * 设置用户的姓名.
     *
     * @param  string  $value
     * @return void
     */
    public function setFirstNameAttribute($value)
    {
        $this->attributes['first_name'] = strtolower($value);
    }
}

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。