Search code examples
phpvuejs2twigshopwareshopware6

Shopware 6: Unable to save an associate field (media) in the new custom plugin


I have created a custom plugin that is doing a CRUD (create, read, update, delete). In the Edit page, all the fields are saved, except the sw-media-field. When I select a media file, it shows but when clicking on save, it reverted to the old file (or none if nothing was there).

The new entity name is 'certificate' and the column name is media_id. What could be wrong here that prevents only the media_id column to be updated?

CertificateDefinition class:

<?php declare(strict_types=1);

namespace Certificate\Entity;

use Shopware\Core\Checkout\Customer\CustomerDefinition;
use Shopware\Core\Content\Category\CategoryDefinition;
use Shopware\Core\Content\Media\MediaDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\BoolField;
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\ManyToOneAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;

class CertificateDefinition extends EntityDefinition
{
    public const ENTITY_NAME = 'certificate';

    public function getEntityName(): string
    {
        return self::ENTITY_NAME;
    }

    public function getEntityClass(): string
    {
        return CertificateEntity::class;
    }

    public function getCollectionClass(): string
    {
        return CertificateCollection::class;
    }

    protected function defineFields(): FieldCollection
    {
        return new FieldCollection(
            [
                (new IdField('id', 'id'))->addFlags(new Required(), new PrimaryKey()),
                (new StringField('name', 'name'))->addFlags(new Required()),
                (new StringField('file_name', 'fileName'))->addFlags(new Required()),
                (new BoolField('active', 'active'))->addFlags(new Required()),

                (new FkField('media_id', 'media', MediaDefinition::class))->addFlags(new Required()),
                (new ManyToOneAssociationField('media', 'media_id', MediaDefinition::class, 'id', true)),
            ]
        );
    }
}

detail.html.twig:

<sw-page class="certificate-detail">
    <template slot="smart-bar-actions">
        <sw-button :routerLink="{ name: 'webconia.certificate.list' }">
            {{ $t('certificate.detail.backButtonText') }}
        </sw-button>

        <sw-button-process :isLoading="isLoading"
                           :processSuccess="processSuccess"
                           variant="primary"
                           @process-finish="saveFinish"
                           @click="onClickSave">
            {{ $t('certificate.detail.saveButtonText') }}
        </sw-button-process>
    </template>
    <template slot="content">
        <sw-card-view>
            <sw-card v-if="certificate">
                <sw-field :label="$t('certificate.list.name')" v-model="certificate.name"
                          validation="required"></sw-field>
                <sw-field :label="$t('certificate.list.fileName')" v-model="certificate.fileName"
                          validation="required"></sw-field>
                <sw-switch-field :label="$t('certificate.detail.active')" v-model="certificate.active" size="medium"
                          validation="required"></sw-switch-field>

                <sw-media-field
                        :media-id="certificate.media.id"
                        fileAccept="application/pdf"
                        label="PDF upload"
                        @media-id-change="onMediaIdChange"
                        >
                </sw-media-field>
                <div>{{ certificate.media.fileName|json_encode }}{{ certificate.media.id|json_encode }}</div>

            </sw-card>
        </sw-card-view>
    </template>
</sw-page>

index.js:

import template from './certificate-detail.html.twig';
const { EntityCollection, Criteria } = Shopware.Data;

Shopware.Component.register('webconia-certificate-detail', {
    template,

    inject: [
        'repositoryFactory'
    ],

    metaInfo() {
        return {
            title: this.$createTitle()
        };
    },

    data() {
        return {
            repository: null,
            mediaReopsitory: null,
            isLoading: null,
            processSuccess: null,
            certificate: null
        };
    },

    created() {
        this.repository = this.repositoryFactory.create('certificate');
        this.mediaReopsitory = this.repositoryFactory.create('media');
        this.getCertificate();
    },
    methods: {
        onMediaIdChange(mediaId) {
            const mediaRepository = this.repositoryFactory.create('media');
            mediaRepository.get(mediaId, Shopware.Context.api).then((media) => {
                this.certificate.media = media;
                // this.certificate.mediaId = mediaId;
            });
        },
        getCertificate() {
            this.repository
                .get(this.$route.params.id, Shopware.Context.api, new Criteria().addAssociation('media'))
                .then((entity) => {
                    this.certificate = entity;
                });
        },
        onClickSave() {
            this.isLoading = true;
            // Save certificate
            this.repository
                .save(this.certificate, Shopware.Context.api)
                .then(() => {
                    this.getCertificate();
                    this.isLoading = false;
                    this.processSuccess = true
                }).catch((exception) => {
                this.isLoading = false;
                this.createNotificationError({
                    title: this.$tc('certificate.detail.errorTitle'),
                    message: exception
                });
            });
        },
        saveFinish() {
            this.processSuccess = false;
        },
    }
});

Solution

  • The propertyName of the FkField shouldn't be the same as the propertyName of the association. Change it to something unique within the definition, like mediaId.

    (new FkField('media_id', 'mediaId', MediaDefinition::class))->addFlags(new Required()),
    (new ManyToOneAssociationField('media', 'media_id', MediaDefinition::class, 'id', true)),
    

    You must also add getters and setters for that new mediaId property to your entity class.

    You can then associate an existing media entity by setting the foreign key field directly like that:

    onMediaIdChange(mediaId) {
        this.certificate.mediaId = mediaId;
    },
    

    Or even simpler just use it as a model bind of sw-media-field and you don't even need a change handler:

    <sw-media-field v-model="certificate.mediaId" />