Blog: Latest Entries (15):


Agentur-Probleme: Es läuft nicht mehr... AI!

bbcode-image


Viele Agenturen haben momentan ein Problem: So wie es die letzten zwei bis drei Jahre lief, läuft es einfach nicht mehr. Kunden bleiben aus. Bestehende setzen weniger Geld um oder springen komplett ab. Was übrig bleibt, können wenige Mitarbeitende bewältigen – während der Rest darauf wartet, dass neue Kunden kommen. Doch das bindet das Sales-Team und erfordert hohe Investitionen, um überhaupt überschaubare Projekte zu gewinnen.

Aber wer oder was ist schuld an der Situation?

Wenn man den zeitlichen Verlauf betrachtet, fällt auf, dass mit dem Beginn der Probleme auch etwas anderes gewachsen ist: Künstliche Intelligenz. AI ermöglicht es Kunden, viele Dinge selbst zu erledigen. Einfach die AI fragen – und schon bekommt man eine brauchbare Antwort. Kunden sind dadurch mündiger geworden und stellen mehr in Frage. Das macht es schwerer, ihnen etwas zu verkaufen. Klar, die wirtschaftliche Lage spielt auch eine Rolle, aber AI scheint der Haupttreiber zu sein.

So einfach ist es dann aber doch nicht.

AI ist großartig, gerade im generativen Bereich. Aber: Kein Kunde ersetzt seine Agentur durch AI und generiert einfach selbst Code. Vielmehr wird AI in Produkte integriert und macht diese zugänglicher und einfacher in der Bedienung. Gleichzeitig wird es aber auch komplexer, diese Systeme zu erweitern – denn die AI muss diese Anpassungen auch „verstehen“. Ja, AI hat sicherlich Entwicklungen beschleunigt, aber viele Probleme gab es schon vorher – und sie wären auch ohne AI irgendwann sichtbar geworden.

Corona ist nun endgültig vorbei. Damals konnte man sich auf die Straße stellen, einmal laut rufen: „Ich bin Programmierer“ – und 20 Minuten später hatte man fünf neue Kunden. Webshops und Homepages waren plötzlich kein Hype mehr wie zu Dotcom-Zeiten, sondern überlebensnotwendig, um mit Kunden in Kontakt zu bleiben und weiterhin verkaufen zu können. Wer keinen Webshop hatte, war raus. Und wer einen hatte, wollte plötzlich wieder vorne mitspielen. Es brauchte Beratung. Sales sprach mit Kunden, als wären sie "dumm". Man konnte ihnen viel verkaufen, denn sie hatten weder Ahnung noch Erfahrung. Lieber zu viel investieren, als unterzugehen.

Diese Zeit ist vorbei. Jeder, der einen Webshop brauchte, hat längst einen. Der Rest hat überlebt – oder sich bestätigt gefühlt, dass es auch ohne geht. Diese kleinen, unerfahrenen Kunden in Massen? Verschwunden. Manche Sales-Abteilungen haben sich nicht angepasst. Heute steht dir ein potenzieller Kunde gegenüber, der Geld für ein Upgrade seines Shops hat – nicht, weil er bisher etwas falsch gemacht hat, sondern weil er vieles richtig gemacht hat. Wer den behandelt wie einen Anfänger, verliert ihn sofort. Warum sollte sich jemand von einem erklären lassen, wie das eigene – erfolgreiche – Geschäft funktioniert?

#1: Die Zeit, in der man Kunden wie Idioten behandeln konnte, ist vorbei!

Auch der Mangel an Entwicklern ist vorbei. Man muss nicht mehr in Osteuropa suchen oder in Asien einkaufen. Entwickler gibt es wieder überall: gute, mittelmäßige und schlechte – also genau wie es sein sollte.

Kein Kunde baut sich mit AI selbst ein Shopware-Plugin. Tools wie Cursor und Junie ermöglichen das theoretisch erst seit wenigen Monaten. Kunden sind auch nicht mehr dankbar, dass eine Agentur überhaupt Zeit für sie hat. Wenn eine Agentur nicht liefert – in Qualität oder Quantität – sucht man sich eine andere oder stellt eben direkt Entwickler ein. Gerade für Firmen mit IT-Abteilung, SAP-Beratern und ERP-Know-how ist es kein weiter Schritt, auch einen Shopware-Entwickler ins Team zu holen. Der kann dann auch direkt an der Middleware mitarbeiten – ohne großen Overhead.

