In diesem kurzen Tutorial zeige ich euch, wie ihr den Firebird-Treiber für PHP in einem Docker-Container problemlos installieren könnt.
Voraussetzungen
* Docker-Umgebung
* PHP-Container
Installationsschritte
1. Systemaktualisierung
Zuerst aktualisieren wir das Paketverzeichnis, um sicherzustellen, dass wir die neuesten Versionen erhalten:
apt-get update
2. Firebird und Abhängigkeiten installieren
Installieren wir nun die notwendigen Pakete, einschließlich des Firebird-Entwicklungspakets und weiterer Hilfsmittel:
apt-get install -y firebird-dev libib-util apache2-utils
3. PHP PDO-Firebird-Erweiterung aktivieren
Abschließenden kompilieren und installieren wir die PDO-Firebird-Erweiterung für PHP:
docker-php-ext-install pdo_firebird
Ergebnis
Diese einfache Befehlsfolge ermöglicht es euch, den Firebird-Treiber erfolgreich in eurem Docker-PHP-Container zu integrieren. Die Installation wurde erfolgreich mit PHP-Version 2.5.9 getestet und funktioniert ohne Probleme.
Fazit
Mit nur wenigen Zeilen Code habt ihr volle Unterstützung für Firebird-Datenbanken in eurer PHP-Anwendung. Der Prozess ist unkompliziert und schnell erledigt – perfekt für Entwicklungs- und Produktionsumgebungen!
erster Test mit eternalai.org als Unterstützung beim Schreiben
Auch wenn Shopware 6.6 mittlerweile nicht mehr die aktuellste Version ist und ich derzeit nicht mehr überwiegend mit Shopware arbeite, habe ich meine GitLab-Pipeline für ein möglichst schlankes und robustes Shopware-6.6-Deployment so aufbereitet, dass ich sie hier veröffentlichen kann.
Die Pipeline lässt sich problemlos auch auf andere Shopware-6-Versionen anpassen (eine Variante für 6.4 lief bereits produktiv, bis sie per AI in GitHub Actions migriert wurde).
Für die lokale Entwicklung genügt ein sehr schlankes Projekt:
custom
apps
plugins
static-plugins
env_config
prod
stage
.env.local
auth.json
composer.json
composer.lock
docker-compose.yml
Die docker-compose.yml kann eine einfache Dockware-Variante sein, in der alle relevanten Ordner als Volumes gemountet werden, sodass man direkt darin entwickeln kann.
In env_config liegt alles, was für die jeweiligen Umgebungen spezifisch ist. Dateien aus diesem Verzeichnis werden übernommen und ergänzen oder überschreiben vorhandene Konfigurationen. Typischerweise findet man hier eine passende .env.local sowie verschiedene Konfigurationsdateien für den config-Ordner.
Die Einrichtung auf dem Server ist unkompliziert: Im gewünschten Verzeichnis wird folgendes Skript ausgeführt:
mkdir shopware
cd shopware
mkdir config
mkdir releases
mkdir shared
cd shared
mkdir media
mkdir thumbnail
mkdir bundles
mkdir files
mkdir log
mkdir sitemap
cd config
mkdir jwt
cd ../..
cd releases
mkdir _init
cd _init
mkdir public
cd public
echo "<html><body>INIT</body></html>" > index.php
cd ../../..
ln -sfn ./releases/_init/public current
Anschließend kann der VHost auf current/public/ zeigen – die INIT-Seite sollte dann bereits erreichbar sein.
Die eigentliche Pipeline besteht aus zwei Dateien: der .gitlab-ci und der rebuild.sh.
In der .gitlab-ci.yml werden sämtliche Dateien zusammengeführt, ohne dass Shopware-Code ausgeführt wird. Alles, was Shopware und eine Datenbank erfordert, wird in der rebuild.sh erledigt.
In der .gitlab-ci.yml sind folgende Variablen erforderlich:
* server
* server_folder
* ssh_user
* target_env
* SSH_PRIVATE_KEY
Falls zwischen Umgebungen unterschieden werden muss (z. B. unterschiedliche Server-Adressen für Stage und Prod), lassen sich hier Environments einsetzen. Die Bezeichnungen müssen exakt mit den Verzeichnisnamen in env_config übereinstimmen.
.gitlab-ci.yml
stages:
- deploy
.default_deploy:
image: kroniak/ssh-client:3.21
stage: deploy
script:
- echo "init start"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- echo "init finished"
- apk add --no-cache zip
- zip -rq custom_$CI_PIPELINE_IID.zip custom
- cd env_config/$target_env/ && zip -rq ../../env_config_$CI_PIPELINE_IID.zip . -x "../*" && cd ../..
- ssh ${ssh_user}@${server} "cd ${server_folder} && composer create-project shopware/production ./releases/$CI_BUILD_TOKEN "v6.6.10.3" --no-interaction --ignore-platform-reqs"
- scp custom_$CI_PIPELINE_IID.zip ${ssh_user}@${server}:${server_folder}
- scp env_config_$CI_PIPELINE_IID.zip ${ssh_user}@${server}:${server_folder}
- scp composer.json ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/composer.json
- scp composer.lock ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/composer.lock
- scp auth.json ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/auth.json
- scp rebuild.sh ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/rebuild.sh
- ssh ${ssh_user}@${server} "cd ${server_folder} && unzip -oq custom_$CI_PIPELINE_IID.zip -d ./releases/$CI_PIPELINE_IID/"
- ssh ${ssh_user}@${server} "cd ${server_folder} && unzip -oq env_config_$CI_PIPELINE_IID.zip -d ./releases/$CI_PIPELINE_IID/"
- ssh ${ssh_user}@${server} "cd ${server_folder} && rm custom_$CI_PIPELINE_IID.zip"
- ssh ${ssh_user}@${server} "cd ${server_folder} && rm env_config_$CI_PIPELINE_IID.zip"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf media && ln -sfn ../../../shared/media media"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf thumbnail && ln -sfn ../../../shared/thumbnail thumbnail"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf sitemap && ln -sfn ../../../shared/sitemap sitemap"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf bundles && ln -sfn ../../../shared/bundles bundles"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/var && rm -rf log && ln -sfn ../../../shared/log log"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/ && rm -rf files && ln -sfn ../../shared/files files"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/ && ./rebuild.sh"
- ssh ${ssh_user}@${server} "cd ${server_folder} && ln -sfn ./releases/$CI_PIPELINE_IID current"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd releases && ls -dt */ | tail -n +4 | xargs rm -rf && cd .."
tags:
- runner-docker
deploy_stage:
extends: .default_deploy
environment: stage
only:
- /^staging_(.+)$/
deploy_prod:
extends: .default_deploy
environment: prod
only:
- /^prod_(.+)$/
Das Skript rebuild.sh kann projektspezifisch angepasst werden, z. B. wenn zusätzliche Node.js-Versionen oder Build-Schritte erforderlich sind.
rebuild.sh
bin/console system:update:prepare
composer composer install
bin/console system:update:finish
bin/console cache:clear
bin/console database:migrate --all
bin/console cache:clear
npm install --prefix ./vendor/shopware/administration/Resources/app/administration
bin/build-administration.sh
bin/build-storefront.sh
bin/console sw:theme:compile
bin/console cache:clear
bin/console cache:warmup
bin/console http:cache:warm:up
--
Zum Schluss noch ein Beispiel für eine docker-compose.yml, die sich bei Bedarf leicht anpassen lässt:
version: "3"
services:
# shopware_full:
# build: ./infrastructure
# ports:
# - "80:80"
# - "443:443"
# - "2222:22"
# volumes:
# - ./infrastructure/my_vhost.conf:/etc/apache2/sites-enabled/000-default.conf:rw
# - ./shopware:/var/www/html
# networks:
# - web
shopware:
image: dockware/dev:6.6.10.3
ports:
- "80:80"
- "443:443"
- "2222:22"
volumes:
- "./custom/apps:/var/www/html/custom/apps"
- "./custom/plugins:/var/www/html/custom/plugins"
- "./custom/static-plugins:/var/www/html/custom/static-plugins"
- "./vendor:/var/www/html/vendor"
- "./composer.json:/var/www/html/composer.json"
- "./composer.lock:/var/www/html/composer.lock"
- "./env_config/dev/.env:/var/www/html/.env"
- "./env_config/dev/.env.local:/var/www/html/.env.local"
- "lang_mysql:/var/lib/mysql"
- "./docker/volumes/media:/var/www/html/public/media"
- "./docker/volumes/theme:/var/www/html/public/theme"
- "./docker/volumes/bundles:/var/www/html/public/bundles"
- "./config/packages/cache.yaml:/var/www/html/config/packages/cache.yaml"
networks:
- web
environment:
# default = 0, recommended to be OFF for frontend devs
- XDEBUG_ENABLED=0
- XDEBUG_REMOTE_HOST=172.17.0.1
# default = latest PHP, optional = specific version
- PHP_VERSION=8.2
- NODE_VERSION=20
#config host:mail, port:1025, user:demo, password:demo
mail:
image: mailhog/mailhog
networks:
- web
ports:
- 8025:8025
# use init or bin/console system:install --force to init database
shopware_mysql:
image: bitnami/mysql:8.4.5
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: shopware
MYSQL_USER: app
MYSQL_PASSWORD: app
networks:
- web
volumes:
- ./docker/dumps/:/docker-entrypoint-initdb.d
- mysql_data:/var/lib/mysql
# shopware_mysql:
# image: bitnami/mariadb:10.6.20
# environment:
# MARIADB_ROOT_PASSWORD: root
# MARIADB_DATABASE: shopware
# MARIADB_USER: app
# MARIADB_PASSWORD: app
# networks:
# - web
# volumes:
# - ./infrastructure/dumps/:/docker-entrypoint-startdb.d
# - mysql_data:/bitnami/mariadb
shopware_redis:
image: redis:7
networks:
- web
shopware_elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.4
networks:
- web
environment:
- discovery.type=single-node
- xpack.security.enabled=false
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
volumes:
lang_mysql:
mysql_data:
elasticsearch_data:
networks:
web:
external: false
Falls man unter Ubuntu 24.04 LTS diese Fehlermeldung erhält
(libaio.so.1: cannot open shared object file: No such file or directory)
hilft das hier
sudo apt install libaio1t64
sudo ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.
So ein Oracle-DB Client ist erstaunlich umständlich zu installieren.

