Blog: Latest Entries (15):


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.

Shopware 6: In Dockware Imagick installieren

Manchmal braucht einfach Imagick. Z. B. wenn man eine Bildvorschau einer PDF erzeugen will oder einfach mehr Power bei der Bildbearbeitung in PHP oder in Scripten braucht.

Während die Installation die meisten Anleitungen für Docker und Imagick mit den default PHP Docker-Image super funktionieren ist es bei Dockware anders, weil es eine volle Ubuntu-Umgebung mitbringt.

Zu beachten ist, dass man für alle PHP-Versionen die Erweiterung installieren muss.


FROM dockware/dev:6.5.8.6

RUN sudo su
RUN sudo apt update \
&& sudo apt install --no-install-recommends --assume-yes --quiet \
imagemagick \
php-imagick \
php8.1-imagick \
php8.2-imagick \
php8.3-imagick \
&& sudo rm -rf /var/lib/apt/lists/*

Shopware 6 Tipp: Nicht funktionierende Snippets

Ein Tipp der eher für Benutzer ist.

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.

Shopware 6: Associations sortieren

Eine kleines Beispiel wie man Associations sortiert, weil es wohl nicht so einfach ist, so eines zu finden.


$criteria->addAssociation('lineItems');
$criteria->getAssociation('lineItems')->addSorting(new FieldSorting('position'));


In einer einzigen Zeile geht es nicht, weil dann die falsche Criteria genutzt wird.

Filter und ähnliches könne so auch gesetzt werden.

EDC/One-DC ist EOL

Zum 2024-01-01 hat One-DC Teile ihrer Dropshipping-Services abgeschaltet. Man kann immer noch Bestellungen dort per API aufgeben und diese direkt an seine Shopkunden senden, aber es gibt keine Feeds mit Produkten, Beständen und Preisen mehr. Der Sinne erschließt sich mir überhaupt nicht, da Dropshipping ja doch irgendwie weiterhin möglich ist und elektronische Katalogdaten auch für PIMs und Kassensysteme der Kunden wichtig ist. Hätte man den Versand an die Endkunden eingestellt würde ich es ja noch verstehen, aber so macht es für mich keinen Sinn. Falls jemand mehr Weiß oder eine Möglichkeit kennt Katalogdaten weiterhin zu erhalten, bitte sich per Email bei mir melden.

Import2Shop stellt deren Anbindung zu EDC auch ein und die Shops sitzen jetzt und versuchen möglichst schnell zu einem anderen Anbieter zu wechseln. Meine Plugins bleiben weiter online, erhalten aber keine Weiterentwicklung mehr und gelten ab sofort als EOL, wenn nicht sich doch noch was neues ergeben sollte.

PHP: XLSX einfach lesen

Bei in Java bei XML ist die bekannteste Lösung manchmal nicht die Lösung, die man gerade braucht. Groß, komplex und kann alles. Dependencies machen dann aber Probleme und wenn man nur eine Datei schnell und einfach lesen möchte, braucht man nicht irgend eine HTML-Lib, die nur in ganz bestimmten Fällen nötig wäre.

Beim Lesen von Excel-Dateien in PHP ist es genau so. HTML-Lib machte Probleme beim Installieren über Composer, aber ich will ga rkeine HTML-Sachen damit machen. Cool das es gehen würde, aber ich will nur schnell und einfach die Daten der Tabelle auslesen. CSV hätte ja gereicht, aber es kommt eben eine Excel-Datei.

Dann fand ich SimpleXLSX.

Super einfach zu benutzen. Hat keine weiteren Dependencies und liefert direkt die Tabelle als ein plain old Array.

bbcode-image


Code (XLSX to JSON):

<?php
include_once __DIR__ . '/vendor/autoload.php';

use Shuchkin\SimpleXLSX;

if ( $xlsx = SimpleXLSX::parse(__DIR__ . '/pets.xlsx') ) {
$json = [];
$head = $xlsx->rows()[0];

foreach ($xlsx->rows() as $idx => $row) {
if($idx > 0) {
$item = [];
foreach ($head as $idxKey => $key) {
if(strlen($key) > 0) {
$item[$key] = $row[$idxKey];
}
}
$json[] = $item;
}
}

echo json_encode($json);
} else {
echo SimpleXLSX::parseError();
}


Ergebnis:

[
{
"Name": "Miki",
"Rasse": "Hund",
"Farbe": "braun"
},
{
"Name": "Teddy",
"Rasse": "Hund",
"Farbe": "wei\\u00df"
},
{
"Name": "Merle",
"Rasse": "Katze",
"Farbe": "wei\\u00df|braun|schwarz"
},
{
"Name": "Finja",
"Rasse": "Katze",
"Farbe": "braun|schwarz"
},
{
"Name": "Ronja",
"Rasse": "Katze",
"Farbe": "wei\\u00df|schwarz"
},
{
"Name": "Molly",
"Rasse": "Katze",
"Farbe": "grau"
}
]

Older posts:

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