asfop / eloquent
在一对一关联情况下的数据缓存的一种尝试
dev-main
2024-06-20 08:03 UTC
Requires
- php: >=7.1|>=8.0|>=8.1
- ext-json: *
Requires (Dev)
- phpunit/phpunit: 8.5.x-dev
- sebastian/phpcpd: ^4.1
- squizlabs/php_codesniffer: 4.0.x-dev
This package is auto-updated.
Last update: 2025-05-20 09:52:04 UTC
README
eloquent 提供了模型关联关系中的一对一和一对多查询。这两种情况下,查询出的数据可以进行缓存以提高性能 在一对一关联情况下的数据缓存的一种尝试
初衷
在多表连接查询的时候,我的一般做法,我会先查主表,然后再array_column条件id去匹配副表信息.
例如, users
和 phones
两张表,它们通过 user_id
进行关联
表结构
- users 表:
字段名 | 类型 | 描述 |
---|---|---|
id | Integer | 主键 |
name | String | 用户名 |
String | 邮箱地址 |
- phones 表:
字段名 | 类型 | 描述 |
---|---|---|
id | Integer | 主键 |
user_id | Integer | 外键,关联到 users 表的 id |
phone_number | String | 电话号码 |
实现方式
use App\Models\User; use App\Models\Phone; $userData = User::select('id', 'name', 'email') ->get(); // 获取所有用户的 id $userIds = $userData->pluck('id')->toArray(); // 获取每个用户的电话号码 $phoneData = Phone::whereIn('user_id', $userIds) ->select('user_id', 'phone_number') ->get() ->groupBy('user_id'); // 将电话号码数据合并到用户数据中 foreach ($userData as $user) { if ($phoneData->has($user->id)) { $user->phones = $phoneData[$user->id]->pluck('phone_number')->toArray(); } else { $user->phones = []; // 如果没有电话号码,则设为空数组或 null,视情况而定 } } // 现在 $userData 中每个 $user 对象都有 phones 属性,包含了该用户的所有电话号码 return $userData;
为了简化上述操作,进行了封装,并且对查询出来的结果使用redis将数据缓存
使用案例
- 获取用户id为10000的信息
use App\Models\User; use App\Models\Phone; $userEloquent = new UserEloquent(); $users=$userEloquent->getById(10000,['info','phone']); // 结果 Array ( "info" => Array ( "id" => 10000, "name" => "Jane", "email" => "jane@example.com" ), "phone" => Array ( "id" => 1, "user_id" => 10000, "phone_number" => "13681985439" ) )
- 获取用户10000和10001的信息
use App\Models\User; use App\Models\Phone; $userEloquent = new UserEloquent(); $users=$userEloquent->getByIds([10000,10001],['info','phone']); // 结果 Array ( 10000 => Array ( "info" => Array ( "id" => 10000, "name" => "Jane", "email" => "jane@example.com" ), "phone" => Array ( "id" => 1, "user_id" => 10000, "phone_number" => "13681985439" ) ), 10001 => Array ( "info" => Array ( "id" => 10001, "name" => "Jane Doe", "email" => "jane.doe@example.com" ), "phone" => Array ( "id" => 2, "user_id" => 10001, "phone_number" => "13712345678" ) ) // 可能还有其他用户的信息,取决于查询到的用户数量 )
如何实现上述查询
//可能存在bug composer require asfop/eloquent
在Hyperf中使用
创建Eloquent
文件夹 目录结构如下
├── User │ ├── UserDrive.php │ ├── UserEloquent.php │ └── Attribute │ ├── Info.php │ └── Phone.php
User
模型 一对一关联UserDrive.php
属性名对应的数据查询类UserEloquent.php
自定义快捷查询的类。可定义查询单个用户名,和多个用户函数名Attribute/Info.php
基础信息实现类Attribute/Phone.php
Phone信息实现类
UserEloquent.php 解释
<?php namespace App\Eloquent\Activity; use App\Log\Log; use Asfop\Eloquent\Cache; use Asfop\Eloquent\Eloquent; use Hyperf\Context\ApplicationContext; use Hyperf\Redis\RedisFactory; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; class ActivityEloquent { /** * 缓存键的前缀,控制整个缓存版本,版本号更改后所有用户缓存失效 */ const CACHE_KEY_PREFIX = "c:eloquent:user:v1"; /** * 根据一组用户ID获取多个信息 * * @param array $ids * @param array $attrs * @return array */ public function getByIds(array $ids, array $attrs = []): array { try { // 获取 Redis 实例并创建缓存对象 $redis = ApplicationContext::getContainer()->get(RedisFactory::class)->get('default'); $cache = new Cache($redis); // 创建数据驱动对象并实例化 Eloquent 类 $drive = new UserDrive(); // 假设这是用户数据驱动的实例化 $eloquent = new Eloquent($cache, $drive, self::CACHE_KEY_PREFIX); // 调用 Eloquent 实例的方法获取信息列表 return $eloquent->getInfoList(array_unique($ids), $attrs); } catch (\Exception $exception) { // 记录错误日志 Log::error("user_eloquent_get_by_ids", [$exception->getMessage(), $exception->getFile(), $exception->getLine()]); } catch (NotFoundExceptionInterface | ContainerExceptionInterface $exception) { // 记录错误日志 Log::error("user_eloquent_get_by_ids", [$exception->getMessage(), $exception->getFile(), $exception->getLine()]); } return []; } /** * 根据用户ID获取单个信息 * * @param int $id * @param array $attrs * @return array */ public function getById(int $id, array $attrs = []): array { try { // 获取 Redis 实例并创建缓存对象 $redis = ApplicationContext::getContainer()->get(RedisFactory::class)->get('default'); $cache = new Cache($redis); // 创建数据驱动对象并实例化 Eloquent 类 $drive = new UserDrive(); // 假设这是用户数据驱动的实例化 $eloquent = new Eloquent($cache, $drive, self::CACHE_KEY_PREFIX); // 调用 Eloquent 实例的方法获取信息列表 return $eloquent->getInfoList([$id], $attrs); } catch (\Exception $exception) { // 记录错误日志 Log::error("user_eloquent_get_by_id", [$exception->getMessage(), $exception->getFile(), $exception->getLine()]); } catch (NotFoundExceptionInterface | ContainerExceptionInterface $exception) { // 记录错误日志 Log::error("user_eloquent_get_by_id", [$exception->getMessage(), $exception->getFile(), $exception->getLine()]); } return []; } /** * 清除特定用户特定属性的缓存 * * @param int $id * @param string $attr * @return void */ public function forgetCache(int $id, string $attr): void { try { // 获取 Redis 实例并创建缓存对象 $redis = ApplicationContext::getContainer()->get(RedisFactory::class)->get('default'); $cache = new Cache($redis); // 创建数据驱动对象并实例化 Eloquent 类 $drive = new UserDrive(); // 假设这是用户数据驱动的实例化 $eloquent = new Eloquent($cache, $drive, self::CACHE_KEY_PREFIX); // 调用 Eloquent 实例的方法清除缓存 $eloquent->forgetCache($id, $attr); } catch (\Exception $exception) { // 记录错误日志 Log::error("user_eloquent_forget_cache", [$exception->getMessage()]); } catch (NotFoundExceptionInterface | ContainerExceptionInterface $exception) { // 记录错误日志 Log::error("user_eloquent_forget_cache", [$exception->getMessage()]); } } }
Info.php 解释
<?php namespace App\Eloquent\User\Attribute; use App\Models\User; use Asfop\Eloquent\attribute\Base; class Info extends Base { /** * 当前属性的版本缓存版本,用于在当前属性查询字段新增或减少后更新缓存 * @var string */ protected $cacheVersion = "v1"; /** * 获取此类的名称或类型标识 * @return string */ public static function getNames(): string { return 'info'; } /** * @inheritDoc */ public function getField(): array { return ["id", "name", "email"]; } /** * 根据给定的 id 数组从数据库获取成员信息 * 当缓存不存在时会执行该方法,存在缓存后则不再执行 * @inheritDoc */ public function getInfoByIds(): array { $userData = User::whereIn('uid', $this->getIds()) ->select($this->getField()) ->get(); return $userData; } /** * 根据数据进行转换处理,每次调用都会执行此方法 * @inheritDoc */ public function transform($data): array { if (empty($data)) { return []; } return [ "id" => $data["id"], "email" => $data["email"], "name" => $data["name"], ]; } }
开发计划
- 基本功能够使用
- 提供基础文档
- 查询功能加上数据缓存,缓存加上版本控制,缓存支持清空
- 支持是否使用缓存开关
- 支持那些属性查询不走缓存
- 支持更多的缓存方式如文件,Memcache缓存方式,
- 支持并发重复请求
- 支持多级缓存,内存缓存->redis缓存
- 命名优化,功能优化
- 完善测试用例