N8N auf einem eigenen Server einrichten inkl. Traefik, damit man auch andere Dinge noch auf dem Server laufen lassen kann.
networks:
netintern:
external: false
web:
external: false
services:
traefik:
image: traefik:v3.4
container_name: traefik
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "250m"
max-file: "1"
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=myemail+n8n@gmail.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./le:/letsencrypt
networks:
- web
- netintern
n8n:
image: docker.n8n.io/n8nio/n8n
container_name: n8n
logging:
driver: "json-file"
options:
max-size: "250m"
max-file: "1"
environment:
- N8N_HOST=n8n.mydomain.de
- N8N_PORT=443
- N8N_PROTOCOL=https
volumes:
- n8n_data:/home/node/.n8n
restart: unless-stopped
networks:
- netintern
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`n8n.mydomain.de`)"
- "traefik.http.routers.n8n.entrypoints=websecure"
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
- "traefik.http.services.n8n.loadbalancer.server.port=443"
- "traefik.http.routers.n8n-http.rule=Host(`n8n.mydomain.de`)"
- "traefik.http.routers.n8n-http.entrypoints=web"
- "traefik.http.routers.n8n-http.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
volumes:
n8n_data:
traefik_data:
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.
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
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
}
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});
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).
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)
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());
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
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.
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.