In HTML gibt es die Möglichkeit Inputs auch außerhalb einer Form zu haben und diese mit einer Form zu verknüpfen. Dazu nutzt man das form-Attribute. Gerade im Checkout von Shopware 6 ist es echt praktisch. Beim DatePicker besteht aber das Problem, dass das Input, dass man selbst definiert durch ein neues Input-Feld ersetzt wird, wenn Flatpickr sich initialisiert. Diesem neuen Input muss man auch das form-Attribute geben.
Tut man es nicht, funktioniert z.B. das required-Flag am DatePicker nicht.
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.
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"]
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.
Diese Meldung muss nicht wirklich was großes bedeuten, denn es kann sein, dass beim Go-Live einfach erst HTTPS aktiviert wurde und vorher nur HTTP da war.
Die APP_URL ist einmal in der .env zu finden und dann nochmal in der Datenbank in der Table system_config mit dem key core.app.shopId. Dort liegt ein JSON-Object, das auch nochmal die APP_URL enthalten kann.
Diesen Wert zu ändern hat geholfen. Wenn man komplett die Domain wechselt und noch eine Stage-Domain für den Shop bei Shopware einträgt, ist es natürlich etwas anderes.. aber für HTTP/HTTPS Probleme sollte es so ausreichen.
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.
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
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:
Ich arbeite nun doch schon einige Zeit mit Shopware 6. Während ich am Anfang vielen Konzepten etwas kritisch gegenüberstand, bin ich nun doch sehr von fast allen Dingen überzeugt. Ich habe viele verschiedene Dinge schon mit Shopware 6 realisiert und einiges wäre in Shopware 5 nicht so einfach gewesen.
Gerade Vue.js in der Administration ohne irgendwelche zusätzlichen Lizenzen nutzen zu können ist super. Ich mochte immer Vue + Bootstrap und Symfony. Also am Ende fühle ich mich so was von extrem in meinen Vorlieben bestätigt... wenn auch Shopware diese Kombinationen nutzt muss ich ja schon immer richtig gelegen haben :-)
Varianten sind immer noch viel zu kompliziert. Daten in das Model für Emails rein zubekommen ist wirklich viel zu umständlich. N:M Relation in DAL ist umständlich bzw wie früher mit puren SQL. Entweder alles vorher löschen oder sich merken was genutzt wird und alles was nicht dazugehört löschen. Aber am Ende kommt man ja gut damit klar... ist eben wie mit JDBC oder PDO direkt zu arbeiten und dass habe ich lange genug gemacht.
So.. aber was kann man alles mit Shopware 6 so alles bauen? Ich habe bis jetzt nur für Kunden direkt entwickelt und falls jemand etwas hier von gerne hätte, geht das leider nur über Anfrage und dann wird ein Angebot erstellt.
1. Adressänderungen verhindern Wie schon bei Shopware 5 war es nötig, weil SAP sonst überfordert ist.
2. Register-Form erweitern Das zu Erweitern war am Ende sehr viel einfacher als gedacht und ich nutzt einfach das Data-Mapping Event für den Customer. Der richtige Weg? Für mich funktioniert er gut.
3. Blog Einträge Das kostenlose Blog-Plugin ist schon echt super. Es fehlt nur ein Flag um Posts von er Suche auszuschließen, Typen, Rechte über Rules und Datei-Anhänge. Ja.. ich weiß.. ich könnte mich da beteiligen und alles einbauen.. sollte ich wirklich machen. Aber bis jetzt war nur Zeit das Plugin zu erweitern um Rezepte aus einem Panipro-System zu importieren.
Typen sind natürlich dynamisch und es werden nur Typen auf der linken Seite angezeigt, die auch gefunden wurden.
4. Bonuspunkte und passende Produkte Produkte die man nur oder auch gegen Bonuspunkte kaufen kann. Das war etwas komplexer und man musste über Collector und Processor des Carts eingreifen. Heute würde ich wohl einiges ein wenig anders machen, aber nicht viel und auch nur minimale Änderungen.
5. Konfigurator Style #1 So kann man sich z.B. Geschenkkörbe oder PCs/Notebooks konfigurieren. In der Administration kann man sich ein Config-Preset anlegen und es verschiedenen Products zuordnen.
6. Konfigurator Style #2 Ein Produkt das z.B. Hackfleisch beinhaltet so konfigurieren in welchen Formen man das Hack gerne geliefert bekommen würde. Wurde als Teil von Punkt 7 entwickelt.
7. Paket verkauf Erst alle Pakete verkaufen und dann erst das Tier schlachten. Spart Lagerkosten und andere Aufwände. Hier habe ich gelernt warum Varianten noch immer umständlich sind und CustomFields zu syncen zwischen Varianten schlechter ist als eine eigene Entity dafür zu erstellen. Aber die Anforderungen waren zuerst so das CustomFields die einfacher Lösung waren.
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.
Gerade wenn man ein Produkt konfigurieren kann, ist es wichtig, dass CartItems nicht einfach aufaddiert werden, sondern jede Konfiguration als eigenes CartItem im Warenkorb abgelegt wird. Das CartItem von Shopware 6 hat auch ein stackable-Flag. Nun könnte man glauben, wenn man dieses auf false setzt, dass nicht das vorhandene CartItem geändert sondern ein neues angelegt wird, wenn erkannt wird, dass das vorhandene nicht stackable ist. Falsch! Man bekommt eine Exception.
Die Lösung ist zum Glück sehr einfach. Man muss selbst die Id des CartItems ändern um ein neues anzulegen. Also wenn man setStackable(false) setzt auch gleich die Id neu setzen. Oder man baut sich ein allgemeines Plugin, dass es macht.
BeforeLineItemAddedEvent:
if (!$event->getLineItem()->isStackable()) {
$event->getLineItem()->setId(Uuid::randomHex());
}
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.
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.
Was ich zur Plugin-Entwicklung mit Shopware 6 nach dem ersten Kennen lernen bei der Shopware AG am 1.7. zu sagen habe:
- Varianten sind nun Produkte mit Parent
- Ids in der Datenbank sind nun UUUIDs (ramsey/uuid) die binär in der DB gespeichert werden
- Ein Plugin definiert sich allein durch eine composer.json und den darin enthaltenen Typ shopware-platform-plugin (ersetzt die plugin.xml) und PSR-4 Namespace und Plugin-Class werden darin definiert
- die Metadaten zum Plugin sind in der composer.json unter extra - Changelog ist jetzte die CHANGELOG.md Datei und mit ISO-Lang Postfixes erhält man die verschiedenen Sprachen (soll wohl mal durch den Store ausgelesen werden können)
- ./bin/console plugin:create --create-config MySW6Plugin erstellt einen ein komplettes Plugin Skeleton mit Config-XML und passender composer.json
- src/Resources/views/ wird automatisch registriert (wenn man seine Views wo anders liegen hat kann man getViewPath überschreiben)
- in der Plugin-Config kann man card-Tags verwenden, um Blöcke zu bilden und alles besser strukturieren zu können
- man kann eigene Felder und View in die Plugin-Config einbringen, in dem man vor eine Vue-Component registriert hat und diese dort als <component name="MyComponent"/> angibt
- Plugin installieren: ./bin/console plugin:install --activate --clear-cache MySW6Plugin öfters ist ein refresh der Liste nötig und cache:clear sichert den liebgewonnen Workflow bei der Entwicklung mit Shopware :-)
- Es gibt kein Doctrine-ORM mehr sondern die eigene DAL die noch DBAL verwendet
- DAL ist so etwas wie ein halb fertiges ORM wo noch die Abstraktion wie Annotations fehlt und man deswegen die Beschreibung und Umwandlungen selbst per Hand vornehmen muss, so wie auch das Anlegen der DB-Tables
- Es gibt dort noch viel zu verbessern, aber das Grundgerüst funktioniert da schon sehr gut. Zu verbessern wäre z.B.: new IdField('id', 'id') auch als new IdField('id') schreiben zu können wenn DB-Column und Class-Field gleich bezeichnet sind
- die Field-Definition-Methode wird nur selten aufgerufen, deswegen kann man hier sonst auch eigene Lösungen wie CamelCase-Converter oder Annotion-Parser selbst integrieren
- Es gibt momentan immer 3 Klassen: Definition, Plain Old PHP Object und Collection (über @Method der eigenen Collection wird so etwas wie Generics versucht abzubilden... in Java wäre es nur List<MyEntity> list = ....)
- Es wir automatisch ein Repository für jede Entity erzeugt, das man mit Hilfe der service.xml injecten kann
- CLI, Subscriber, etc werden weiterhin über die service.xml verwaltet und implementieren nur andere Interfaces (sonst hat sich dort kaum was verändert)
- Es gibt keine Hooks mehr sondern nur noch Service-Decoration (wie die meisten wohl schon jetzt lieber verwendet haben) und eben die Events
- Es gibt mit der DAL ein Versions-Feld
- Änderungen an der Datenbank sollten über das Migration-System (Timestamp basiert) erledigt werden und nicht über die Lifecycle-Methoden des Plugins
- Es gibt Updates und destructive Update, die die Kompatibilität der DB zu älteren Plugin-Versionen zerstören. Beides kann getrennt ausgeführt werden (2 Phasen Update für Cluster-nodes)
- Die DAL unterstützt native Translated-Fields
- Extensions von Entities werden serialisiert in der DB gespeichert (ALTER TABLE, Extension, Serializer-Klasse, etc). Es gibt keine Attributes mehr.
- Gespeichert wird immer per Array (von Entity-Arrays) und es wird nicht das Object zum Speichern übergeben
- upsert (update or insert) ist da das neue merge - Der Cart hat nun LineItems, die wiederum Children haben können
- LineItems sind nun vollkommen unabhängig von Produkten, es gibt nur noch ein Type und Enrichment-Handler, die Namen und Preise für diesen Type an das Item ran dekorieren
- ... damit kann man verschiedene Bestell-Modi wie "kaufen" und "ausleihen" pro Produkt zusammen in einem Warenkorb abbilden
- Die API stellt automatisch generierte Swagger-Beschreibungen bereit
- Es gibt mehrere APIs so dass nicht nur Datenspflege bestrieben werden kann, sondern auch komplette Bestellprozesse damit abgebildet werden können (damit kann man Shopware als Backend-Lösung für In-App-Käufe oder POS-Systeme verwenden)
- Dadurch kann man Shopware auch headless verwenden (ohne Storefron und Admin, oder nur mit einem davon)
- ein Product aht ein SalesChannelProduct... man kann dort bestimmt auch eine Stock-Quantity hinzufügen und so einen Sales-Channel abhängigen Lagerbestand implementieren (Jeder POS auf einen Event oder in Disco könnte ein eigener Sales-Channel werden und dann kann man genau
monitoren wo Getränke oder Verbrauchsmaterial zu neige geht und reagieren bevor die Mitarbeiter überhaupt merkten, dass in 15min wohl die Strohhalme ausgehen werden)
Insgesamt macht Shopware 6 einen wirklich sauberen und durch dachten Eindruck. Nur die Migrations brauchen noch etwas und die DAL könnte gut einen weiteren Layer drum herum gebrauchen (ich wäre für Annotations an der Entity-Klasse)
Danach sollte auf localhost:8000 der Shop starten.. wenn eine MySQL Auth.-Method Fehlermeldung kommt hilft die Container zu beenden. In der Docker-Compose das DB-Image von dem MySQL-Image auf das MariaDB-Image zu ändern und noch mal die Container neu zustarten und ein wieder install durchführen.
Dann sollte alles laufen und man kann sich unter localhost:8000/admin mit admin - shopware anmelden.
Wenn man die lokalen psh.phar-Aufrufe durch die direkten Docker-Befehle ersetzt, sollte es auch unter Windows funktionieren. Innerhalb des Containers kann man natürlich wieder psh.phar verwenden.
Update: manchaml kann auch helfen, dieses vorher einmal auszuführen, bevor man den Container startet und dort install aufruft:
php composer update --ignore-platform-reqs
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?