Ich kenne zwei Fälle, in denen der Ansprechpartner beim Kunden selbst aus der IT kam – und entschied: „Das machen wir jetzt intern.“ Eine Vollzeitkraft ist produktiver als eine Agentur, weil kaum Overhead entsteht. Die Codequalität? Ausreichend.

Und dann höre ich von Agenturen:
„Der Kunde hat zwei Entwickler eingestellt, um unseren einen zu ersetzen… Die werden schon sehen, dass das nichts wird!“
Falsch! Der Kunde hat genug Arbeit für zwei Leute – und die Agentur konnte schlicht nicht genug liefern.

#2: Angestellte Entwickler sind eine echte Alternative zu Agenturen!

Entwickler haben in den letzten Jahren sehr gute Arbeit geleistet. Ich habe mal zu einem Arbeitgeber gesagt:
„Ich bin gut, wenn ich mich selbst überflüssig gemacht habe – wenn die Software so einfach ist, dass ihr mich nicht mehr braucht.“

Und genau das haben viele Entwickler geschafft: intuitive Oberflächen, einfache Bedienung, modular aufgebaut, wiederverwendbar. Kunden sind nicht so individuell, wie sie glauben. Zwei, drei Konfigurations-Flags – und ein Plugin funktioniert für mehrere. Viele Shops kann man heute zu 90 % aus bestehenden Plugins zusammenstellen – ohne eine einzige Codezeile.

Dieses "Zusammenklicken" lässt sich super in einfache Oberflächen gießen. Dazu automatisiertes Deployment – und zack, hat man einen Cloud-Service. SAP behauptet, es gäbe keine Standardschnittstellen – aber am Ende bauen sie doch bei jedem Kunden das Gleiche. 90 % gibt es schon, 10 % sind individuelle Anpassungen – theoretisch auslagerbar in eine JSON-Datei.

Webshops funktionieren seit 20 Jahren nach dem gleichen Prinzip: Listing, Produktseite, Warenkorb, Checkout, Account. Wer so lange entwickelt hat, hat alles schon mal gebaut. Der Punkt ist erreicht: Kunden finden fast alles, was sie brauchen, bereits fertig.

#3: Es gibt fast alles schon – es muss nicht mehr neu entwickelt werden!

Natürlich zahlt ein Kunde heute nicht denselben Preis, wenn jemand einfach einen Shop zusammenklickt, wie damals, als alles individuell programmiert werden musste. Für echtes Custom-Development zahlt man noch immer gut – aber es ist seltener nötig.

Ein weiteres Thema ist der Overhead. Kunden wollen für Impact zahlen – nicht für Projektmanagement. Viele Kunden wären zufrieden, wenn man den Projektmanager streicht. Spart Geld, spart Zeit.

Natürlich gibt es sehr gute Projektmanager – die wissen, was der Kunde will, die auch mal einen Shopware-Flow anpassen, damit der Entwickler sich auf Code konzentrieren kann. Aber genauso gibt es viele, die teure Projekte verkaufen und managen wollen – aber nicht mal den Admin-Bereich bedienen können. Kunden, die täglich mit dem System arbeiten, merken schnell, wenn sie es besser verstehen als die Agentur.

#4: Kenne dein Produkt – und sei ein echter Experte!

Heute haben wir Kunden, die Profis sind. Und wir haben Tools, die fast alles können. Jedes Ticket ist schnell erledigt. Weniger Aufwand, weniger Stunden – weniger Geld. Gleichzeitig wächst der Druck: Tickets sollen schneller fertig werden. Und wenn du gut bist und brauchst statt 8 nur 4 Stunden, super – nur bekommst du dann auch weniger bezahlt.

Zeit gegen Geld ist ein veraltetes Modell. Kunden bezahlen gerne für Impact – und oft auch dafür, es schnell zu bekommen. Warum machen wir Aufwandsschätzungen zum Problem des Kunden?

Der Kunde will ein Ergebnis – das bekommt einen Preis. Ob das nun schon fertig rumlag oder neu entwickelt wird, ist egal. Wenn der Entwickler freitags mittags geht, weil er fertig ist: gut so! Die Leistung ist erbracht, das Geld verdient.

#5: Weg vom Zeitmodell – hin zu Festpreisen für Ergebnisse. Impact zählt, nicht Aufwand.

