Introduction

This page explains how the mapping-extension is able to map references to other Shopware entities.

This page provides a general overview and background information that you need in order to understand the mapping of foreign key relations. The step-by-step guide provides several in-depth examples on how to set up the mapping-extension for references.

Field Configuration

To configure a foreign key, use the field configuration foreignKeyField:

{
    "foreignKeyFieldConfig": {
        "sourcePath": "...",
        "targetFieldName": "...",
        "allowsNullValues": ...,
        "references": {
            "table": "referenced_table_name",
            "field": "referenced_column_name"
        },
        "filterSearchField": "..."
    }
}  
  • references.table contains the name of the referenced table
  • references.field contains the name of the referenced column (the name of the column your foreign key column is referencing). The default value is id.
  • filterSearchField is the name of the field that must be equal to the value of your source path in order to generate a reference

The configuration of foreign keys differs slightly depending on the type of foreign key. The module makes a distinction between so-called "internal" and "external" foreign keys. The difference is explained in the sections below.

Internal Foreign Keys

Internal foreign keys are references between documents that are both mapped by the mapping extension. This is the case if a Document A references another Document B and both documents are mapped to Shopware by the help of the mapping-extension.

Tables

Let's take a look at an example and assume that a plugin added two tables to Shopware:

create table example_document_table
(
    id                     binary(16) not null primary key,
    referenced_document_id binary(16) not null,
    constraint `...` foreign key (referenced_document_id) references referenced_document_table (id)
);

create table referenced_document_table
(
    id binary(16) not null primary key
);  

Documents

The corresponding documents that the mapping-extension uses to populate those tables would look like this:

/** 
* 
* Example document that references another document mapped by the extension as well. Mapped to 'example_document_table'.
* 
* @MongoDB\Document 
*/  
class ExampleDocument extends BaseDocument  
{  
    /** @MongoDB\ReferenceOne(targetDocument=ReferencedDocument::class, cascade="persist") */    
    protected ReferencedDocument $someReferencedDocument = null;  
}  
  
/** 
* Referenced document. This document is mapped to the table `referenced_document_table`.
*
* @MongoDB\Document 
*/  
class ReferencedDocument extends BaseDocument  
{  
    // ...
}  

Configuration

This will lead to the following configuration for the internal foreign key:

{
    "sourcePath": "someReferencedDocument",
    "targetFieldName": "referenced_document_id",
    "foreignKeyFieldConfig": {
        "allowsNullValues": false,
        "references": {
            "table": "referenced_document_table"
        }
    }
}  

As you can see setting up an internal foreign key is pretty straight forward. All you have to do is to let the sourcePath point to the referenced document and add the target fields and tables to the configuration.

Source Path

The sourcePath must point to the referenced document that is also transferred by the mapping extension. Do not use any other field of the document (e.g. "someReferencedDocument.id" or "someReferencedDocument.identifier", the only valid source path would be "someReferencedDocument").

The referenced UUID (the specific id of table_b that an entry of table_a references) is calculated automatically from the referenced Document B (either by its ObjectId or Synqup-Identifier, depending on the configured mode).

Target Field Name

As in every other field type the targetFieldName contains the name of the column where the source value will be written to. But instead of the source value itself the value of the referenced column of the corresponding Shopware entity will be written into this field.

Referenced Table

The field foreignKeyFieldConfig.references.table contains the name of the referenced Shopware table. For internal references this must be the name of the table that belongs to the referenced document.

Referenced Field

Internal foreign keys always have to reference the UUID of the other Shopware table. So the extension enforces the value id for the field referencedField (other values than ìd will lead to errors). Therefore, you can simply omit the field in the configuration.

The field foreignKeyFieldConfig.references.field contains the name of the referenced column (the name of the column your foreign key column is referencing). The default value is id.

Order of Object Mappings

Since object mappings are handled in sequential order it is very important to add the object-mapping of referenced entities before the object-mapping of the referencing entity.

External Foreign Keys

External foreign keys are references to documents or Shopware entities that are not mapped by the mapping-extension. This can either be a reference to a document that is mapped by the main module (e.g. a Product or Customer) or a reference to a default Shopware entity by a specific/single value.

In difference to internal foreign keys it is not possible to calculate the uuid of the referenced entity without searching it in Shopware. So you need to have a unique value that is present both in your document and the target/referenced entity in Shopware. This value is then used to search the referenced entity/id in Shopware.

