我正在使用Symfony2,Doctrine,FOSRestBundle和JMSSerializer构建一个REST API。
我遇到的问题是在序列化我的实体时,序列化程序会拉入任何相关实体。例如,对于一个任务,该任务是作为板的一部分的故事的一部分,因此在序列化任务时,我得到的输出包括包含板的故事,然后包括板上的所有其他故事。
有没有一种简单的方法来限制这一点,而只包含 foreignId?
使用 JMS 排除策略。
在类别实体上使用注释的示例,其中您不希望包含子项和要包含的产品相关实体:
use ...
JMSSerializerBundleAnnotationExclusionPolicy,
JMSSerializerBundleAnnotationExclude,
...;
/**
* ...
* @ExclusionPolicy("none")
*/
class Category
{
/**
* ...
* @Exclude
*/
private $children;
/**
* ...
* @Exclude
*/
private $products;
}
查看 JMSSerializer 文档以获取更多信息。
编辑:
例如,您可以使用部分关键字仅选择所需的数据。虽然我不能,但为了我的生活,如果我将实体对象传递给序列化程序,则禁用完整相关实体的加载(向下两级)(即使在 DoctrineProxyHandler 中禁用加载),但是如果我使用数组,那么它就不会使用通过代理延迟加载原则(如预期的 ofc)。
使用示例实体的示例:
$dql = "SELECT t, s, partial b.{id}, partial ss.{id}
FROM AcmeAppBundleEntityTask t
JOIN t.story s
JOIN s.board b
JOIN b.stories ss"
$q = $this->_em-createQuery($dql);
$result = $q->getArrayResult();
这样你会得到类似的东西:
[
{
id: 33,
title: "My Task",
story: [
{
id: 554,
board: [
{
id: 14,
stories: [
{
id: 554
},
{
id: 3424
},
{
id: 3487
}
]
}
]
}
]
}
]
附言我实际上对这个"问题"很感兴趣。无论如何,我会看到如何在不使用数组结果的情况下序列化实体对象的解决方案。
只是最新版本的JMSSerializer中的更新,您应该查看的地方是
JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber
而不是
Serializer\Handler\DoctrineProxyHandler
要覆盖默认的延迟加载行为,应定义自己的事件订阅者。
在你的app/config.yuml中添加这个:
parameters:
...
jms_serializer.doctrine_proxy_subscriber.class: YourBundleEventDoctrineProxySubscriber
您可以将类从 JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber 复制到您的\Bundle\Event\DoctrineProxySubscriber,并注释掉 $object->__load();
行public function onPreSerialize(PreSerializeEvent $event)
{
$object = $event->getObject();
$type = $event->getType();
// If the set type name is not an actual class, but a faked type for which a custom handler exists, we do not
// modify it with this subscriber. Also, we forgo autoloading here as an instance of this type is already created,
// so it must be loaded if its a real class.
$virtualType = ! class_exists($type['name'], false);
if ($object instanceof PersistentCollection) {
if ( ! $virtualType) {
$event->setType('ArrayCollection');
}
return;
}
if ( ! $object instanceof Proxy && ! $object instanceof ORMProxy) {
return;
}
//$object->__load(); Just comment this out
if ( ! $virtualType) {
$event->setType(get_parent_class($object));
}
}
更新:我最终编写了自己的简化版本的序列化工具:https://github.com/dlin-me/array-converter-bundle
检查 JMSSerializerBundle 上的 Serializer/Handler/DoctrineProxyHandler.php 文件。现在,如果您评论此行:
public function serialize(VisitorInterface $visitor, $data, $type, &$handled)
{
if (($data instanceof Proxy || $data instanceof ORMProxy) && (!$data->__isInitialized__ || get_class($data) === $type)) {
$handled = true;
if (!$data->__isInitialized__) {
//$data->__load();
}
它将停止延迟加载您的实体。如果这是您要查找的,那么请继续创建自己的处理程序,而不会延迟加载。
如果这不正确,我建议您先自定义实体,然后再根据自己的喜好将它们发送到JMSSerializerBundle。例如,在任何相关实体中,我都需要 ID,而在其他实体中,我需要自定义列名称,如代码、名称或任何内容。
我只是创建实体对象的副本,然后开始获取关系所需的字段。然后,我序列化该副本。 JMSSerializerBundle 不会延迟加载,因为我已经提供了正确的字段。
下面是一个函数,用于以通用方式选择一对一或一对多关联实体的 ID,而无需使用联接。
function selectWithAssociations($doctrine, $className) {
$em = $doctrine->getManager();
$meta = $em->getClassMetadata($className);
//explicitly get IDs of associated entities
$assocClauses = array();
foreach ($meta->getAssociationMappings() as $assocName => $assoc) {
if (isset($assoc['joinTable'])) {
//todo: doesn't handle many to many associations
} else {
$assocClauses[] = ", IDENTITY(e.$assocName) AS $assocName";
}
}
//run custom DQL query
$q = $em->createQuery('SELECT e AS _d' . implode('', $assocClauses) . ' FROM ' . $className . ' e');
$result = $q->getArrayResult();
return $result;
}
这是防止延迟加载一个或多个关联的类,这些关联可用作 JMS 序列化程序排除策略。
use DoctrineORMPersistentCollection;
use DoctrineORMProxyProxy;
use JMSSerializerContext;
use JMSSerializerExclusionExclusionStrategyInterface;
use JMSSerializerMetadataClassMetadata;
use JMSSerializerMetadataPropertyMetadata;
use JMSSerializerSerializationContext;
/**
* Class OnlyLoadedAssociationsExclusionStrategy
*
* http://stackoverflow.com/questions/11851197/avoiding-recursion-with-doctrine-entities-and-jmsserializer
*/
class OnlyLoadedAssociationsExclusionStrategy implements ExclusionStrategyInterface
{
public function shouldSkipClass(ClassMetadata $metadata, Context $context)
{
}
public function shouldSkipProperty(PropertyMetadata $property, Context $context)
{
if ($context instanceof SerializationContext){
$vistingSet=$context->getVisitingSet();
//iterate over object to get last object
foreach ($vistingSet as $v){
$currentObject=$v;
}
$propertyValue=$property->getValue($currentObject);
if ($propertyValue instanceof Proxy){
// skip not loaded one association
if (!$propertyValue->__isInitialized__){
return true;
}
}
if ($propertyValue instanceof PersistentCollection){
// skip not loaded many association
if (!$propertyValue->isInitialized()){
return true;
}
}
}
return false;
}
}
使用示例:
$serializationContext->addExclusionStrategy(
new OnlyLoadedAssociationsExclusionStrategy()
);