AI beschleunigt die Entwicklung enorm – vor allem bei neuen Ideen, Prototypen und Design. Dass etwas schneller geht, ist ein Vorteil, den man verkaufen sollte – kein Nachteil!

Fazit: AI ist weder Schuld noch allein die Lösung. Die eigentlichen Probleme reichen tiefer und sind älter als brauchbare AI. Sie kann vieles verbessern – aber nur, wenn das Umfeld mitzieht. „Das haben wir schon immer so gemacht“ ist ein schwaches Argument, wenn es darum geht, warum plötzlich nichts mehr funktioniert.

Agentur-Probleme: Qualität kann nur aus Komplexität entstehen

bbcode-image


Agenturen existieren in einer eigenen sehr speziellen Bubble, in der Gesetze gelten, die sich außerhalb dieser Bubble seit 20 Jahren oft schon überholt haben. Eines dieser Gesetze ist, dass Qualität nur aus Komplexität entstehen kann. Je komplexer eine Lösung ist, desto höher ist deren Qualität. Wenn es darum geht eine vorhandene alte Lösung durch eine schlankere und einfacher Lösung zu ersetzen, kommt neben dem "das haben wir schon immer so gemacht" auch eine reine Beurteilung an Anzahl der Zeilen, Anzahl der Dateien und ob Copy&Paste als Design-Pattern dort zum Einsatz kam. Also wenn die neue Lösung 40 Zeilen lang ist, in eine Datei passt und extends-Funktionalität nutzt ist die Qualität geringer als die alte Lösung aus über 200 Zeilen, 6 Dateien und wo alles per Copy&Paste erstellt wurde und mindestens 2 Dateien per Hand synchron gehalten werden beim verwendeten Docker-Image. Wenn man also noch eine Risikobewertung mit rein nimmt verhält sich Qualität also immer entgegen des Risikos. Das Onboarding eines neuen Developers dauert über eine Stunde und nicht 15min? Das nennt sich Qualität, weil jemand sehr viel mehr dran gearbeitet hat als andere neuen Lösung (die Zeilen und Dateien entstehen ja nicht ohne Arbeit). Viel Arbeit = Viel Qualität.
Das stammt auch aus der Art, wie Abgerechnet wird. Zeit gegen Geld. Somit gilt also: Viel Arbeit = Viel Geld = Viel Qualität.

Interessanter Weise gilt das selbe für die Auswahl bei von der AI generierten Lösungen. Je komplexer die präsentierte Lösung ist, desto besser und als qualitativ hochwertiger wird sie betrachtet, weil die AI darauf ja mehr Rechenleistung verbraten hat.

Shopware 6: HTML Ajax und JavaScript Plugins

Wenn man z.B. ein Product-Box per Ajax von einem Controller nach lädt, wird der "In den Warenkorb" Button nur als normale Form funktionieren und nicht den Off-Canvas Warenkorb nutzen per JavaScript. Weil bei dem initialisieren der Plugins der HTML-Code natürlich noch nicht da war. Das kann man aber einfach nachholen.


document.getElementById('productRecommendation').innerHTML = result.productRecommendation;
window.PluginManager.initializePlugins(document.getElementById('productRecommendation'));


Damit wird der HTML-Code nach Plugin-Markern durch sucht und die Plugins an den Elementen neu initialisiert.

Grav CMS Honeypot

Ein Honeypot in das Kontaktformular einbauen ist recht einfach. Man kann alles am Feld hinterlegen:


form:
name: contact-form
fields:
- name: honeypot
label: "Leave this field empty"
type: text
attributes:
style: "display: none; position: absolute; left: -9999px;"
validate:
required: false # Sicherstellen, dass das Feld nicht ausgefüllt werden muss
message: "Bot detected!" # Fehlermeldung, falls das Feld ausgefüllt wird
rule: empty # Das Feld muss leer bleiben
buttons:
- type: submit
value: Send


Man kann auch eine Rule in user/plugins/form/form.yaml[anlegen] und die rule anstelle von "empty" angeben. Wenn man mehrere Formulare auf der Seite hat, ist das sicher die bessere Methode.


rules:
honeypot:
message: "Bot detected!"
validate: empty

Ollame lokal mit Docker

Nachdem ich feststellen musste, dass ChatGPT nur per API nutzbar ist, wenn man dafür bezahlt und sowie es ja problematisch sein kann Daten wie Telefonnummern oder Adressen dahin zu schicken, habe ich mich nach Alternativen umgesehen. Google Gemini kann man ohne Probleme per API nutzen, auch wenn man nicht bezahlt, aber das Datenschutzproblem bleibt. Also wäre eine lokale Lösung sowie so viel besser.