Configuration

Let's take a look on the configuration:

{
    "sourcePath": "...",
    "targetFieldName": "...",
    "foreignKeyFieldConfig": {
        "allowsNullValues": false,
        "references": {
            "table": "referenced_table_name",
            "field": "referenced_column_name"
        },
        "filterSearchField": "..."
    }
}  

Source Path

The sourcePath must point to a specific/single scalar value that corresponds to a value of an entity in Shopware. This must be a unique value that is present both in your document and the target table in Shopware.

Valid examples: locale or customer.customerInformation.email.

Referenced Field

The field foreignKeyFieldConfig.references.field contains the name of the referenced column (the name of the column your foreign key column is referencing). The default value is id (you probably never have to change this since foreign keys in Shopware are usually implemented with ids).

Referenced Table

The field foreignKeyFieldConfig.references.table contains the name of the referenced Shopware table.

Target Field Name

targetFieldName is the foreign key column in your table. The referenced value of the referenced entity will be written into this column.

An example with a simple mysql query helps to understand this. The value written in your target field is equal to the mysql query SELECT {{references.field}} FROM {{references.table}} WHERE {{filterSearchField}} = {{value @ sourcePath}}.

Filter Search Field

This is the name of the field that must be equal to the value of your source path in order to generate a reference to the target entity. If you take a look at the next section ("Search Requests") the use of this field becomes way easier to understand.

Using Filter Paths

You can search external referenced entities with associations. But please note that this must be a valid filter path in Shopware. There are some limitations to using filter paths instead of fields:

  • The path must be a valid association in Shopware. Please test the request you want to generate (e.g. in Postman) to avoid FRAMEWORK_ASSOCIATION_NOT_FOUND errors.
  • Only single layered associations are supported, the path one.two.three would be invalid.
  • Although it is not an association, you can also search for values of customFields (e.g. customFields.identifier).

You can find examples on how to use filter paths instead of fields in the examples below.

Search Requests

The module will generate a search request from your configuration in order to find the referenced entity.

The value written in your column specified in targetFieldName is equal to the result of the following search request:

{{HOST}}/api/search/{{references.table}}/

{
    "filter": [
        {
            "type": "equals",
            "field": "{{filterSearchField}}",
            "value": "{{value @ sourcePath}}"
        }
    ],
    "associations": {
        "{{extracted-from-'filterSearchField'}}": {}
    }
}  

Or in other words: The value written in your target field is equal to the mysql query SELECT {{references.field}} FROM {{references.table}} WHERE {{filterSearchField}} = {{value @ sourcePath}}.

Example 1 - Referencing External Documents

This is an example for an external foreign key with a document mapped by the main module. In this scenario a document should reference a country from Shopware (table country).

Target Table

create table example_document_table
(
    id         binary(16) not null primary key,
    country_id binary(16) null,
    constraint `...` foreign key (country_id) references country (id)
); 

Source Document

/** @MongoDB\Document */  
class ExampleDocument extends BaseDocument  
{  
    /** @MongoDB\ReferenceOne(targetDocument=Country::class) */    
    protected Country $country = null;  
}  

Configuration

{
    "sourcePath": "country.iso3",
    "targetFieldName": "country_id",
    "foreignKeyFieldConfig": {
        "allowsNullValues": false,
        "references": {
            "table": "country",
            "field": "id"
        },
        "filterSearchField": "iso3"
    }
}  

Read the configuration as follows: The country (table country) whose value at the field iso3 corresponds to the source value at country.iso3 is going to be referenced. So the mapping-extension generates a search request to find the matching country:

{{HOST}}/api/search/country/

{
    "filter": [
        {
            "type": "equals",
            "field": "iso3",
            "value": "{{value @ country.iso3}}"
        }
    ]
}  

The id of the resulting entity is written into the field specified by targetFieldName.

Example 2 - Referencing External Shopware Entities

The following example configures an external foreign key to the Shopware entity "language".

Target Table

create table example_document_table
(
    id            binary(16) not null primary key,
    `language_id` BINARY(16) NOT NULL,
    CONSTRAINT `...` FOREIGN KEY (`language_id`) REFERENCES `language` (`id`) ON ...
);    

Document

class ExampleDocument extends BaseDocument  
{  
    
    /**  
     * Example for an external reference.
     * 
     * @var string  
     * @MongoDB\Field(type="string")  
     */    
     private string $language;  
  
