核心答案是使用symfony serializer组件将审计记录转换为数组;2. 首先确定审计数据来源(如gedmo logentry、auditbundle或自定义实现),不同来源的数据结构决定后续处理方式;3. 对于实体类审计记录,利用serializer的normalize方法配合datetimenormalizer和objectnormalizer将其转为数组,并通过上下文参数控制序列化行为;4. 若审计实体中包含json字符串字段(如data字段),需在序列化后额外调用json_decode($data, true)解析为数组;5. 使用序列化组(@groups)精确控制输出字段,避免敏感信息泄露和循环引用问题,尤其适用于关联对象(如user实体)的扁平化输出;6. 当默认序列化器不足时,可创建自定义normalizer实现复杂逻辑,如在审计上下文中仅输出用户id和用户名;7. 不同审计bundle策略不同:gedmo需特别处理data字段的json解析,simplethingsauditbundle结构较扁平易于序列化,encorelabs/auditbundle等复杂bundle需结合其api和内部实体结构定制序列化方案;8. 自定义审计实现若以json存储则直接json_decode,若为多列扁平结构可手动构建数组或映射到对象后序列化;9. 直接sql查询审计表不推荐,因难以解析非结构化数据和处理关联信息,易导致性能问题和逻辑重复;10. 最终推荐方案是结合serializer、序列化组、自定义normalizer及后期批量处理,实现高效、可控、可维护的审计数组转换。