So kam ich zu Ollama. Das kann man ohne Probleme per Docker starten. Ohne GPU-Beschleunigung war es aber doch recht langsam. Zum Glück installiert der Nvidia-Treiber alles mit, um auch unter Windows GPU-Beschleunigung in Docker-Containern nutzen zu können.

Selbst mit einer GTX 970 ist das llama3 Model recht gut nutzbar. Test mit einem separaten Linux-System und Telsa P4 folgen später, wenn die Karte da ist.

Docker-Container starten:

docker run -d -v ollama:/root/.ollama -p 11434:11434 --gpus=all --name ollama_2 ollama/ollama


Ollama CLI Eingabe starten:

docker exec -it ollama_2 ollama run llama3


Abfrage via API:

POST http://localhost:11434/api/generate
Content-Type: application/json

{
"model": "llama3",
"prompt": "write a short poem about a 1HE server.",
"stream": false
}

Rotations-Prinzip

Menschen kommen, scheitern und gehen. Der nächste wird besser sein. Es ist eine endlose Suche nach dem einen der diesen ewigen Kreislauf durchbricht. Wobei Suche der falsche Begriff ist. Es ist mehr ein warten. Ein passives Warten. Aber ganz sicher kommt irgendwann der eine oder die eine, die alles mitbringt was man haben will. Jemand der nicht auf die Hilfe der anderen angewiesen ist, die keine Zeit für sowas haben und somit das Scheitern für die meisten schon vorprogrammiert ist. Nein! Ein Mensch wird kommen und genau die Person sein, die man schon immer haben wollte.

Die anderen, die durch die Rotation gegangen sind, ziehen weiter. Zur nächsten Firma und durch den nächsten Rotationszyklus bis auch die Firma sie wieder ausspuckt. Diese Menschen sind das Rauschen, dass die gesuchten verdeckt, ihre Aufgabe ist es ausgesiebt zu werden oder bis sie sich ändern. Aber wenn sie sich ändern könnten, wären sie nicht da wo sie sind. Nicht gefangen in einen immer wieder kehrenden Zyklus von Kommen, Scheitern und Gehen gefangen.

Und somit.. mal gucken ob der nächste Projektmanager, die nächste Projektmanagerin ein weiter Zyklus in der Rotation ist oder diese Rotation durchbrechen kann.




Natürlich könnte man mehr Zeit in Auswahl, Förderung und Aufbau eigener Leute stecken.. aber das bezahlt einen eben niemand.

Shopwar 6: DatePicker außerhalb einer Form

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.

Hier eine Quick&Dirty Lösung für das Problem:


const observer = new MutationObserver((mutationlist, observer) => {
mutationlist.forEach(mutation => {
if(mutation.type === 'childList') {
const picker = document.querySelector('.hp_shipping_date.form-control');
if(picker) {
picker.setAttribute('form', 'confirmOrderForm');
observer.disconnect();
}
}
});
});

observer.observe(document.querySelector('.hp-shipping-date-container'), {childList: true, subtree: true});

Shopware 6: Custom Storefront Endpoint in Apps

Da ich lange herum probieren und die Dokumentation interpretieren musste hier einmal schnell wie das Mapping zwischen Twig-Fule und URL aussieht.

URL

http:localhost/storefront/script/testscript?value=test


Path

Resources/scripts/storefront-testscript/index.twig


Beispiel

{% set value = hook.query['value'] %}

{% set response = services.response.json({'value': value}) %}
{% do response.cache.disable() %}
{% do hook.setResponse(response) %}


liefert einfach das was man übergibt als JSON zurück (ohne Cache).

Shopware 6: Immer Ärger mit den JWT-Dateien

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.

bbcode-image


Damit klappte auch ein Deployment auf platform.sh dann ohne Probleme.

Docker und AWS CLI: secretsmanager nutzen

Um z.B. in einer Gitlab Pipeline den AWS secretsmanager zu nutzen, um Passwörter oder Token abzufragen muss man erstmal den CLI Client installieren und konfigurieren. Das geht am Besten wenn man die Dateien direkt schreibt.