    // construct, getters, setters, ...  
}

Our document has the value en-GB as language:

$document = new ExampleDocument(...);  
$document->setLanguage('en-GB');

Search Request

So we want to reference the language in Shopware whose locale code is equal to en-GB. You could read the language with the following api request. Note that there is no locale field in the table language. Instead, locales are stored in a separate table. If we want to find the language whose locale code is equal to en-GB we have to use associations.

{{HOST}}/api/search/language/

{  
  "filter": [  
	 {      
	     "type": "equals",  
	     "field": "locale.code",  
	     "value": "de-DE"  
     }
   ],
   "associations": {
	   "locale": {}
   }
}  

This request returns the language whose locale is equal to en-GB.

Configuration

If you want the mapping-extension to recreate this request in order to set the uuid of that language to your entity you have to use the following configuration:

{
    "targetFieldName": "language_id",
    "sourcePath": "language",
    "foreignKeyFieldConfig": {
        "references": {
            "table": "language"
        },
        "allowsNullValues": false,
        "filterSearchField": "locale.code"
    }
}

Many-To-Many Associations

The mapping extension is only able to support one-to-many references. Many-to-many references are not populated automatically. Every Shopware table to be populated by the mapping-extension must exist as a document, even M-N-tables / junction-tables. So in order to populate M-N-references you have to create and configure a matching document for your junction table. There is a very detailed example available in the step-by-step guide.

Reference Loading Modes

The mapping extension provides two "reference loading modes" (on-demand and bulk). This determines the way the module searches for referenced Shopware entities. This can be set by configuring the field referenceLoadingMode. The default value is bulk. This value can be set individually for any object mapping.

Quick-Reference

The reference loading mode on-demand searches a Shopware entity as soon as a reference is found. Produces a lot of requests. The reference loading mode bulk collects all referenced values and searches them in a bulk search request. Produces way less requests at the cost of two more subsections to go throug.

On-Demand

The on-demand mode is pretty straight forward: Search a single corresponding Shopware entity when needed, so referenced Shopware entities are searched and cached "on demand". This is done by generating a search request for every referenced entity.

There are 0-1 search requests for every referenced entity. A search request is not necessary if the referenced entity is an internal reference to a document that was mapped on the same flow execution.

Bulk

This mode is more complex than on-demand. Basically referenced shopware values are extracted from their source objects which allows the extension to search multiple referenced entities in a single search request.

This would lead to a search requests like this (the only difference to the examples from above is the usage of an equalsAny-filter instead of equals):

{
    "filter": [
        {
            "type": "equalsAny",
            "field": "{{references.field}}",
            "value": [
                "1",
                "2",
                "3",
                "4",
                "..."
            ]
        }
    ]
}  

To be able to search several referenced entities at once the module needs to extract those referenced entities beforehand. This is done in the subsection relation-extract. The entities are searched in the subsection relation-cache. Those subsections are only added to the subsection tree of an object-mapping in which the bulk mode is present.

Which Mode should I use?

This actually depends on the use case. The pros and cons of each mode are listed below.

Bulk

  • Fast: Less amount of search requests necessary to find referenced entities. Example: If you choose a batch size for relation-cache of 100 the number of requests is reduced by a factor of (up to) 100.
  • Slow: Extraction of referenced shopware values is necessary. This can be slow in very huge data sets.

On-Demand

  • Fast: Extraction of referenced entities is skipped entirely. Saves a lot of time in very huge data sets.
  • Slow: One search request for every referenced entity is necessary. This can be slow if a lot of different entities are referenced.

Recommendation

The default mode is bulk and in most cases you will be fine using this. It is actually very simple: If you have the feeling that one of these modes is too slow try the other one.

Basically the reference loading mode highly influences the amount of search requests generated by the mapping-extension. So you have to decide whether the extra effort of extracting the referenced values from the documents to reduce the number of requests is worth it or not.

There are cases that showed that there is a turning point from which it is faster to skip the extraction of references and just search entities when needed. Let's take a look at examples:

  • Case 1: 4.000 documents with references to several hundred Shopware entities. In this case it is very fast to extract all referenced values. Only a few requests are necessary. The bulk-search is faster than sending several hundred search requests on demand.
  • Case 2: 20.000.000 documents that contain references to ~300 Shopware entities. The process of extracting all referenced shopware values took ~3 hours. In this case it was way faster to skip the extraction and just search entities on demand.