-
Notifications
You must be signed in to change notification settings - Fork 240
/
add-complex-data-to-existing-entities.md
362 lines (269 loc) · 16.5 KB
/
add-complex-data-to-existing-entities.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# Adding complex data to existing entities
## Overview
Sometimes you want to extend existing entities with some custom information, this guide will have you covered. Extensions are technical and not configurable by the admin user just like that. Also they can deal with more complex types than scalar ones.
## Prerequisites
In order to create your own entity extension for your plugin, you first need a plugin as base. Therefore, you can refer to the [Plugin Base Guide](../../plugin-base-guide.md).
Also, basic knowledge of [creating a custom entity](add-custom-complex-data.md) and [adding associations](add-data-associations.md) to it are very helpful here.
## Creating the extension
In this example we're going to add a new string field to the product entity.
You can choose whether or not you want to save the new string field to the database or not. Therefore, you're going to see two sections, one for each way.
For both cases, you need to create a new "extension" class in the directory `<plugin root>/src/Extension/`. In this case we want to extend the `product` entity, so we create a subdirectory `Content/Product/` since the entity is located there in the Core. Our class then has to extend from the abstract `Shopware\Core\Framework\DataAbstractionLayer\EntityExtension` class, which forces you to implement the `getDefinitionClass` method. It has to point to the entity definition you want to extend, so `ProductDefinition` in this case.
Now you add new fields by overriding the method `extendFields` and add your new fields in there.
Here's an example class called `CustomExtension`:
{% code title="<plugin root>/src/Extension/Content/Product/CustomExtension.php" %}
```php
<?php declare(strict_types=1);
namespace Swag\BasicExample\Extension\Content\Product;
use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
class CustomExtension extends EntityExtension
{
public function extendFields(FieldCollection $collection): void
{
$collection->add(
// new fields here
);
}
public function getDefinitionClass(): string
{
return ProductDefinition::class;
}
}
```
{% endcode %}
Now we have to register our extension via the DI-container. If you don't know how that's done in general, head over to our guide about registering a custom service [Add a custom class / service](../../plugin-fundamentals/add-custom-service.md) or our guide about the [dependency injection](../../plugin-fundamentals/dependency-injection.md).
Here's our `services.xml`:
{% code title="<plugin root>/src/Resources/config/services.xml" %}
```markup
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Swag\BasicExample\Extension\Content\Product\CustomExtension">
<tag name="shopware.entity.extension"/>
</service>
</services>
</container>
```
{% endcode %}
### Adding a field with database
In this guide you're extending the product entity in order to add a new string field to it. Since you must not extend the `product` table with a new column, you'll have to add a new table which contains the new data for the product. This new table will then be associated using a [OneToOne association](add-data-associations.md#One%20to%20One%20associations).
Let's start with the `CustomExtension` class by adding a new field in the `extendFields` method.
{% code title="<plugin root>/src/Extension/Content/Product/CustomExtension.php" %}
```php
<?php declare(strict_types=1);
namespace Swag\BasicExample\Extension\Content\Product;
use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToOneAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
class CustomExtension extends EntityExtension
{
public function extendFields(FieldCollection $collection): void
{
$collection->add(
new OneToOneAssociationField('exampleExtension', 'id', 'product_id', ExampleExtensionDefinition::class, true)
);
}
public function getDefinitionClass(): string
{
return ProductDefinition::class;
}
}
```
{% endcode %}
As you can see, we're adding a new `OneToOneAssociationField`. Its parameters are the following, in correct order:
* `propertyName`: The name of the property which should contain the associated entity of type `ExampleExtensionDefinition` in the `ProductDefinition`. Property names are usually camelCase, with the first character being lower cased.
* `storageName`: Use the `id` column here, which refers to the `id` field of your product. This will be used for the connection to your association. Storage names are always lowercase and snake_cased.
* `referenceField`: In the `storageName` you defined one of the two connected columns, `id`. The name of the other column in the database, which you want to connect via this
association, belongs into this parameter. In that case, it will be a column called `product_id`, which we will define in the `ExampleExtensionDefinition`.
* `referenceClass`: The class name of the definition that we want to connect via the association.
* `autoload`: As the name suggests, this parameter defines if this association should always be loaded by default when the product is loaded. In this case,
we definitely want that.
#### Creating ExampleExtensionDefinition
You most likely noticed the new classs `ExampleExtensionDefinition`, which we're going to create now. It will contain the actual string field that we wanted to add to the product.
Creating a new entity is not explained in this guide, so make sure you know [this guide](add-custom-complex-data.md) beforehand.
Our new entity will be located in the same directory as our extension. Let's first have a look at it before going into the explanation:
{% code title="<plugin root>/src/Extension/Content/Product/ExampleExtensionDefinition.php" %}
```php
<?php declare(strict_types=1);
namespace Swag\BasicExample\Extension\Content\Product;
use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToOneAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
class ExampleExtensionDefinition extends EntityDefinition
{
public const ENTITY_NAME = 'swag_example_extension';
public function getEntityName(): string
{
return self::ENTITY_NAME;
}
public function getEntityClass(): string
{
return ExampleExtensionEntity::class;
}
protected function defineFields(): FieldCollection
{
return new FieldCollection([
(new IdField('id', 'id'))->addFlags(new Required(), new PrimaryKey()),
new FkField('product_id', 'productId', ProductDefinition::class),
(new StringField('custom_string', 'customString')),
new OneToOneAssociationField('product', 'product_id', 'id', ProductDefinition::class, false)
]);
}
}
```
{% endcode %}
We've created a new entity definition called `ExampleExtensionDefinition`, as mentioned in the `CustomExtension` class. Its table name will be `swag_example_extension` and it will have custom entity class called `ExampleExtensionEntity`, as you can see in the `getEntityClass` method. This will remain an example, creating the actual entity `ExampleExtensionEntity` is not part of this guide.
So let's have a look at the `defineFields` method. There's the default `IdField`, that almost every entity owns. The next field is the actual `product_id` column, which will be necessary in order to properly this entity with the product and vice versa. It has to be defined as `FkField`, since that's what it is: A foreign key.
Now we're getting to the actual new data, in this example this is just a new string field. It is called `customString` and could now be used in order to store new string data for the product in the database.
The last field is the inverse side of the `OneToOneAssociationField`. The first parameter defines the name of the propery again, which will contain the `ProductEntity`. Now have a look at the second and third parameter - those are the same as in the `ProductDefinition`, but the other way around. This is important, do not forget to do that!
The fourth parameter is the class of the associated definition, the `ProductDefinition` in this case. The last parameter, once again, defines the auto loading. In this example, the product definition will **not** be loaded, when you're just trying to load this extension entity. Yet, the extension entity will always automatically be loaded when the product entity is loaded, just like we defined earlier.
Of course, this new definition also needs to be registered to the DI container:
{% code title="<plugin root>/src/Resources/config/services.xml" %}
```markup
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Swag\BasicExample\Extension\Content\Product\CustomExtension">
<tag name="shopware.entity.extension"/>
</service>
<service id="Swag\BasicExample\Extension\Content\Product\ExampleExtensionDefinition">
<tag name="shopware.entity.definition" entity="swag_example_extension" />
</service>
</services>
</container>
```
{% endcode %}
#### Adding the new database table
Of course you have to add the new database table via a [database migration](../../plugin-fundamentals/database-migrations.md). Have a look into the guide linked above to see how exactly this is done. Here's the example migration and how it could look like:
```php
<?php declare(strict_types=1);
namespace Swag\BasicExample\Migration;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Migration\MigrationStep;
class Migration1614903457ExampleExtension extends MigrationStep
{
public function getCreationTimestamp(): int
{
return 1614903457;
}
public function update(Connection $connection): void
{
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `swag_example_extension` (
`id` BINARY(16) NOT NULL,
`product_id` BINARY(16) NULL,
`custom_string` VARCHAR(255) NULL,
`created_at` DATETIME(3) NOT NULL,
`updated_at` DATETIME(3) NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SQL;
$connection->executeStatement($sql);
}
public function updateDestructive(Connection $connection): void
{
}
}
```
#### Writing into the new field
As already mentioned, your new association is automatically being loaded every time a product entity is loaded. This section here will show you how to write to the new field instead.
As every [write operation](writing-data.md), this is done via the product repository in this example.
```php
$this->productRepository->upsert([[
'id' => '<your product ID here>',
'exampleExtension' => [
'customString' => 'foo bar'
]
]], $context);
```
In this case you'd write "foo bar" to the product with your desired ID. Note the keys `exampleExtension`, as defined in the product extension class `CustomExtension`, and the key `customString`, which is the property name that you defined in the `ExampleExtensionDefinition` class.
### Adding a field without database
Adding a field without saving its value to the database is a lot less complicated. First of all, you'll have to let Shopware know that you're going to take care of this field yourself and it doesn't have to search for it in the database. This is done by using the `Runtime` flag on the new field.
{% code title="<plugin root>/src/Extension/Content/Product/CustomExtension.php" %}
```php
<?php declare(strict_types=1);
namespace Swag\BasicExample\Extension\Content\Product;
use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Runtime;
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
class CustomExtension extends EntityExtension
{
public function extendFields(FieldCollection $collection): void
{
$collection->add(
(new StringField('custom_string', 'customString'))->addFlags(new Runtime())
);
}
public function getDefinitionClass(): string
{
return ProductDefinition::class;
}
}
```
{% endcode %}
In this case, you directly add the `StringField` to the extension class itself. Afterwards we're adding the `Runtime` flag to this field, so Shopware knows that it doesn't have to take care of this new field automatically. We're doing this ourselves now.
For this we need a new subscriber. If you are not familiar with a subscriber, have a look at our [Listening to events](../../plugin-fundamentals/listening-to-events.md) guide.
We can use the DAL event which gets fired every time the product entity is loaded. You can find those kind of events in the respective entities' event class, in this case it is `Shopware\Core\Content\Product\ProductEvents`.
Below you can find an example implementation where we add our extension, when the product gets loaded.
{% code title="<plugin root>/src/Subscriber/ProductSubscriber.php" %}
```php
<?php declare(strict_types=1);
namespace Swag\BasicExample\Subscriber;
use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Core\Content\Product\ProductEvents;
class ProductSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
ProductEvents::PRODUCT_LOADED_EVENT => 'onProductsLoaded'
];
}
public function onProductsLoaded(EntityLoadedEvent $event): void
{
/** @var ProductEntity $productEntity */
foreach ($event->getEntities() as $productEntity) {
$productEntity->addExtension('custom_string', new ArrayEntity(['foo' => 'bar']));
}
}
}
```
{% endcode %}
We're registering to the `ProductEvents::PRODUCT_LOADED_EVENT` event, which is fired everytime one or multiple products are requested. In the event listener method `onProductsLoaded`, we're then adding our own data to the new field via the method `addExtension`.
Please note that its second parameter, the actual value, has to be a struct and not just a string or other kind of scalar value.
After we've created our subscriber, we have to adjust our `services.xml` to register it. Below you can find our `services.xml`.
{% code title="<plugin root>/src/Resources/config/services.xml" %}
```markup
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Swag\BasicExample\Extension\Content\Product\CustomExtension">
<tag name="shopware.entity.extension"/>
</service>
<service id="Swag\BasicExample\Subscriber\ProductSubscriber">
<tag name="kernel.event_subscriber"/>
</service>
</services>
</container>
```
{% endcode %}
## Entity extension vs. Custom fields
[Custom fields](../custom-field/add-custom-field.md) are by default configurable by the admin user in the administration and they mostly support scalar types, e.g. a text-field, a number field or the likes. If you'd like to create associations between entities, you'll need to use an entity extension, just like we did here. Of course you can also add scalar values without an association to an entity via an extension.