apt-get install -y python3 python3-pip awscli --no-install-recommends
mkdir ~/.aws
echo -e "[default]\naws_access_key_id = ${AWS_KEY}\naws_secret_access_key = ${AWS_SECRECT}" > ~/.aws/credentials
echo -e "[default]\nregion = eu-central-1\noutput = json" > ~/.aws/config
echo $(aws secretsmanager get-secret-value --secret-id my_secret_token --query SecretString --output text)

Shopware 6: Order Recalculation nach Änderung

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)


$versionId = $this->orderRepository->createVersion($orderId, $this->getContext());
$this->orderLineItemRepository->delete([['id' => $id]], $this->getContext()->createWithVersionId($versionId));
$this->recalculationService->recalculateOrder($orderId, $this->getContext()->createWithVersionId($versionId));
$this->orderRepository->merge($versionId, $this->getContext());

Shopware 6 DB-Dump und sed

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.

Shopware: Update von 6.4 auf 6.5

Shopware: Update von 6.4 auf 6.5

An sich geht es ganz einfach. In der Administration geht man auf Update, bestätigt alles, die Plugins werden deaktiviert (vielleicht auch das Language-Pack) und dann startet der Installer und .. läuft in einen Fehler und dann läuft garnichts mehr. Scheint jeden Falls öfters mal so zu passieren.

Ich hab mir ein Script gebastelt mit dem man sich eine Kopie des Shops auf die neue Version updaten kann und dann später auf diese Kopie switchen kann.

Man muss nicht alle Plugins deaktiveren, aber einfacher ist es. Also eine Kopie (Dateien und Datenbank) anlegen und da alles Plugins deaktiveren. Per SQL-Statement geht es recht schnell und einfach.

Der original Shop liegt in shop/ und der neue in shop65/. Die .env der Kopie (wegen DATABASE_URL) wird in shop_shared/ abgelegt und um LOCK_DSN="flock" und SHOPWARE_SKIP_WEBINSTALLER=1 ergänzen.

Dann das Script laufen lassen.. oder besser Zeile für Zeile per Hand ausführen.


rm -rf ~/public_html/shop65/{*,.*}
composer create-project shopware/production ~/public_html/shop65 "v6.5.0.0" --no-interaction

rsync -avh ~/public_html/shop/files/ ~/public_html/shop65/files/
rsync -avh ~/public_html/shop/public/bundles/ ~/public_html/shop65/public/bundles/
rsync -avh ~/public_html/shop/public/css/ ~/public_html/shop65/public/css/
rsync -avh ~/public_html/shop/public/fonts/ ~/public_html/shop65/public/fonts/
rsync -avh ~/public_html/shop/public/js/ ~/public_html/shop65/public/js/
rsync -avh ~/public_html/shop/public/media/ ~/public_html/shop65/public/media/
rsync -avh ~/public_html/shop/public/sitemap/ ~/public_html/shop65/public/sitemap/
rsync -avh ~/public_html/shop/public/theme/ ~/public_html/shop65/public/theme/
rsync -avh ~/public_html/shop/public/thumbnail/ ~/public_html/shop65/public/thumbnail/
rsync -avh ~/public_html/shop/public/.htaccess ~/public_html/shop65/public/.htaccess
rsync -avh ~/public_html/shop/var/log/ ~/public_html/shop65/var/log/
rsync -avh ~/public_html/shop/config/jwt/ ~/public_html/shop65/config/jwt
rsync -avh ~/public_html/shop/custom/ ~/public_html/shop65/custom/

rsync -avh ~/public_html/shop_shared/.env ~/public_html/shop65/.env

cd ~/public_html/shop65 && composer update
cd ~/public_html/shop65 && bin/console system:update:finish
cd ~/public_html/shop65/vendor/shopware/administration/Resources/app/administration && npm install && cd ~/public_html/shop65


Ziel ist es wieder in die Administration zu kommen und dort alle Plugins zu aktualisieren. Wenn das gelungen ist, dann alle nach und nach wieder aktivieren und wieder die Themes in den SalesChannels einrichten.

Wenn Fehler auftreten immer mal wieder bin/console aufrufen, weil dann die Exceptions meistens ganz gut dargestellt wird.

So kommt man auch sehr gut ohne den Installer zu seinem aktuellen Shopware und räumt auch direkt noch etwas auf.

Shopware 6 Filesysteme nutzen

