17370845950

Shopware订单对象中获取产品自定义字段的正确方法

本文详细阐述了在Shopware订单对象中,如何正确获取产品自定义字段的策略。针对常见的语言依赖性问题,教程指出通过调整关联路径为`lineItems.product.default`,而非`translations`,可以有效访问非语言相关的自定义字段,并提供了具体的代码示例和最佳实践,帮助开发者高效集成自定义数据。

在Shopware的开发实践中,经常需要在订单处理流程中访问与产品相关的自定义字段(Custom Fields)。例如,一个产品可能有一个名为emakers_custom_field_warehouse_name的自定义字段,用于存储其所属仓库的名称。当订单生成后,开发者需要从订单行项目(Line Items)中获取该产品及其自定义字段信息。然而,直接尝试获取自定义字段可能会遇到因语言依赖性而导致的数据缺失问题。

问题描述与初始尝试

在Shopware中,产品数据通常包含多语言翻译。自定义字段的配置也可能分为语言相关和非语言相关两种。当通过订单对象检索产品信息时,如果自定义字段是非语言相关的,但查询关联却指向了翻译实体,就可能无法正确获取这些字段。

以下是一个常见的初始查询尝试,旨在获取订单及其行项目中的产品翻译信息:

use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\System\SalesChannel\Repository\SalesChannelRepositoryInterface; // 假设通过销售渠道仓库获取订单

// ... 在一个服务或控制器中 ...

public function getOrderWithProductCustomFields(string $orderId): ?OrderEntity
{
    $criteria = new Criteria();
    $criteria->addFilter(new EqualsFilter('id', $orderId));
    $criteria->addAssociations([
        'deliveries.shippingOrderAddress.salutation',
        'deliveries.shippingOrderAddress.country',
        'orderCustomer.customer.group',
        'lineItems.product.translations', // 初始尝试,关联产品翻译
    ]);

    // 假设通过 orderRepository 注入
    $orderObject = $this->orderRepository->search($criteria, Context::createDefaultContext())->first();

    if ($orderObject) {
        $firstLineItem = $orderObject->getLineItems()->first();
        if ($firstLineItem && $firstLineItem->getProduct()) {
            // 尝试获取自定义字段,此时可能为空
            $customFields = $firstLineItem->getProduct()->getCustomFields();
            // 尝试访问产品翻译,会报错:Attempted to call an undefined method named "getProductTranslations"
            // $firstLineItem->getProduct()->getProductTranslations();
        }
    }
    return $orderObject;
}

在这种情况下,即使产品数据中确实存在emakers_custom_field_warehouse_name这样的自定义字段,通过$firstLineItem->getProduct()->getCustomFields()获取到的结果也可能是空的。进一步尝试直接调用getProductTranslations()方法也会导致错误,因为ProductEntity本身并没有直接暴露这个方法来获取所有翻译集合。

解决方案:关联默认产品数据

问题的核心在于,非语言相关的自定义字段通常存储在产品实体(ProductEntity)的默认数据中,而不是其语言翻译实体(ProductTranslationEntity)中。当通过'lineItems.product.translations'关联时,Shopware会加载与当前上下文语言匹配的产品翻译数据,而这些数据可能不包含非语言相关的自定义字段。

正确的做法是关联'lineItems.product.default'。这个关联会确保我们获取到产品实体的默认数据,其中就包含了那些不依赖于特定语言的自定义字段。

以下是修正后的Criteria配置:

use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Checkout\Order\OrderEntity; // 引入OrderEntity
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; // 引入EntityRepository

// ... 在一个服务或控制器中 ...
class OrderService
{
    private EntityRepository $orderRepository;

    public function __construct(EntityRepository $orderRepository)
    {
        $this->orderRepository = $orderRepository;
    }

    public function getOrderWithProductCustomFields(string $orderId): ?OrderEntity
    {
        $criteria = new Criteria();
        $criteria->addFilter(new EqualsFilter('id', $orderId));
        $criteria->addAssociations([
            'deliveries.shippingOrderAddress.salutation',
            'deliveries.shippingOrderAddress.country',
            'orderCustomer.customer.group',
            'lineItems.product.default', // 关键修改:关联产品默认数据
        ]);

        $orderObject = $this->orderRepository->search($criteria, Context::createDefaultContext())->first();

        if ($orderObject) {
            $firstLineItem = $orderObject->getLineItems()->first();
            if ($firstLineItem && $firstLineItem->getProduct()) {
                // 现在可以正确获取自定义字段
                $customFields = $firstLineItem->getProduct()->getCustomFields();

                // 示例:访问特定自定义字段
                if ($customFields && isset($customFields['emakers_custom_field_warehouse_name'])) {
                    $warehouseName = $customFields['emakers_custom_field_warehouse_name'];
                    // ... 处理 $warehouseName ...
                }
                return $orderObject;
            }
        }
        return null;
    }
}

通过将关联从'lineItems.product.translations'改为'lineItems.product.default',$firstLineItem->getProduct()->getCustomFields()现在将能成功返回包含emakers_custom_field_warehouse_name在内的所有非语言相关自定义字段。

深入理解Shopware数据模型:default 与 translations

在Shopware的实体关联中:

  • .translations: 用于加载当前上下文语言下的实体翻译数据。如果自定义字段被配置为“可翻译”,则它们会存储在翻译实体中。但如果自定义字段是全局的、非语言相关的,它们则不会出现在这里。
  • .default: 通常指向实体的主数据或默认数据。对于产品而言,这包含了产品的基础信息以及那些不依赖于特定语言的自定义字段。当自定义字段不是可翻译的(即在Shopware后台创建时未勾选“可翻译”选项),它们就会存储在这里。

因此,当需要访问那些全局的、非语言相关的自定义字段时,应优先考虑关联.default。如果确实需要访问特定语言下的可翻译自定义字段,则需要确保该自定义字段在后台被配置为可翻译,并且在查询时使用正确的语言上下文。

实践建议

  1. 明确自定义字段类型: 在Shopware后台创建自定义字段时,要清楚其是“可翻译”还是“不可翻译”。这决定了在代码中如何正确地通过关联来访问它们。
  2. 选择合适的关联路径:
    • 对于非语言相关的自定义字段,使用.default关联。
    • 对于语言相关的自定义字段,如果需要特定语言的翻译,可以使用.translations并确保上下文语言正确。
  3. 考虑性能: 过多的关联会增加数据库查询的复杂度和数据量。只关联你真正需要的数据,避免不必要的关联。
  4. 上下文管理: Context::createDefaultContext()通常用于获取系统默认上下文。如果你的操作需要在特定的销售渠道或语言环境下执行,请确保使用相应的上下文对象。
  5. 空值检查: 始终对获取到的实体和自定义字段数组进行空值检查,以避免潜在的null引用错误。

通过理解Shopware的数据关联机制,特别是default和translations的区别,开发者可以更精确、高效地在订单对象中获取所需的产品自定义字段,从而构建健壮的Shopware应用程序。