在Symfony中将审计记录转换为数组,核心在于理解你的审计数据是如何存储的。无论是通过Doctrine事件监听器、特定的审计Bundle,还是自定义的逻辑,最终目标都是将这些分散、可能带有复杂关联的数据,整理成一个易于处理的、扁平化的PHP数组。这通常会涉及Symfony的序列化组件,或者一些手工的数据映射工作。
将Symfony审计记录转换为数组,最直接且推荐的方式是利用Symfony的序列化组件(Serializer Component)。它能处理实体对象、集合,并将其转换为各种格式,包括数组。
确定审计记录的来源和类型:
Gedmo\Loggable\Entity\LogEntry
objectId
objectClass
version
data
loggedAt
username
AuditEntry
利用Symfony Serializer组件:
对于实体对象(如LogEntry或AuditEntry): 这是最常见的情况。
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; // For PHP 8+ attributes
// 假设你有一个审计实体 $auditRecord
// 例如:$auditRecord = $entityManager->getRepository(LogEntry::class)->find(123);
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
$normalizers = [
new DateTimeNormalizer(), // 处理日期时间对象
new ObjectNormalizer($classMetadataFactory) // 处理普通对象
];
$encoders = [new JsonEncoder()];
$serializer = new Serializer($normalizers, $encoders);
// 使用normalize方法将其转换为数组
// 可以通过上下文参数控制序列化深度、循环引用等
$auditArray = $serializer->normalize($auditRecord, 'json', [
'groups' => ['audit_read'], // 如果你定义了序列化组
ObjectNormalizer::ENABLE_MAX_DEPTH => true, // 启用最大深度限制
ObjectNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
return $object->getId(); // 处理循环引用,返回ID
}
]);
// 特别处理Gedmo LogEntry的'data'字段,它通常是JSON字符串
if (isset($auditArray['data']) && is_string($auditArray['data'])) {
$auditArray['data'] = json_decode($auditArray['data'], true);
}定义序列化组(Serialization Groups): 在你的审计实体或相关实体(如User实体)上使用
#[Groups(['audit_read'])]
对于存储为JSON或序列化字符串的字段: 如果你的审计记录直接在数据库中存储为JSON字符串(例如一个
json_data
json_decode($string, true)
serialize()
unserialize()
自定义Normalizer(可选但推荐): 当默认的
ObjectNormalizer
user
['id' => 123, 'username' => 'John Doe']
// 示例:一个简化版的自定义Normalizer来处理User对象
// 这通常需要实现NormalizerInterface和DenormalizerInterface
// 并在服务配置中注册
/*
class UserAuditNormalizer implements NormalizerInterface
{
public function normalize($object, string $format = null, array $context = [])
{
if (!$object instanceof User) {
return null;
}
// 假设你在审计上下文中需要扁平化的用户数据
if (isset($context['audit_context']) && $context['audit_context'] === true) {
return [
'id' => $object->getId(),
'username' => $object->getUsername(),
// ... 其他你需要的字段
];
}
// 否则,让ObjectNormalizer处理
return null;
}
public function supportsNormalization($data, string $format = null, array $context = [])
{
return $data instanceof User;
}
}
*/
// 然后在serializer的normalizers数组中,将你的自定义normalizer放在ObjectNormalizer之前
// $normalizers = [new UserAuditNormalizer(), new ObjectNormalizer($classMetadataFactory)];直接通过SQL查询来获取审计记录,听起来是直接了当,但对于复杂的审计场景来说,这往往不够“优雅”,甚至可能带来不少麻烦。审计记录通常不只是简单的行数据,它们包含了历史版本、字段变更对比、关联用户、时间戳等等,这些信息可能以非结构化(比如JSON字符串)、或者跨多表关联的形式存在。
你直接去查,拿到手可能是一堆ID,或者一些序列化过的、你看不懂的字符串。比如,一个
LogEntry
data
oldValue
newValue
更关键的是,许多审计Bundle提供了高层级的API来查询和解释这些数据,它们帮你处理了版本回溯、数据差异比对等逻辑。你直接绕过这些API去读原始数据,等于放弃了这些便利,还得自己重新实现一遍这些复杂的业务逻辑。这不仅费时费力,还容易出错,尤其是在处理不同版本的数据结构差异时,手动解析会非常痛苦。
审计记录里,谁做了什么修改,这“谁”通常是个用户对象,或者其他关联实体。光一个ID肯定不够,我们希望看到用户的名字、邮箱,甚至角色。但如果把整个用户对象都序列化到审计记录里,那数据量就太大了,而且还可能遇到循环引用问题。所以,得找个平衡点。
一种非常实用的方式是利用Symfony Serializer的序列化组(Serialization Groups)。你可以在你的User实体上定义一个特定的组,比如
audit_read
id
username
// src/Entity/User.php
use Symfony\Component\Serializer\Annotation\Groups;
class User
{
#[Groups(['audit_read'])]
private ?int $id = null;
#[Groups(['audit_read'])]
private ?string $username = null;
#[Groups(['audit_read'])]
private ?string $email = null;
// ... 其他字段和方法
}然后在序列化审计记录时,告诉Serializer只序列化
audit_read
$auditArray = $serializer->normalize($auditRecord, 'json', [
'groups' => ['audit_read']
]);这样,当审计记录中包含一个User对象时,它就只会以一个包含
id
username
如果这种方式还不够,或者你需要更复杂的逻辑(比如根据上下文动态决定输出哪些字段),那么自定义Normalizer就是你的终极武器。你可以编写一个专门的Normalizer来处理User实体,当它在审计上下文中被序列化时,就按照你想要的方式输出。这能让你对输出的粒度有完全的控制。
最后,一个比较“粗暴”但有时有效的方法是后期处理。先将审计记录序列化成数组,然后遍历这个数组,如果发现某个字段是关联实体的ID,就根据这个ID去数据库或者缓存里查出你需要的信息,然后替换掉原来的ID。这种方法在需要批量处理大量记录时,可以考虑批量查询关联数据,提高效率。
市面上的Symfony审计Bundle不少,它们的设计哲学和数据存储方式各有侧重,所以转换策略自然也会有所不同。
Gedmo DoctrineExtensions (Loggable):
LogEntry
LogEntry
objectId
objectClass
version
data
loggedAt
username
LogEntry
data
LogEntry
data
json_decode(true)
username
SimpleThingsAuditBundle:
AuditEntry
entityType
entityId
changes
Change
revision
Change
fieldName
oldValue
newValue
AuditEntry
Change
oldValue
newValue
Encorelabs/AuditBundle (或类似更复杂的Bundle):
自定义审计实现:
json_decode(true)
user_id
entity_id
field_name
old_value
new_value
总的来说,无论使用哪种Bundle或自定义方案,Symfony Serializer都是将审计记录转换为数组的核心工具。关键在于理解你的审计数据是如何存储的,然后灵活运用序列化组、自定义Normalizer,以及适当的后期处理,来达到你想要的数组结构。
以上就是Symfony 如何把审计记录转为数组的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号