Arbeiten mit Dateien ist in Shopware 6 an sich recht einfach, gerade seit die Media-Entity und deren Thumbnails ein Path-Feld haben, in dem der relative Pfad direkt angegeben werden kann. Wenn man den hat muss man nur noch den absoluten Pfad bauen. Wenn man z.B. in das public/ Verzeichnis will um dort etwas zu hinterlegen oder ein Media-File zu lesen kann man sich die Umgebungsvariable mit Symfony Project Root per Dependency Injection direkt in den Constructor seines Services geben und von da aus dahin navigieren. Von der aktuellen PHP-Datei aus ist es nicht so toll, da man nicht immer weiß wo das Plugin sich befindet. Z.B: kann es im custom-Folder oder irgendwo in vendor/ sich befinden.


<argument>%kernel.project_dir%</argument>


Und was ist wenn man die Dateien in einem Cluster-Betrieb über ein S3-Bucket an die Cluster-Nodes verteilt? Shopware 6.5 hat zum Glück nicht nur Flysystem dabei, sondern nutzt es auch richtig. Man kann sich direkt per Dependency Injection das privater oder das öffentliche Dateisystem geben lassen und dann ist es egal ob es auf einem FTP, in einem S3 Bucket oder im lokalen Dateisystem liegt.


namespace HPr\FSTest\Services;

use League\Flysystem\FilesystemOperator;
use Shopware\Core\Content\Media\MediaEntity;

class MediaTest {
public function __construct(private FilesystemOperator $filesystem){}

/**
* @throws FilesystemException
*/
public function md5Media(MediaEntity $media): string {
return md5($this->filesystem->read($media->getPath()));
}
}


Um nun das passende Dateisystem zu bekommen ist nicht viel nötig.


<service id="HPr\FSTest\Services\MediaTest">
<argument type="service" id="shopware.filesystem.public"/>
</service>


Public ist das öffentliche Verzeichnis, das man für Product-Bilder, CMS-Media oder auch andere Downloads nutzen kann, die jeder sehen darf. Dann gibt es auch das private Dateisystem, wo man alles wie Rechnungen und Dinge ablegt, die nicht jeder sahen darf und wo man den Zugriff am besten durch einen eigenen Controller kapselt. Die MediaEntity hat einen private-Flag, um anzugeben in welchem Dateisystem man die Datei findet.

Das Dateisystem selbst kann man in einer YAML-Datei in config/packages/ definieren. Wie man da z.B. seine Dateien in einem MinIO S3 Bucket ablegen kann, habe ich in einem Post vorher schon erklärt.

Shopware 6 und MinIO

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.

docker-compose.yml:

version: '3'

services:
shopware:
....


minio:
image: minio/minio:latest
volumes:
- minio_data1:/data
ports:
- "9000:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minioKey123
command: server /data
networks:
- shopware-web

createbuckets:
image: minio/mc
depends_on:
- minio
entrypoint: >
/bin/sh -c "
/usr/bin/mc config host add myminio http://minio:9000 minio minioKey123;
/usr/bin/mc rm -r --force myminio/public;
/usr/bin/mc mb myminio/public;
/usr/bin/mc anonymous set public myminio/public;
exit 0;
"
networks:
- shopware-web

volumes:
minio_data1:
driver: local

networks:
shopware-web:
external: false


Nun kann man für einzelne der getrennten Dateisysteme das Bucket als Storage hinterlegen (config/packages/shopware.yaml):

shopware:
filesystem:
public:
type: "amazon-s3"
url: 'http://s3.local:9000/public'
config:
bucket: "public"
endpoint: "http://minio:9000"
use_path_style_endpoint: true
region: 'local'
credentials:
key: minio
secret: minioKey123
options:
visibility: "public"
theme:
type: "amazon-s3"
url: 'http://s3.local:9000/public'
config:
bucket: "public"
endpoint: "http://minio:9000"
use_path_style_endpoint: true
region: 'local'
credentials:
key: minio
secret: minioKey123
options:
visibility: "public"
cdn:
strategy: md5


Man kann auch getrennte Buckets nutzen, aber das macht an sich wenig Sinn.

Nun Cache leeren, Themes compilieren und die Medien einmal neu importieren oder auf in das Bucket kopieren.

Das kann man gut mit dem lokalen Client machen:

wget http://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin
mc config host add myminio http://localhost:9000 minio minioKey123


Die Dokumentation ist gewöhnungsbedürftig und ChatGPT liefert auch gerne veraltete Anleitungen.. aber mit etwas suchen klappt es dann doch recht gut.

Older posts:

Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?