Bei Deployments die ein per Composer erzeugtes Shopware 6 Projekt als Basis haben (was wohl alle neueren sind) muss man die JWT-Dateien immer noch zusätzlich erzeugen und sie müssen die richtigen Rechte haben.
Man kann auch Env-Variablen (JWT_PUBLIC_KEY und JWT_PRIVATE_KEY) verwenden, was bei mir aber irgendwie nicht korrekt funktionierte und beim Login in die Administration zu einer Exception führt.
Aber es gibt auch einen Weg ganz ohne JWT Keys und der verwendet das APP_SECRET aus der .env Datei.
Damit klappte auch ein Deployment auf platform.sh dann ohne Probleme.
Eine Bestellung zu bearbeiten in dem man z.B. ein LineItem löscht benötigt die Nutzung von Versionen. Was an sich nicht schwer ist, wenn man weiß
wie man es machen muss. (Beispiel ist Shopware 6.4)
MySQL Dump bei Shopware haben manchmal das Problem, dass DEFINER und Datenbanknamen an Triggers mit exportiert werden, die nicht zur neuen Datenbank passen, wenn die Datenbank anders heißt und man einen anderen Benutzernamen hat.
Man kann dann mit einem Texteditor wie nano, vi oder Notepad++ die Datei durchsuchen und es per Hand fixen. Nur doof wenn die Datei 6GB groß ist und keiner der Editoren mehr so richtig performant mit der Datei arbeiten will.
Dafür gibt es dann in der Linux Kommandozeile sed:
mysqldump -u demouser -p demo_webshop > ./dump_for_65_update.sql
sed 's/`demo_webshop`.//g' dump_for_65_update.sql > dump_for_65_update_clean.sql
sed -i 's|/\*!50017 DEFINER=`demouser`@`localhost`\*/||g' dump_for_65_update_clean.sql
mysql --database=demo65_webshop --user=demouser65 -p < ./dump_for_65_update_clean.sql
Man kann da sicher noch allgemeine Ausdrücke für schreiben, aber das lag dieses mal außerhalb dem was der Kunde bezahlt hätte.
Da man beim einfachen Entwickeln nicht ein AWS S3-Bucket für die Entwickler bereit stellen möchte, kann man hier sehr gut MinIO verwenden. Es lässt sich schnell in docker-compose einbinden und die FileSystems von Shopware können den normalen S3-Adapter verwenden.
Manchmal funktionieren ein paar Snippets nicht. Ich vermute es liegt daran wie ein Theme über nicht immer sehr gradlinige Wege überschrieben und erweitert wurde.
Z.B. steht dann "orion.footer.certificates" auf der Seite, obwohl die Snippets korrekt für alle Sprachen in der Administration gefunden werden. Also an sich sollte es dann ja funktionieren.
Lösung: Einmal den Übersetzungen ein 'X' anhängen, speichern, das 'X' wieder entfernen und erneut speichern. Dann sind sie in der Storefront auch richtig.
Weil sie dann aus der Datenbank geladen werden und nicht aus dem Theme/Plugin/App.
Wie man eine eigene Entity in die Suche integriert hatte ich schon erklärt. Was aber wenn man eine vorhandene Entity um weitere durchsuchbare Felder erweitern will? Das geht auch relativ einfach.
if (module?.manifest?.defaultSearchConfiguration) {
module.manifest.defaultSearchConfiguration = {
...module.manifest.defaultSearchConfiguration,
extensions: {
// In case some other plugin has already done this trick; we do not want to remove theirs.
...(module.manifest.defaultSearchConfiguration.extensions ?? {}),
// Add our extension fields to the omnisearch
customFields: {
customer_debitor_set_number: {
_searchable: true,
_score: searchRankingPoint.HIGH_SEARCH_RANKING,
},
}
},
};
}
Auch wenn dort die Felder hierarchisch angegeben werden, sind diese bei den Snippets flach strukturiert.
In einem Cart-Validator sollte man vermeiden vom Cat-Service die getCart()-Methode zu verwenden. Ich habe einen Service der mir für den Validator benötigte Daten lieferte und dabei auch einen Wert aus dem Cart generierte. getCart() triggert aber wieder den Validator.. Endlossschleife! Also am besten wirklich nur das in die Validator-Methode rein gereichte Cart-Object verwenden.
Es gibt manchmal CustomFields in denen man Daten wie externe Ids, ein Import- oder Export-Datum oder ein einfaches Bool-Flag speichern möchte. Der normale Admin-Benutzer darf diese Daten gerne sehen sollte sie aber nicht ändern, weil er oder sie nicht das nötige Wissen über die internen Abläufe des Plugins hat, um genau zu wissen, welche Auswirkungen so eine Änderung hat.
Deswegen ist es gut so ein CustomField readonly zu machen und am Besten komplett zu disablen. Das ist über die config des CustomFields sehr einfach möglich. In der Manifest einer App kann dass leider schon wieder ganz anders sein, weil dort sowas nicht vorgesehen ist.
Oft ist es sehr viel einfacher direkt etwas in die Suche der Administration einzugeben, als umständlich eine Seite zu öffnen und etwas aus der Liste per Hand oder Browser-Suche heraus zu suchen.
Eigene oder fehlende Entitäten dort zu integrieren ist an sich recht einfach und logisch. Es gibt hier eine Anleitung die aber leider so für mich nicht funktioniert hat, weil ein wichtiger Teil fehlte.
Routennamen sind hier erstmal nur Beispiel haft vergeben.
Step 1 Ich gehe davon aus das ein Plugin existiert mit einem JS-Module, das mindestens eine Route hat und dessen Name nach dem Schema {vendor}-{name} aufgebaut ist. Zum Module müssen wir wie beschrieben
einige wenige Dinge ergänzen:
{
...
entity: 'ce_my_entity',
...
}
hier kommt später noch was dazu!
Step 2
Den Type (der Entität) hinzufügen. Der Name muss nicht dem Namen der Entität entsprechen, ist aber nicht verkehrt es so zu machen.
Damit kennt die Suche nun den neuen Type und dann theoretisch schon danach suchen.
Step 3
Jetzt müssen wir festlegen wie unsere Entität bei den Ergebnissen dargestellt werden soll. Dafür erweitern wir ein Template und machen es der Suche bekannt.
Was noch fehlt Soweit ist alles gut und nach Anleitung. Aber es funktionierte einfach nicht. Es wurde nach allen möglichen Entitäten gesucht nur nicht nach der eigenen. Nach viel Gesuche kam ich dann darauf, dass bei den Preferences, die für die Liste der Entities genutzt wird, meine eigene garnicht aufgelistet wurde. Warum? Weil ich natürlich keine Preferences dafür hinterlegt hatte, weil es nirgendwo angegeben war.
Diese Preferences findet man im Profile seine Admin-Users und kann es dort alles noch genauer anpassen, wie die Suche suchen soll. Hier werden nun die Felder name und description angeboten und auch direkt aktiviert.
Damit funktionierte die Suche dann auch sofort wie gewünscht.
Wenn man sich in einer Shopware 6 SaaS Umgebung und Apps bewegt, hat man nicht mehr die Möglichkeit Rules im PageLoader zu prüfen und ein bool-Value rein zu reichen, weil man nun alles via Twig machen muss. Entweder im Template oder in den App Scripts, die die PageLoader-Events ersetzt haben.
Geht zum Glück an ganz einfach auch wenn es kein array_intersect gibt.
{% set hideBuyButton = false %}
{% set checkRuleIds = config('MyApp.config.checkRules') %}
{% set intersect = checkRuleIds|filter((rule) => rule in context.context.ruleIds) %}
{% if intersect|length > 0 %}
{% set hideBuyButton = true %}
{% endif %}
Während man in 6.4 noch beliebigen eigenen HTML-Code in z.B. CMS-Elementen oder Snippets eingeben konnte, filtert 6.5 Teile dieses Codes nun heraus. Er gilt als möglicherweise unsicher. Wenn man nun von 6.4 auf 6.5 migriert und z.B. style-Tags entfernt werden, wäre es sehr aufwendig alles nun in SCSS und dem Theme unterzubringen. Einfacher ist es den Sanitizer zu deaktivieren und das selbe Verhalten wie bei 6.4 wieder zu haben.
In der config/packages/shopware.yaml kann den Sanitizer einfach deaktiveren.
Update meiner Shopware Docker Umgebung. Funktioniert mit 6.4. An 6.5 arbeite ich noch. Es ist Imagick installiert, um z.B. automatisch beim Upload von PDFs die erste Seite als JPG zu speichern und in einem CustomField als Vorschau zu verlinken.
RUN docker-php-ext-install dom \
&& docker-php-ext-install pdo \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install curl \
&& docker-php-ext-install zip \
&& docker-php-ext-install intl \
&& docker-php-ext-install xml \
&& docker-php-ext-install xsl \
&& docker-php-ext-install fileinfo
RUN mkdir -p /usr/src/php/ext/imagick
RUN curl -fsSL https://github.com/Imagick/imagick/archive/06116aa24b76edaf6b1693198f79e6c295eda8a9.tar.gz | tar xvz -C "/usr/src/php/ext/imagick" --strip 1
RUN docker-php-ext-install imagick
RUN echo 'memory_limit = 512M' >> /usr/local/etc/php/php.ini
RUN a2enmod rewrite
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php composer-setup.php --2.2 #there are problem
RUN mv composer.phar /usr/local/bin/composer
# copy conf-file to /etc/apache2/sites-enabled/000-default.conf
RUN mkdir /files;
COPY ./setup.sh /files/setup.sh
ENTRYPOINT ["sh", "/files/setup.sh"]
Nach einigen Hin und noch mehr Her, hat die Interspark Inc bestätigt, dass sie keine IPs der Interspark GmbH übernommen hat und somit auch nicht die IPs an den beiden Plugins besitzt. War am Ende sehr schnell feste gestellt und ich übernehme die Plugins nun wieder, da es ja sonst niemanden gibt, der Ansprüche erhebt. Die Lizenz wird auf die MIT/BSD Lizenz geändert und die GitLab Projekte werden öffentlich.
Während die Shopware 5 Version schon in mehreren Shops bewiesen hat, das sie funktioniert, steht bei der Shopware 6 Version noch aus, diese als production-ready zu deklarieren.
Sie hat die meisten Test gut gemeistert und sollte nun auch mit Shopware 6.5 funktionieren. Trotzdem steht noch aus diese in einer produktiven Umgebung über einige Tage laufen zulassen.
Builds und Releases wird es ab jetzt hier geben. Jeder der Interesse oder Bedarf hat kann sich die Plugins installieren oder auch gerne mit weiter entwickeln.
Eine App-Version des Plugins, um damit auch die Shopware 6 Cloud Version (SaaS) abzudecken ist bis jetzt nicht von mir geplant, außer jemand würde mir dafür 5000 EUR geben, weil der Aufwand wirklich groß ist. Es wäre eine vollständig eigenständige Software, die außerhalb von Shopware läuft und dort wo die DAL verwendet wird dann die Shopware API (mit Multi-Tenant usw) aufrufen würde.
Manchmal möchte man etwas Auslösen, wenn in der Administration etwas gespeichert wird. Aber auch nur wenn es von dort kommt. Nicht wenn es per CLI oder Storefront ausgelöst wird. Da hilft der Context.
Wenn man eigne Snippet-Sets anlegen möchte, bekommt man eine Auswahl an Base-Files angezeigt. Normal sind es die messages.de-DE und die messages.en-GB. Was aber wenn man eine eigene Sprache benötigt, die nicht da und auch nicht im Language-Pack definiert ist? Hier gilt Convention-over-Configuration. Die Snippet-Datei muss einfach nur auf eine bestimmte Art und Weise benannt sein, um als Base-File erkannt zu werden.
Es gibt zwei Komponenten wenn es um die Anpassung oder die Änderungen am Cart oder seiner Items geht. Die Namen sind aber nicht immer klar in der Bedeutung der verschiedenen Schritte.
Collector: Man sammelt hier nicht die zu ändernden Cart-Items sondern die Daten, die für die Änderungen benötigt werden. Also Datenbank, API-Abfragen und Berechnungen gehören hier rein.
Processor: Hier werden die im ersten Schritt gesammelten Daten auf die Cart-Items angewendet. Auch zusätzliche Items hinzufügen sollte hier geschehen. Wichtig ist natürlich eine Prüfung, ob es diese Items schon gibt.
Der Collector wird einmal ausgeführt. Der Processor kann mehr mals ausgeführt werden, abhängig davon wie viele Änderungen so geschehen.
Was passiert wenn man die Berechnungen nicht im Collector macht sondern im Processor aufgrund der vorhandenen Daten in den Items? Spannender Effekt, der einen echt Zeit kosten kann um hinter das Problem zu kommen.
Ein Rabat von 5EUR in pseudo-Code:
item.price = item.price - 5.00;
Nun wird der Processor 6mal ausgeführt. Ein netter Rabatt von 30EUR ist die Folge.
Im Collector sammelt man also im 1. Schritt alle neuen Daten zusammen und setzt diese im 2. Schritt im Processor an den richtigen Stellen (wenn nötig auch mehrmals).
Benutzer abhängige Ansichten oder Aktionen sind in der Shopware 6 Administration sehr selten. Aber gerade auf dem Dashboard kann toll Ansichten und Nachrichten unterbringen, die speziell für einen Benutzer oder eine Benutzergruppe gedacht sind. Aber dafür muss man wissen wer gerade eingeloggt ist, um z.B. ein Criteria mit den richtigen Filtern zu versehen.
Ich weiß, dass als ich die Namen der Trigger zum ersten mal benutzt hatte, ich keine Übersetzungen brauchte und auch etwas verwundert war, dass es einfach so ging. Hat sich nun wohl geändert und es läuft alles nach dem Schema sw-flow.triggers.XXXX.
Also checkout.order.export.success spaltet sich auf in:
Wenn man den Lieferschein erzeugt, fällt manchmal auf dass das eine Datum sich im Format stark unterscheidet. Es wäre so als würde es eine andere Locale als die anderen benutzen.. oder gar keine.
Der Fehler liegt direkt in der delivery_note.html.twig:
% block document_side_info_contents %}
{{ parent() }}
<tr><td>{% trans with {'%deliveryDate%': config.custom.deliveryDate|format_date('medium', locale=order.language.locale.code)} %}document.deliveryDate{% endtrans %}</td></tr>
{% endblock %}
[code]
Wenn man sich die anderen Datum-Formate in der base oder letter_header Datei anguckt fällt auf, dass hier das Locale nicht aus der Order stammt bzw nicht daraus gelesen wird. Korrekt wäre hier also:
[code]
% block document_side_info_contents %}
{{ parent() }}
<tr><td>{% trans with {'%deliveryDate%': config.custom.deliveryDate|format_date('medium', locale=locale)} %}document.deliveryDate{% endtrans %}</td></tr>
{% endblock %}
Nach dem ich einmal gesehen habe, dass jemand aus dem Meta-Informationen von page sich per über den Meta-Title den Namen der aktuellen Kategorie geholt hat und dieser nur der Name ist solange kein SEO-Title vergeben wurde, hier ein mal der korrekte Weg:
{{ page.header.navigation.active.name }}
Etwas versteckt, aber da findet man die Category-Entity und darin den Namen (natürlich auch mit entsprechender Übersetzung).
Daten per Criteria abfragen und in einer Table oder einer Form darzustellen oder verarbeiten ist in der Shopware 6 Administration extrem einfach und fast zu 100% genau so wie in PHP. Aber manchmal will man auch auf eine sehr spezielle Art oder Weise Daten erhalten und diese dem Benutzer direkt als Download anbieten können.
Hier ein kleines Beispiel eines CSV-Downloads der von einem API-Controller mit der Route "/api/example-module/download/csv/{id}" kommt. Man kann aber genau so gut alles per Criteria abfragen und das CSV in JavaScript bauen.
Die Methode für den Download sieht mit Controller-Lösung so aus:
Das Prinzip ist an sich sehr einfach. Man baut sich ein a-Element mit gesetzten download-Attribute und simuliert einen Click darauf. Nur hinterlegt man hier die Daten als Data-URL und nicht die URL zum Controller direkt. Das würde nicht funktionieren als dann dort die Authorization-Infos im GET-Header fehlen würden.
Die Data-URL kann eben aus Daten von einem Controller gebaut werden oder direkt aus Daten, die JavaScript aggregiert wurden.
Sehr viel einfacher als die alten Lösungen mit FileSaver-Lib und viel hin und her wandeln von Blobs.
Falls man seinen Shopware 6 in einem Shared-Hosting Paket von Cyon (Schweiz) betreibt, ist man es ja schon fast gewohnt, dass es Probleme beim Bauen der Administration über CLI gibt. Momentan gibt es wohl Probleme mit Puppeteer. Die Lösung hier ist es vor eine entsprechende Umgebungsvariable zu setzen:
export PUPPETEER_SKIP_DOWNLOAD='true'
Dann geht es wieder.. nicht schnell aber das war es dort ja noch nie.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! administration@1.0.0 build: `mode=production webpack`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the administration@1.0.0 build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /var/www/.npm/_logs/2022-11-24T11_18_35_338Z-debug.log
sollte man gucken, ob vorher ein Fehler dieser Art aufgetreten ist
Meine erste Lösung war es den MD5-Hash als Dateiname zu nehmen. Dann später kam wollte ich doch lieber ein CustomField dafür nutzen (wie ich auch schon in der Shopware 5 Version ein Attribute genutzt hatte). Funktionierte alles super bis ich dann auf ein System kam, das nicht im dev-Mode lief. Am fileName hängt wohl mehr als man denkt und ich habe gelernt, dass man in einem produktiven Shopware 6 nicht den fileName ändern sollte. Kommt mit auf meine Liste der ganz großen Shopware 6 Nein-Nein's.
Oft will man einfach nur den Status der Stellung anpassen, weil irgendwas mit der Bestellung nicht stimmt oder man sie auf abgeschlossen setzen will, aber der Rest über das ERP/die WaWi läuft. Man will keine Mail an den Kunden senden. Leider ist die Mail immer direkt aktiviert und man muss immer daran denken diese zu deaktivieren.
Nervig.. also ein tolles kleines (wirklich kleines) Plugin-Projekt:
die Art und Weise erinnert mich etwas wie ich damals bei meinen cJSv2-Framework die Controller an Elemente gebunden und initialisiert habe.. der default-Name der Init-Methode war da auch init.
Der DomAccess-Helper ist super und sollte man so verwenden. Über den kann man auch den QuerySelector nutzen und dass schützt hoffentlich davor, dass ein Haufen klassischer Webdesigner versuchen JQuery wieder da rein zu bringen. Der HttpClient ist auch nett aber man kann genau so gut direkt mit fetch() arbeiten, wenn man nicht unbedingt irgendwelche Steinzeit Browser supporten möchte. Aber besonder mag ich von den Helpern DeviceDetection.isTouchDevice() weil man damit einfach unterschieden kann ob man gerade ein Touch oder ein MouseEnter
Event nutzen muss.
Wenn man in der Storefront an einem eignem JavaScript-Plugin arbeitet ist es beim Debuggen, oft nervig, dass der JavaScript Code nicht direkt lesbar ist. Oft weiß man beim eigenen Code direkt was los ist, aber gerade wenn 3rd Party Plugins auch mit rein spielen, kommt man nicht drum herum sich Exceptions mit lesbaren Stacktrace anzeigen zu lassen.
Das ist zum Glück extrem einfach:
* Port 9998 aus dem Docker-Container durchleiten
* Dem SalesChannel in dem man entwickelt die Domain "localhost" zu ordnen
* das Script bin/watch-storefront.sh starten
Nun kann man localhost:9998 im Browser aufrufen. Der JavaScript Code ist lesbar und Änderungen werden sofort übernommen ohne dass man die Storefront neu bauen muss.
Einmal schnell gucken, wie viele und welche Art von Produkten über welchen SalesChannel und URL zu kaufen sind.
SELECT x.url, 'single' type, count(x.id)
FROM (
select d.url, p.id, p.parent_id, count(p2.id) children from sales_channel_domain d
join sales_channel sc on d.sales_channel_id = sc.id
join product_visibility pv on pv.sales_channel_id = sc.id
join product p on p.id = pv.product_id
left outer join product p2 on p.id = p2.parent_id
where sc.active = 1
group by d.url, p.id, p.parent_id
)x
WHERE x.parent_id is null and x.children < 1
group by x.url
union all
SELECT x.url, 'main' type, count(x.id)
FROM (
select d.url, p.id, p.parent_id, count(p2.id) children from sales_channel_domain d
join sales_channel sc on d.sales_channel_id = sc.id
join product_visibility pv on pv.sales_channel_id = sc.id
join product p on p.id = pv.product_id
left outer join product p2 on p.id = p2.parent_id
where sc.active = 1
group by d.url, p.id, p.parent_id
)x
WHERE x.parent_id is null and x.children > 0
group by x.url
union all
SELECT x.url, 'variant' type, count(x.id)
FROM (
select d.url, p2.id, p2.parent_id, 0 children from sales_channel_domain d
join sales_channel sc on d.sales_channel_id = sc.id
join product_visibility pv on pv.sales_channel_id = sc.id
join product p on p.id = pv.product_id
left outer join product p2 on p.id = p2.parent_id
where sc.active = 1
)x
group by x.url
Gerade in Migrations kommt man manchmal in darum herum mit den UUIDs arbeiten zu müssen. Da es binary-Daten sind muss man etwas mit den anstellen, um die aus Shopware 6 bekannte Darstellung zu erreichen und auch um diese wieder die in Datenbank zu bekommen.
Lesen der Id: Um die aus Shopware 6 gewohnte Darstellung zu bekommen muss man in MySQL die HEX-Function verwenden. Zusätzlich muss der String noch in Kleinbuchstaben umgewandelt werden.
Schreiben der Id: Zuerst wieder alles in Großbuchstaben umwandelnt. Dann muss mit der MySQL-Function UNHEX der Hex-String wieder in binary Data umgewandelt werden.
Es bleibt zwar etwas umständlich, aber ist damit durch aus handhabbar. Da es in dem Sinne kein Autoincrement gibt für die UUIDs hilft dann dort Uuid::randomHex().
Etwas was bei Shopware 6 so komplett fehlt und wo ich schon mehrmals gehört habe, dass es Kunden/Benutzern Probleme macht, ist die Hauptvariante bei Shopware 6. Die Hauptvariante ist immer die Variante, die im Dialog beim Erzeugen der Varianten zuerst gewählt wurde. Es gibt danach keine Möglichkeit mehr es zu ändern.
An sich ist die Hauptvariante aber auch nur ein FK am Product. Man kann dort einfach eine andere Variante eintragen und schon hat man seine neue Hauptvariante. Also alles auf DB-Ebene sehr einfach.
Auch das in die Administration einbauen war dann relativ einfach. Man brauchte nur zusätzlich ein Repository und eine Criteria für Varianten (WHERE parent_id = ... ). Die Id kann man direkt per v-model setzen am Product.
Es gibt grob zwei Varianten wie man eigene Informationen im Warenkorb an den LineItems halten kann. Einmal gibt es die Extensions wo man eigene Structs hinterlegenk ann und es gibt den Payload wo man einfach Werte wie einen String oder ein Boolean hinterlegen kann.
Der Hauptunterschied ist, dass das Payload mit vom Cart LineItem in das Order LineItem übernommen wird ohne dass man etwas tun muss. Damit spart man sich auch CustomFields am Order LineItem. Die sind dann wirklich für Order-Angelegenheiten da, oder man kann nach dem Speichern der Order diese mit Hilfe des Payload füllen.
Extensions sind gut für Runtime-Data. Hier muss man drauf achten, dass Before und After add-Events, die Collcetor und Processors noch mit eingreifen und zusätzliche Logik triggern. Wenn man selbst den Cart bearbeitet (z.B. über eine eigene Controller-Action) muss man den CartService mit seiner Recalc-Method bemühen. Aber insgesamt ist der Cart zusammen mit dem CartService einfach zu beherrschen und auch zu manipulieren.
Ich habe mich am Ende entschieden beides gleichzeitig zu verwenden, wobei in den Extensions alle möglichen für die Darstellung wichtigen Daten stehen und im Payload nur das Aggregat dieser Daten als Boolean. Mehr brauche ich für den Export der Order auch nicht. Warum diese also speichern?
PS: 0 Euro Produkte im Warenkorb und Bestellungen sind in Shopware 6 kein Problem mehr.
Während ja jede Entity eine id hat, hat nicht jede ein name-Field. Ich musste doch etwas suchen bis ich erstmal die Dokumentation zu der Component gefunden habe und dann mir klar war wie ich ein andere Feld angeben kann. Am Ende natürlich genau so wie man es sich dachte nur der Prop-Name ist irgendwie seltsam, weil Label ja auch anders dort verwendet wird als Begriff.
Plugins für Shopware 6 zu schreiben ist an sich garnicht so unterschiedlich zu dem selben Vorgang für Shopware 5. Was nur anders geworden ist, dass Shopware eigene XMLs und Enlight-Components durch den Composer und Symfony abgelöst wurden. Man kann nun wirklich Namespaces definieren, die unabhängig vom Plugin-Namen sind und es ist kein Problem mehr den Composer mit Plugins zu verwenden, sondern es ist jetzt der zu gehende Weg.
Setup
Wie gehabt kann man sein Plugin unter custom/plugins anlegen.
custom/plugins/HPrOrderExample
Zu erst sollte man mit der composer.json anfangen.
Wichtig in der composer.json ist einmal der PSR-4 Namespace den man auf das src/ Verzeichnis setzt und der "extra" Teil. Dort wird alles definiert, was mit Shopware zu tun hat. Hier wird auch die zentrale Plugin-Class angegeben, die nicht mehr wie in Shopware 5 den selben Namen des Verzeichnisses des Plugins haben muss. Auch sieht man hier dass der Namespace-Name nichts mehr mit dem Verzeichniss-Namen zu tun hat.
Wir könnten durch die composer.json das Plugin auch komplett außerhalb der Shopware-Installation entwickeln, weil die gesamte Plattform als Dependency eingebunden ist. Deswegen müssen wir auch einmal den Composer ausführen.
php composer install
Plugin programmieren Nun fangen wir mit dem Schreiben des Plugins an.
Die Plugin-Klasse enthält erst einmal keine weitere Logik:
<?php
namespace HPr\OrderExample;
use Shopware\Core\Framework\Plugin;
class OrderExample extends Plugin{
}
Wir wollen auf ein Event reagieren. Dafür benutzen wir eine Subscriber-Klasse. Während man diese in Shopware 5 nicht unbedingt brauchte und alles in der Plugin-Klasse erldedigen konnte, muss man nun eine extra Klasse nutzen und diese auch in der services.xml eintragen. Die Id des Services ist der fullqualified-classname des Subscribers
Resources ist vordefiniert, kann aber in der Plugin-Klasse angepasst werden, so dass diese Dateien auch in einem anderen Package liegen könnten.
src/Subscribers/OnOrderSaved
<?php
namespace HPr\OrderExample\Subscribers;
use Shopware\Core\Checkout\Order\OrderEvents;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class OnOrderSaved implements EventSubscriberInterface {
/**
* @return array The event names to listen to
*/
public static function getSubscribedEvents()
{
return [
OrderEvents::ORDER_WRITTEN_EVENT => ['dumpOrderData']
];
}
public function dumpOrderData(EntityWrittenEvent $event) {
file_put_contents('/var/test/orderdump.json', json_encode($event->getContext()));
}
}
Hier sieht man, dass nur noch Symfony benutzt wird und keine Shopware eigenen Events.
Wir schreiben einfach die Order-Entity, die gespeichert wurde in eine Datei. Dort können wir UUID und ähnliches der Order finden.
Das ganze kann man dann weiter ausbauen und auf andere Order-Events (die man alle in der OrderEvents.php finden kann) reagieren.
Installieren
Damit ist das Plugin auch schon soweit fertig. Den Composer haben wir schon ausgeführt (ansonsten müssen wir es spätestens jetzt machen).
Nun geht es über die UI oder die Console weiter. Dort wird das Plugin installiert und und aktiviert.
Wenn man nun eine Bestellung ausführt wird, deren Entity in die oben angegebene Datei geschrieben.
Ich habe gerade die Early Access Version von Shopware 6 installiert und erst einmal einen Hersteller und einen eigenen Artikel angelegt.
Das Problem war, dass ich keine Scale-Unit mit angeben konnte, da ich einfach noch keine angelegt hatte und nicht alle Artikeldaten nochmal neu eingeben wollte habe ich das Feld einfach leer gelassen. Speichern klappte ohne Probleme.
Die Kategorie funktionierte dann aber nicht mehr.
Zum Glück war mir das ja direkt aufgefallen, dass ich da was nicht eingeben konnte und habe erst einmal versucht eine Unit anzulegen und zu ergänzen.
Danach funktionierte es dann auch alles.
Da muss man also aufpassen, weil fehlende Eingaben noch viel kaputt machen können. Hab da auch gleich ein Issue dafür aufgemacht.
Blog-entries by search-pattern/Tags:
Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?