<?xml version="1.0" encoding="utf-8" ?>

<rss version="2.0" 
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:admin="http://webns.net/mvcb/"
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
   xmlns:wfw="http://wellformedweb.org/CommentAPI/"
   xmlns:content="http://purl.org/rss/1.0/modules/content/"
      xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
   xmlns:atom="http://www.w3.org/2005/Atom"
   xmlns:sc="http://podlove.org/simple-chapters"
 xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"  xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" >
<channel>
     

<itunes:subtitle>Sperrobjekt Weblog</itunes:subtitle>
<itunes:author>Sperrobjekt Weblog</itunes:author>
<itunes:summary>The Social Web, Coding, Linux ... and Football, of course!</itunes:summary>
<itunes:image href="http://blog.sperrobjekt.de/itunes.jpg" />
<itunes:category text="Technology" />                
                
    <title>Sperrobjekt Weblog</title>
    <link></link>
    <description>The Social Web, Coding, Linux ... and Football, of course!</description>
    <dc:language>de</dc:language>
    <generator>Serendipity 2.5.0 - http://www.s9y.org/</generator>
    <pubDate>Sat, 30 Aug 2025 18:08:34 GMT</pubDate>

    <image>
    <url>templates/2k11/img/s9y_banner_small.png</url>
    <title>RSS: Sperrobjekt Weblog - The Social Web, Coding, Linux ... and Football, of course!</title>
    <link></link>
    <width>100</width>
    <height>21</height>
</image>

<item>
    <title>Wochenrückblick KW 35/2025</title>
    <link>content/1000558-Wochenrueckblick-KW-352025.html</link>
            <category>Deutsche Beiträge</category>
    
    <comments>content/1000558-Wochenrueckblick-KW-352025.html#comments</comments>
    <wfw:comment>wfwcomment.php?cid=1000558</wfw:comment>

    <slash:comments>0</slash:comments>
    <wfw:commentRss>rss.php?version=2.0&amp;type=comments&amp;cid=1000558</wfw:commentRss>
    

    <author>nospam@example.com (Matthias Gutjahr)</author>
    <content:encoded>
    &lt;p&gt;In den letzten Wochen war Sommerpause angesagt. Keine Lust zu bloggen &lt;img src=&quot;plugins/serendipity_event_emoticate/img/emoticons/wink.png&quot; alt=&quot;;-)&quot; class=&quot;emoticon&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;-urlaub&quot;&gt;🏖️ Urlaub&lt;/h2&gt;

&lt;p&gt;Wir waren im Urlaub in der Oberpfalz, und ich konnte dort zum Glück ein paar Regenpausen zum Graveln nutzen. Sehr schön und gut fahrbar war etwa der &lt;a href=&quot;https://de.wikipedia.org/wiki/Schwarzachtal-Radweg&quot;&gt;Schwarzachtal-Radweg&lt;/a&gt;. Interessant auch das verlassene Dorf &lt;a href=&quot;https://de.wikipedia.org/wiki/Lu%C4%8Dina_(Nemanice)&quot;&gt;Lučina&lt;/a&gt; (Grafenried), dessen deutschsprachige Bewohner nach dem Zweiten Weltkrief vertrieben wurden. Und auch sonst haben wir eigentlich das Beste aus dem bescheidenen Wetter gemacht. Nur die geplante Kanutour auf dem Regen ist aus dem gleichnamigen Grund ins Wasser gefallen 🫣&lt;/p&gt;

&lt;h2 id=&quot;-literatur&quot;&gt;📚 Literatur&lt;/h2&gt;

&lt;p&gt;Aber positiv: Dank des Wetters hatte ich im Urlaub viel Zeit zu lesen, unter anderem folgende Romane:&lt;/p&gt;

&lt;h3 id=&quot;tom-hillenbrand-lieferdienst&quot;&gt;Tom Hillenbrand - Lieferdienst&lt;/h3&gt;

&lt;p&gt;Dass der Autor früher als Wirtschaftsjournalist gearbeitet hat, merkt man diesem dystopischen Roman an. Der Protagonist steht im Mittelpunkt des Konkurrenzkampfes zwischen Lieferdiensten, die schon mal über Leichen gehen. Ähnlichkeiten mit real existierenden globalen Firmen sind nicht von der Hand zu weisen. Flott geschrieben, nicht allzu lang, und mit Hoverboards: Gute Unterhaltung.&lt;/p&gt;

&lt;h3 id=&quot;nnedi-okorafor-death-of-the-author&quot;&gt;Nnedi Okorafor - Death of the Author&lt;/h3&gt;

&lt;p&gt;Ich bin zugegebenermaßen Fan von Okorafors &lt;em&gt;Africanfuturism&lt;/em&gt; und wurde auch hier nicht enttäuscht. Okorafor verwebt zwei Storylines miteinander, beide spielen in der Zukunft, eine ist die Fiktion in der Fiktion. Wie auch Okorafor pendeln die Protagonisten zum Teil zwischen den USA und Nigeria. Ich musste allerdings aufpassen, dass ich mich streckenweise nicht in das Setting von TJ Klunes &quot;In the Lives of Puppets&quot; verirrte - gewisse Ähnlichkeiten sind vorhanden.&lt;/p&gt;

&lt;h3 id=&quot;colson-whitehead-die-intuitionistin&quot;&gt;Colson Whitehead - Die Intuitionistin&lt;/h3&gt;

&lt;p&gt;Gelesen in der Neuübersetzung, das englischsprachige Original von 1999 wäre vermutlich die bessere Wahl gewesen (aber das gab&#039;s gerade nicht in der Onleihe). Abgesehen davon fand ich den Roman aber gut, die vielen originellen Einfälle haben Spaß gemacht, auch wenn sie mir manchmal zu sehr ins Detail gingen. Insgesamt ließ er sich aber flüssig lesen und ist auch nach 25 Jahren inhaltlich noch relevant.&lt;/p&gt;

&lt;p&gt;Passend dazu bin ich übrigens auf ein weiteres Item für meine Bucket-List gestoßen: Das &lt;a href=&quot;https://de.wikipedia.org/wiki/Aufzugmuseum&quot;&gt;Aufzugmuseum&lt;/a&gt; im Wasserturm Mannheim-Seckenheim. Da muss ich hin! In zwei Wochen ist &lt;a href=&quot;https://www.tag-des-offenen-denkmals.de/&quot;&gt;Tag des Offenen Denkmals&lt;/a&gt;, aber leider scheint das Museum diesmal nicht teilzunehmen 😔&lt;/p&gt;

&lt;h3 id=&quot;lesetagebu-ch&quot;&gt;Lesetagebu.ch&lt;/h3&gt;

&lt;p&gt;Ich sammle meine gelesenen Bücher übrigens seit einigen Jahren im &lt;a href=&quot;https://lesetagebu.ch/&quot;&gt;Lesetagebu.ch&lt;/a&gt;. Allerdings überlege ich, ob ich mir nicht (auch) einen &lt;a href=&quot;https://bookwyrm.social/&quot;&gt;Bookwyrm&lt;/a&gt;-Account zulegen sollte. Oder welche freien Alternativen mögt ihr?&lt;/p&gt;

&lt;h2 id=&quot;-haus-garten&quot;&gt;🏡 Haus &amp;amp; Garten&lt;/h2&gt;

&lt;h3 id=&quot;w-rmepumpe&quot;&gt;Wärmepumpe&lt;/h3&gt;

&lt;p&gt;Mittlerweile steht der Einbau kurz bevor, das Fundament für das Außengerät ist gesetzt, die Leitungen wurden durch die Außenwand gelegt, und die Elektronik samt Steuerschrank wurde bereits installiert. Zwei Heizkörper müssen noch getauscht werden, und dann starten die Arbeiten im Heizungskeller. Ich bin schon sehr aufgeregt.&lt;/p&gt;

&lt;p&gt;Wir hatten unterdessen schon einige Nächte mit Temperaturen um die 4 °C, aber ich möchte die alte Gasheizung eigentlich gar nicht mehr einschalten.&lt;/p&gt;

&lt;h2 id=&quot;-computer&quot;&gt;🖥️ Computer&lt;/h2&gt;

&lt;h3 id=&quot;pangolin&quot;&gt;Pangolin&lt;/h3&gt;

&lt;p&gt;Zwar soll irgendwann Glasfaser kommen, aber noch kommt das Internet über den Kabelanschluss ins Haus, und wegen DS-Lite ist es kompliziert, von außen auf mein Homelab zuzugreifen. Klar komme ich per VPN rein, aber das ist nicht immer möglich oder komfortabel. Daher habe ich mich diese Woche hingesetzt und &lt;a href=&quot;https://github.com/fosrl/pangolin&quot;&gt;Pangolin&lt;/a&gt; installiert:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pangolin is an open-source and identity-aware tunneled reverse proxy server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pangolin läuft auf einem kleinen VPS und ist dort über ein Web-Dashboard administrierbar. Auf meinem Homelab läuft &lt;a href=&quot;https://github.com/fosrl/newt&quot;&gt;Newt&lt;/a&gt;,&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;a fully user space WireGuard tunnel client and TCP/UDP proxy, designed to securely expose private resources controlled by Pangolin.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ich kann jetzt für einzelne Homelab-Dienste in Pangolin eine Subdomain konfigurieren, die dann über den Tunnel auf die interne IP:Port geleitet wird - aber erst nach Autorisierung im Browser. Den Zugriff kann ich rollen- und nutzerbasiert einschränken und auch noch feiner konfigurieren. Auch Einmalpasswörter und zeitliche Freigaben sind möglich, wenn ich das richtig verstanden habe.&lt;/p&gt;

&lt;h2 id=&quot;-fahrrad&quot;&gt;🚴 Fahrrad&lt;/h2&gt;

&lt;h3 id=&quot;dotwatching&quot;&gt;Dotwatching&lt;/h3&gt;

&lt;p&gt;Ich fahre nicht nur selbst gern Fahrrad, sondern schaue mir auch gern passiv Radsport-Events an: Tour de France, Giro d&#039;Italia, Vuelta a España, Frühjahrsklassiker, Cyclocross im Winter - und in letzter Zeit auch immer häufiger Langstreckenevents wie das Atlas Mountain Race, die Tour Divide, &lt;a href=&quot;https://blog.sperrobjekt.de/content/1000549-Wochenrueckblick-KW-24-2024.html&quot;&gt;Taunus Bikepacking&lt;/a&gt;, das Silk Road Mountain Race und das Transcontinental Race.&lt;/p&gt;

&lt;p&gt;Diese Rennen sind so besonders, weil sich die Teilnehmenden selbst versorgen müssen, wenn sie viele Tage am Stück mit nur wenigen Pausen tausende Kilometer zurücklegen auf einer Strecke, die sie je nach Rennen sogar selbst geplant haben. Natürlich gibt es keine Fernsehübertragungen davon, und selbst Beiträge auf Social Media sind schwierig, weil sich das Feld unendlich in die Länge zieht.&lt;/p&gt;

&lt;p&gt;Aber es gibt eine Landkarte, auf der viele bunte Punkte zu sehen sind: die Dots. Denn alle, die unterwegs sind, führen einen GPS-Tracker mit sich, der in regelmäßigen Abständen seine Position durchgibt. Und diese landet dann - mit weiteren Zusatzinfos versrehen - auf der Map. So kann ich von meinem Bildschirm zuhause jederzeit sehen, wer wann wo fährt. Ich beobachte die Punkte auf der Landkarte: Dot-Watching eben.&lt;/p&gt;

&lt;p&gt;Und ich bin nicht allein damit. Auf Social Media-Plattformen findet sich zu diesen Events eine Gemeinschaft von Dotwatcherinnen und Dotwatchern zusammen, die alle mitfiebern, Infos sammeln und austauschen. Was ist in der vergangenen Nacht passiert? Hat Fahrer X technische Probleme? Wo steckt Fahrerin Y? Oder zuletzt beim Transcontinental Race, bei dem eine selten verkehrende Fähre zwischen Italien und Albanien benutzt werden durfte: Wer erreicht die Fähre noch pünktlich?&lt;/p&gt;

&lt;p&gt;Das kann wahnsinnig spannend werden und auch emotional, wenn man einzelne Teilnehmende über Tage verfolgt und versucht, sich deren Höhen und Tiefen auszumalen anhand der spärlichen zur Verfügung stehenden Informationen. Wenn ihr mehr über diese Faszination erfahren wollt, hört euch die &lt;a href=&quot;https://detektor.fm/gesellschaft/antritt-transcontinental-race&quot;&gt;aktuelle Folge des &quot;Antritt&quot;-Podcasts&lt;/a&gt; auf detektor.fm an oder schaut euch die ZDF-Doku &lt;a href=&quot;https://www.zdf.de/reportagen/das-verrueckteste-radrennen-der-welt-movie-100&quot;&gt;&quot;Das verrückteste Radrennen der Welt&quot;&lt;/a&gt; mit der Wiederholungssiegerin Jana Kesenheimer an.&lt;/p&gt;

&lt;p&gt;Und dann setzt euch aufs Fahrrad und fahrt los.&lt;/p&gt;
 
    </content:encoded>

    <pubDate>Sat, 30 Aug 2025 18:06:00 +0000</pubDate>
    <guid isPermaLink="false">content/1000558-guid.html</guid>
    <category>fahrrad</category>
<category>literatur</category>
<category>wärmepumpe</category>
<category>wochenrückblick</category>
<creativeCommons:license>http://creativecommons.org/licenses/by/4.0/deed.de</creativeCommons:license>
</item>
<item>
    <title>Wochenrückblick KW 29/2025</title>
    <link>content/1000557-Wochenrueckblick-KW-292025.html</link>
            <category>Deutsche Beiträge</category>
    
    <comments>content/1000557-Wochenrueckblick-KW-292025.html#comments</comments>
    <wfw:comment>wfwcomment.php?cid=1000557</wfw:comment>

    <slash:comments>0</slash:comments>
    <wfw:commentRss>rss.php?version=2.0&amp;type=comments&amp;cid=1000557</wfw:commentRss>
    

    <author>nospam@example.com (Matthias Gutjahr)</author>
    <content:encoded>
    &lt;h2 id=&quot;-fahrrad&quot;&gt;🚴 Fahrrad&lt;/h2&gt;

&lt;h3 id=&quot;lord-helmchen&quot;&gt;Lord Helmchen&lt;/h3&gt;

&lt;p&gt;Ich bin auf der Suche nach einem Rennrad-/Gravelbike-Fahrradhelm, der gut auf schmalere Köpfe passt. Die meisten Helme sind mir tatsächlich zu weit, so auch das Nachfolgemodell meines aktuellen Specialized Chamonix, der &lt;a href=&quot;https://www.specialized.com/de/de/chamonix-3/p/1000208478&quot;&gt;Chamonix 3&lt;/a&gt;. Dabei wäre der relativ günstig und hat sogar MIPS. Vielleicht muss ich doch mal vor Ort im Fahrradgeschäft testen.&lt;/p&gt;

&lt;h3 id=&quot;geo-activity-playground&quot;&gt;Geo Activity Playground&lt;/h3&gt;

&lt;p&gt;Ich habe kürzlich meine 500. Strava-Aktivität aufgezeichnet, aber ich beobachte durchaus auch andere Optionen, um meine Radfahrten zu sammeln und auszuwerten. Eine Zeit lang habe ich Tools wie &lt;a href=&quot;https://www.goldencheetah.org/&quot;&gt;GoldenCheetah&lt;/a&gt; (zu viel Analyse) oder &lt;a href=&quot;https://turtlesport.sourceforge.io/EN/home.html&quot;&gt;Turtle Sport&lt;/a&gt; (wird nicht mehr weiterentwickelt) ausprobiert, die ausschließlich lokal auf dem eigenen Computer laufen - aber wieder verworfen.&lt;/p&gt;

&lt;p&gt;Nun bin ich auf &lt;a href=&quot;https://martin-ueding.github.io/geo-activity-playground/&quot;&gt;Geo Activity Playground&lt;/a&gt; von Martin Ueding aufmerksam geworden und spiele seit einer Weile lokal damit herum. Gefällt mir echt gut, weil die Anwendung interessante und wirklich nett gemachte Statistiken liefert und auch Ansichten für &lt;a href=&quot;https://martin-ueding.github.io/geo-activity-playground/explorer-tiles/&quot;&gt;&quot;Explorer Tiles&quot; bzw. &quot;Squadratinhos&quot;&lt;/a&gt; mitbringt. Bisher sammle ich zwar keine Kartenquadrate, aber das kann ja noch kommen.&lt;/p&gt;

&lt;h2 id=&quot;-haus-garten&quot;&gt;🏡 Haus &amp;amp; Garten&lt;/h2&gt;

&lt;h3 id=&quot;glasfaserausbau&quot;&gt;Glasfaserausbau&lt;/h3&gt;

&lt;p&gt;In unsere Straße werden zurzeit &lt;a href=&quot;https://www.taunusstein.de/portal/pressemitteilungen/glasfaserausbau-in-wehen-deutsche-telekom-startet-schon-bald-auswirkungen-fuer-buergerinnen-und-buerger-900003811-29880.html?rubrik=900000071&quot;&gt;Glasfaser&lt;/a&gt;-Leerrohre verlegt, und auch in unseren Keller führt jetzt eine Abzweigung. Die Kommunkation im Vorfeld war ziemlich unterirdisch (bzw. nicht vorhanden), aber trotzdem hat irgendwie alles geklappt. Das lag vor allem an dem (in unserem Fall spanischen) Subunternehmer-Arbeitstrupp vor Ort, der ein unglaubliches Arbeitspensum abgeleistet hat, und trotzdem immer hilfsbereit war. Dennoch bleibt mit Blick auf die &lt;a href=&quot;https://www.tagesschau.de/wirtschaft/arbeitsmarkt/glasfaserausbau-arbeitsbedingungen-100.html&quot;&gt;Arbeitsbedingungen&lt;/a&gt; ein &lt;a href=&quot;https://igbau.de/Fallsammlung-Glasfaserausbau.html&quot;&gt;ungutes&lt;/a&gt; &lt;a href=&quot;https://www.faire-mobilitaet.de/fachinformationen/berichte-und-erfolge/glasfaserausbau-prekaere-bedingungen-und-lohnbetrug/&quot;&gt;Gefühl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Fast zeitgleich kam übrigens die &lt;a href=&quot;https://www.taunusstein.de/portal/pressemitteilungen/unsere-gruene-glasfaser-zieht-sich-aus-taunusstein-zurueck-900003950-29880.html?rubrik=900000071&quot;&gt;Meldung&lt;/a&gt;, dass sich das Unternehmen &quot;Unsere Grüne Glasfaser&quot; (UGG) aus dem Ausbau zurückzieht. UGG war nach &quot;GVG Glasfaser&quot; schon das zweite Unternehmen, das bei uns ausbauen wollte, aber dann von der Deutschen Telekom verdrängt wurde.&lt;/p&gt;

&lt;h3 id=&quot;w-rmepumpe&quot;&gt;Wärmepumpe&lt;/h3&gt;

&lt;p&gt;Wir sind dabei, von einer Gasheizung auf eine Wärmepumpe umzustellen, was sich als längerer Prozess herausstellt. Nun wurde zumindest die &lt;a href=&quot;https://de.wikipedia.org/wiki/Heizlast#Heizlast_nach_EN_12831&quot;&gt;Heizlastberechnung&lt;/a&gt; unseres Hauses durchgeführt, um die Wärmepumpe richtig dimensionieren zu können. Ein Ergebnis habe ich allerdings noch nicht vorliegen.&lt;/p&gt;

&lt;h2 id=&quot;-computer&quot;&gt;🖥️ Computer&lt;/h2&gt;

&lt;h3 id=&quot;jumelages&quot;&gt;Jumelages&lt;/h3&gt;

&lt;p&gt;Ich habe mein neues Feierabend-Projekt, eine kleine (experimentelle) Webseite zum Auffinden von Partnerstädten/-gemeinden online gestellt. Die Daten stammen aus Wikidata und Openstreetmap, mehr dazu &lt;a href=&quot;https://blog.sperrobjekt.de/content/1000556-Jumelages-Finde-Partnerkommunen-mit-Wikidata-und-Openstreetmap-und-Vibe-Coding.html&quot;&gt;in einem eigenen Blogpost&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;taproom&quot;&gt;taproom&lt;/h3&gt;

&lt;p&gt;Mittlerweile installiere und aktualisiere ich Pakete auf meinem Linux-System nicht mehr nur mit &lt;code&gt;apt&lt;/code&gt;, sondern auch als &lt;code&gt;snap&lt;/code&gt;, &lt;code&gt;flatpak&lt;/code&gt;, &lt;code&gt;AppImage&lt;/code&gt; und &lt;code&gt;Homebrew&lt;/code&gt;. Ob das gut und sinnvoll ist? Ich weiß es nicht, aber aktuellste Programmversionen bekomme ich oft nur so. Für das &lt;code&gt;Homebrew&lt;/code&gt;-Ökosystem habe ich nun &lt;a href=&quot;https://terminaltrove.com/taproom/&quot;&gt;taproom&lt;/a&gt; entdeckt, ein &quot;TUI for the Homebrew package manager&quot;. Das Tool ist zwar noch stark in der Entwicklung, aber es hilft mir ein bisschen dabei, den Überblick über die installierten Homebrew-Pakete zu behalten.&lt;/p&gt;
 
    </content:encoded>

    <pubDate>Tue, 22 Jul 2025 07:57:00 +0000</pubDate>
    <guid isPermaLink="false">content/1000557-guid.html</guid>
    <category>fahrrad</category>
<category>glasfaser</category>
<category>strava</category>
<category>taproom</category>
<category>wärmepumpe</category>
<category>wochenrückblick</category>
<creativeCommons:license>http://creativecommons.org/licenses/by/4.0/deed.de</creativeCommons:license>
</item>
<item>
    <title>Jumelages - Finde Partnerkommunen mit Wikidata und Openstreetmap (und Vibe-Coding)</title>
    <link>content/1000556-Jumelages-Finde-Partnerkommunen-mit-Wikidata-und-Openstreetmap-und-Vibe-Coding.html</link>
            <category>Deutsche Beiträge</category>
    
    <comments>content/1000556-Jumelages-Finde-Partnerkommunen-mit-Wikidata-und-Openstreetmap-und-Vibe-Coding.html#comments</comments>
    <wfw:comment>wfwcomment.php?cid=1000556</wfw:comment>

    <slash:comments>0</slash:comments>
    <wfw:commentRss>rss.php?version=2.0&amp;type=comments&amp;cid=1000556</wfw:commentRss>
    

    <author>nospam@example.com (Matthias Gutjahr)</author>
    <content:encoded>
    &lt;h2 id=&quot;die-idee&quot;&gt;Die Idee&lt;/h2&gt;

&lt;p&gt;Auf meinen Fahrradtouren fahre ich immer wieder durch kleinere und größere Ortschaften, und viele dieser Gemeinden haben am Ortseingang ein Schild stehen, das über &lt;a href=&quot;https://de.wikipedia.org/wiki/Gemeindepartnerschaft&quot;&gt;Partnerstädte bzw. -gemeinden&lt;/a&gt; informiert. Alternativ weisen Wegweiser in die Richtung der Partnerkommune und zeigen die Entfernung an. Als Kind habe ich die &lt;a href=&quot;https://www.hirschberg-bergstrasse.de/gemeinde/unser-hirschberg/partnergemeinden?sortDirection=ascending&amp;amp;sortType=alphabetic&quot;&gt;Einrichtung einer solchen Partnerschaft&lt;/a&gt; mit einer französischen Gemeinde erlebt, es gab ein kleines Fest, ein Platz wurde nach der Partnergemeinde benannt, und der Partnerschaftsverein vergrub dort eine Zeitkapsel mit Andenken aus dem Jahr 1986.&lt;/p&gt;

&lt;p&gt;Ich finde das Konzept von Gemeindepartnerschaften eigentlich ganz spannend und machte mich auf die Suche nach einer Übersicht über Gemeindepartnerschaften. Fündig wurde ich beim &lt;a href=&quot;https://www.rgre.de/&quot;&gt;Rat der Gemeinden und Regionen Europas&lt;/a&gt;, der eine &lt;a href=&quot;https://www.rgre.de/partnerschaft/datenbank&quot;&gt;Datenbank der kommunalen Partnerscahften&lt;/a&gt; pflegt. Diese Datenbank ist &lt;a href=&quot;https://www.rgre.de/partnerschaft/online-datenbank/listenansicht&quot;&gt;durchsuchbar&lt;/a&gt; und bietet sogar eine &lt;a href=&quot;https://www.rgre.de/partnerschaft/online-datenbank&quot;&gt;Kartenansicht&lt;/a&gt;, die auf Openstreetmap basiert. Fehlende Partnerschaften können über ein Formular beantragt werden. Nach eigenen Angaben enthält die Datenbank etwa 7.000 Einträge.&lt;/p&gt;

&lt;p&gt;Aber ließe sich eine solche Karte nicht auch mit Offenen Daten realisieren? Immerhin gibt es in Wikidata die Eigenschaft &quot;&lt;a href=&quot;https://www.wikidata.org/wiki/Property:P190&quot;&gt;P190 twinned adminstrative body&lt;/a&gt;&quot;, welche einzelnen Gemeinden zugeordnet werden kann und sie mit ihrer Partnergemeinde verknüpft. Und auch Openstreetmap &lt;code&gt;node&lt;/code&gt;- oder &lt;code&gt;relation&lt;/code&gt;-IDs sind häufig in Wikidata erfasst. Potentiell dürften dort sogar mehr Partnerschaften eingetragen sein als in der oben genannten Datenbank - oder zumindest ist das mein Eindruck.&lt;/p&gt;

&lt;p&gt;Ich wollte das aber genauer wissen und auch so eine Karte auf dieser Datenbasis erstellen. Ein Gerüst für eine kleine PHP-Webanwendung in Laravel war schnell erstellt, die Openstreetmap-Karte konnte ich einfach mit &lt;a href=&quot;https://leafletjs.com/&quot;&gt;Leaflet&lt;/a&gt; anzeigen. Dann habe ich manuell Testdaten für eine Handvoll Städte und Partnerstädte in die Datenbank eingetragen und diese im JSON-Format ans Frontend übergeben und auf der Karte angezeigt. So weit, so gut. Aber wie geht es jetzt weiter? Die Daten sollen schließlich per SPARQL-Abfrage von Wikidata abgeholt, verarbeitet und gespeichert werden. Meine bisherigen SPARQL-Kenntnisse sind bestenfalls rudimentär. Also erstmal &lt;a href=&quot;https://www.wikidata.org/wiki/Wikidata:SPARQL_tutorial/de&quot;&gt;SPARQL lernen&lt;/a&gt;?&lt;/p&gt;

&lt;h2 id=&quot;die-umsetzung&quot;&gt;Die Umsetzung&lt;/h2&gt;

&lt;p&gt;Im Januar 2025 kam dann die Meldung, dass GitHub Copilot auch LLMs von Anthropoic, Google und OpenAI untestützt. Zuvor hatte ich nur zaghaft mit &quot;Vibe Coding&quot; experimentiert, und die Antworten der AI-Modelle waren auch oft enttäuschend geblieben. Aber nach vermehrt positiven Berichten gab ich den Coding-Assistenten eine neue Chance. Und es hat erstaunlich gut funktioniert!&lt;/p&gt;

&lt;p&gt;Nach wenigen Prompts hatte mit Copilot ich eine SPARQL-Query erzeugt, die mir das gewünschte Ergebnis liefert. Beinahe gleichzeitig entdecke ich auch &lt;a href=&quot;https://spinach.genie.stanford.edu/&quot;&gt;Spinach Wikidata&lt;/a&gt;, einen&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;experimental assistant designed to help users converse data stored in Wikidata using natural language.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Spinach schlug folgende Abfrage vor:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;sparql&quot;&gt;SELECT ?community ?communityLabel ?twin ?twinLabel WHERE {
  ?community wdt:P190 ?twin.
  SERVICE wikibase:label { bd:serviceParam wikibase:language &quot;[AUTO_LANGUAGE],en&quot;. }
}
LIMIT 50
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;und erklärte sie auf Wunsch auch:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
  &lt;li&gt;&lt;code&gt;WHERE { ?community wdt:P190 ?twin. }&lt;/code&gt;:
  
  &lt;ul&gt;
  &lt;li&gt;&lt;code&gt;wdt:P190&lt;/code&gt; is the property for &quot;twinned administrative body.&quot;&lt;/li&gt;
  &lt;li&gt;The query retrieves all pairs of entities where a community (&lt;code&gt;?community&lt;/code&gt;) is linked to its twin (&lt;code&gt;?twin&lt;/code&gt;) via this property.&lt;/li&gt;
  &lt;/ul&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;SERVICE wikibase:label { bd:serviceParam wikibase:language &quot;[AUTO_LANGUAGE],en&quot;. }&lt;/code&gt;:
  
  &lt;ul&gt;
  &lt;li&gt;This ensures that the labels (&lt;code&gt;?communityLabel&lt;/code&gt; and &lt;code&gt;?twinLabel&lt;/code&gt;) are retrieved in the user&#039;s preferred language (or English as a fallback).&lt;/li&gt;
  &lt;/ul&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Das war schon ziemlich gut als Grundlage. Weitere Verfeinerungen nahm ich dann im Wechsel manuell und mithilfe von Copilot und Spinach vor. Aufgrund der Menge der Daten kann der Wikidata-Endpoint schon einmal überlastet werden oder in einem Timeout laufen. Daher musste ich die Daten in mehrere Abfragen unterteilen.&lt;/p&gt;

&lt;p&gt;Und schließlich galt es auch noch die Kartenanzeige im Frontend zu verbessern. Auch hier nutzte ich meine AI-Assistanten zum Ausbauen meines rudimentären Code-Gerüsts, später auch zum Refaktorieren und Aufteilen des Codes in separate Funktionen.&lt;/p&gt;

&lt;h2 id=&quot;das-ergebnis&quot;&gt;Das Ergebnis&lt;/h2&gt;

&lt;p&gt;&lt;a  class=&quot;serendipity_image_link&quot;   rel=&#039;lightbox[1000556]&#039; href=&#039;uploads/jumelages.png&#039;&gt;&lt;!-- s9ymdb:683 --&gt;&lt;img class=&quot;serendipity_image_center&quot; width=&quot;1024&quot; height=&quot;546&quot;  src=&quot;uploads/jumelages.png&quot;  loading=&quot;lazy&quot; alt=&quot;Screenshot der Anwendung Jumelages&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nach einigen Feierabend-Sitzungen und weiteren, diesmal manuellen Überarbeitungen war ich mit dem Prototypen erst einmal zufrieden. Die Daten werden jetzt in mehreren Abfragen von Wikidata ausgelesen, umgeformt und in der Datenbank gespeichert. Die Anzeige auf der Karte erfolgt bei geringen Zoomstufen als Cluster, beim Reinzoomen dann genauer. Nach dem Klick auf eine Gemeinde wird wieder so weit herausgezoomt, dass alle Partnerkommunen angezeigt werden.&lt;/p&gt;

&lt;p&gt;Ich habe noch ein paar Ideen für Features und Verbesserungen, aber die Anwendung funktioniert erstmal und ist live unter &lt;strong&gt;&lt;a href=&quot;https://jumelages.gutjahr.dev/&quot;&gt;jumelages.gutjahr.dev&lt;/a&gt;&lt;/strong&gt; zu sehen. Der Quellcode liegt auf Codeberg im Repository &lt;a href=&quot;https://codeberg.org/mattsches/jumelages&quot;&gt;mattsches/jumelages&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;was-noch-fehlt&quot;&gt;Was noch fehlt&lt;/h3&gt;

&lt;p&gt;In Wikidata sind offenbar noch deutlich mehr &quot;twinned administrative bodies&quot; eingetragen, aber mir ist es bisher nicht gelungen, sie abzufragen. Denn es scheint für Gemeinden/Städte keine übergreifende Eigenschaft oder Hierarchie zu geben, mit der ich die entsprechenden Einträge zurückbekommen könnte. Manche deutschen Gemeinden sind als &quot;&lt;code&gt;Q116457956 non-urban municipality in Germany&lt;/code&gt;&quot; klassifiziert, was wiederum eine Unterklasse von &quot;&lt;code&gt;Q262166 municipality in Germany&lt;/code&gt;&quot; darstellt, die wiederum auf &quot;&lt;code&gt;Q15284 municipality&lt;/code&gt;&quot; basiert. Und so weiter. Da reichen meine Wikidata-/SPARQL-Kenntnisse nicht weit genug, und auch die KI konnte mir bisher nicht weiterhelfen. Vielleicht findet sich ja eine &lt;em&gt;human intelligence&lt;/em&gt;, die dafür eine Lösung kennt.&lt;/p&gt;

&lt;p&gt;Ich würde außerdem die geraden Linien zwischen den Partnerkommunen durch gebogene Kurven ersetzen. Es gibt für diesen Zweck ein &lt;a href=&quot;https://github.com/elfalem/Leaflet.curve&quot;&gt;Leaflet-Plugin&lt;/a&gt;, aber damit habe ich mich noch nicht weiter beschäftigt. Mein erster Eindruck ist jedoch positiv, das Feature sollte sich umsetzen lassen.&lt;/p&gt;

&lt;p&gt;Für weitere Ideen und Feature Requests bin ich offen (habe auch selbst noch ein paar weitere), aber die Umsetzung hängt natürlich stark von zeitlichen Ressourcen ab. Vielleicht sollte ich auch mal messen, ob &lt;em&gt;Jumelages&lt;/em&gt; überhaupt genutzt wird - Tracking habe ich noch gar nicht eingebaut.&lt;/p&gt;

&lt;h2 id=&quot;fazit&quot;&gt;Fazit&lt;/h2&gt;

&lt;h3 id=&quot;allgemein&quot;&gt;Allgemein&lt;/h3&gt;

&lt;p&gt;Aus einem Gedankenexperiment und persönlichem Interesse wurde eine konkrete Idee, und diese ließ sich erfolgreich umsetzen. Die Anwendung ist live und funktioniert als Prototyp. Die Entwicklung hat mir Spaß gemacht, ich konnte mich mit Technologien (z. B. SPARQL) intensiver beschäftigen und Projekte, die ich sehr mag (Openstreetmap, Wikidata), sinnvoll nutzen. Von daher bin ich sehr zufrieden.&lt;/p&gt;

&lt;h3 id=&quot;vibe-coding&quot;&gt;Vibe-Coding&lt;/h3&gt;

&lt;p&gt;Zum Anderen habe ich viele Erfahrungen mit KI-Coding-Assistenten sammeln können und bin eigentlich ganz angetan von den Resultaten. Gerade für solche kleinen Tools und Prototypen eignet sich Vide-Coding recht gut - wie auch die Story von &lt;a href=&quot;https://www.golem.de/news/mit-ki-an-die-spitze-vibe-coder-ohne-coding-skills-dominiert-hackathons-2507-197813.html&quot;&gt;Rene Turcios&lt;/a&gt; zeigt, der mit diese Methode erfolgreich an zahlreichen Hackathons teilgenommen hat. Die Sorge um Code-Qualität einerseits, aber auch um die Zukunft unseres &quot;Handwerks&quot; schwingt natürlich mit. Aber ich kann dennoch jeder Software-Entwicklerin und jedem Software-Entwickler raten, sich mit KI, LLMs und dem sich rasend schnell entwickelnden Ökosystem drum herum auseinanderzusetzen. Und sei es nur, um fundiert mitreden und die Zukunft mitgestalten zu können.&lt;/p&gt;
 
    </content:encoded>

    <pubDate>Mon, 21 Jul 2025 10:49:00 +0000</pubDate>
    <guid isPermaLink="false">content/1000556-guid.html</guid>
    <category>coding assistant</category>
<category>gemeindepartnerschaft</category>
<category>github copilot</category>
<category>jetbrains junie</category>
<category>leaflet</category>
<category>openstreetmap</category>
<category>partnerstadt</category>
<category>sparql</category>
<category>städtepartnerschaft</category>
<category>vibe coding</category>
<category>wikidata</category>
<creativeCommons:license>http://creativecommons.org/licenses/by/4.0/deed.de</creativeCommons:license>
</item>
<item>
    <title>Wochenrückblick KW 28/2025</title>
    <link>content/1000555-Wochenrueckblick-KW-282025.html</link>
            <category>Deutsche Beiträge</category>
    
    <comments>content/1000555-Wochenrueckblick-KW-282025.html#comments</comments>
    <wfw:comment>wfwcomment.php?cid=1000555</wfw:comment>

    <slash:comments>0</slash:comments>
    <wfw:commentRss>rss.php?version=2.0&amp;type=comments&amp;cid=1000555</wfw:commentRss>
    

    <author>nospam@example.com (Matthias Gutjahr)</author>
    <content:encoded>
    &lt;p&gt;Ich versuche es noch einmal mit den Wochenrückblicken …&lt;/p&gt;

&lt;p&gt;&lt;!-- s9ymdb:682 --&gt;&lt;img class=&quot;serendipity_image_center&quot; width=&quot;1024&quot; height=&quot;576&quot;  src=&quot;uploads/bohrmaschinenpumpe.jpg&quot;  loading=&quot;lazy&quot; alt=&quot;Bohrmaschinenpumpe mit angeschlossener Bohrmaschien und Schläuchen&quot;&gt;&lt;/p&gt;

&lt;h2 id=&quot;-feiern&quot;&gt;🎉 Feiern&lt;/h2&gt;

&lt;p&gt;Vor 30 Jahren habe ich mein Abitur gemacht. Am Freitagabend hat sich mein Jahrgang getroffen, um das gebührend zu feiern. Es war ein sehr schöner Abend mit vielen tollen Gesprächen.
Ein Großteil meiner damaligen Mitschüler:innen war da, manche hatte ich seit mehr als zwei Jahrzehnten nicht gesehen, aber alle habe ich ohne Probleme wiedererkannt.
Ich bin immer noch dabei, den komprimierten Input des Abends zu verarbeiten. 🤯&lt;/p&gt;

&lt;h2 id=&quot;-fahrrad&quot;&gt;🚴 Fahrrad&lt;/h2&gt;

&lt;p&gt;Nach dem &lt;a href=&quot;https://www.nibelungen-gravel.de/nibelungen-gravelride&quot;&gt;Nibelungengravelride&lt;/a&gt; vor zwei Wochen, meiner bisher längsten Graveltour mit 155 km und 2.100 hm bei über 30 °C, lasse ich es momentan ruhiger angehen.
Ich bin im Juni so viel gefahren, wie lange nicht mehr, da schadet eine Pause sicher nicht. Außerdem bleibt so mehr Zeit, um &lt;em&gt;Tour de France&lt;/em&gt; zu gucken. 😉&lt;/p&gt;

&lt;h2 id=&quot;-backen&quot;&gt;🍞 Backen&lt;/h2&gt;

&lt;p&gt;Ich habe Mehl bei der &lt;a href=&quot;https://www.seitz-muehle.de/&quot;&gt;Seitz Mühle&lt;/a&gt; gekauft, unter anderem französisches T65 und Schweizer Ruchmehl.
Am Sonntag gab es bereits Brötchen inspiriert von Marcel Paas &lt;a href=&quot;https://www.marcelpaa.com/rezepte/express-semmel/&quot;&gt;Express Semmeln&lt;/a&gt;, aber angepasst an mein Mehl. Als nächstes steht ein Ruchmehl-Brot auf dem Programm.&lt;/p&gt;

&lt;h2 id=&quot;-haus-garten&quot;&gt;🏡 Haus &amp;amp; Garten&lt;/h2&gt;

&lt;p&gt;Ich habe gelernt, dass &lt;a href=&quot;https://de.wikipedia.org/wiki/Bohrmaschinenpumpe&quot;&gt;Bohrmaschinenpumpen&lt;/a&gt; existieren.
Für das Planschbecken im Garten habe ich im Baumarkt so eine für kleines Geld gekauft, auf ein Stück Holz montiert und Bohrmaschine und Schläuche angeschlossen.
Nach einigen Versuchen pumpte der Impeller das Wasser langsam aber stetig aus dem Becken, und wir konnten damit die Pflanzen gießen.
Da die Pumpe komplett aus Kunststoff ist, rechne ich nicht mit langer Haltbarkeit, aber so häufig wird sie auch nicht eingesetzt werden.&lt;/p&gt;

&lt;h2 id=&quot;-computer&quot;&gt;🖥️ Computer&lt;/h2&gt;

&lt;h3 id=&quot;umstieg-von-wallabag-auf-readeck&quot;&gt;Umstieg von Wallabag auf Readeck&lt;/h3&gt;

&lt;p&gt;Mozilla hatte angekündigt, den Dienst &lt;a href=&quot;https://support.mozilla.org/de/kb/Zukunft-von-Pocket&quot;&gt;&quot;Pocket&quot; einzustellen&lt;/a&gt;, eine Sammelstelle für Bookmarks und ein Archiv für Online-Artikel.
Die Aufregung war groß, und schnell wurden einige Alternativen genannt, auch fürs Self-Hosting. Ich bin bereits &lt;a href=&quot;https://blog.sperrobjekt.de/content/1000457-Wallabag-Eine-Open-Source-Read-it-later-App.html&quot;&gt;vor zehn Jahren auf Wallabag&lt;/a&gt; umsgestiegen, war aber in letzter Zeit nicht mehr ganz zufrieden damit.
Es gab Probleme beim Update des Servers, und auch die mobilen Apps funktionierten zum Teil nicht mehr richtig.&lt;/p&gt;

&lt;p&gt;Also habe ich &lt;a href=&quot;https://readeck.org/&quot;&gt;Readeck&lt;/a&gt; ausprobiert und werde das jetzt erst einmal eine Weile testen. Der erste Eindruck ist jedoch gut.&lt;/p&gt;

&lt;h2 id=&quot;-podcasts&quot;&gt;🎧 Podcasts&lt;/h2&gt;

&lt;h3 id=&quot;armutszeugnis-autoindustrie&quot;&gt;Armutszeugnis: Autoindustrie&lt;/h3&gt;

&lt;p&gt;Von der &lt;a href=&quot;https://www.rosalux.de/&quot;&gt;Rosa Luxemburg Stiftung&lt;/a&gt; gibt es einen noch recht jungen Podcast zum Thema Armut:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Einmal im Monat diskutieren Eva Völpel und Sabine Nuss über die Verteilungskrise. Warum haben Wenige so viel und Viele so wenig? Stimmt, was man uns immer predigt, «Ohne Fleiß kein Preis», «Jeder ist seines Glückes Schmied»? Sind die Reichen wirklich smarter? Reichtum und Armut sind gleichermaßen von Mythen umrankt. Wir räumen damit auf.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Die &lt;a href=&quot;https://soundcloud.com/rosaluxstiftung/18-armutszeugnis-autoindustrie-wenn-menschen-nur-noch-zahlen-sind&quot;&gt;aktuelle Folge&lt;/a&gt; zu den Arbeitsbedingungen in der (deutschen) Autoindustrie und die Herausforderungen der Transformation in der Branche ist - nicht nur aus arbeitssoziologischer Perspektive - hörenswert.&lt;/p&gt;
 
    </content:encoded>

    <pubDate>Mon, 14 Jul 2025 15:06:00 +0000</pubDate>
    <guid isPermaLink="false">content/1000555-guid.html</guid>
    <category>wochenrückblick</category>
<creativeCommons:license>http://creativecommons.org/licenses/by/4.0/deed.de</creativeCommons:license>
</item>
<item>
    <title>Datenmigration in PHP: Praktische Patterns &amp; Tools</title>
    <link>content/1000553-Datenmigration-in-PHP-Praktische-Patterns-Tools.html</link>
            <category>Deutsche Beiträge</category>
    
    <comments>content/1000553-Datenmigration-in-PHP-Praktische-Patterns-Tools.html#comments</comments>
    <wfw:comment>wfwcomment.php?cid=1000553</wfw:comment>

    <slash:comments>0</slash:comments>
    <wfw:commentRss>rss.php?version=2.0&amp;type=comments&amp;cid=1000553</wfw:commentRss>
    

    <author>nospam@example.com (Matthias Gutjahr)</author>
    <content:encoded>
    &lt;p&gt;&lt;!-- s9ymdb:681 --&gt;&lt;img class=&quot;serendipity_image_center&quot; width=&quot;1536&quot; height=&quot;1024&quot;  src=&quot;uploads/datenmigration_php.png&quot;  loading=&quot;lazy&quot; alt=&quot;Ein PHP-Elefant, Symbole für Quelltext und Datenbanken und ein PHP-Array mit den Werten &#039;a&#039;, &#039;b&#039; und &#039;c&#039;&quot;&gt;&lt;/p&gt;

&lt;p&gt;Ende Juni habe ich online am &lt;a href=&quot;https://www.meetup.com/de-DE/laravel-meetup-germany/events/308227553/&quot;&gt;Laravel Meetup Germany&lt;/a&gt; teilgenommen. Es gab zwei Talks, der erste stellte &lt;a href=&quot;https://inertiajs.com/&quot;&gt;Inertia&lt;/a&gt; vor. Inertia ist ein Framework, das es vereinfacht, serverseitige (Laravel-)Apps mit clientseitigen &lt;abbr title=&quot;Single Page Application&quot;&gt;SPA&lt;/abbr&gt;s zu verbinden.&lt;/p&gt;

&lt;p&gt;Den zweiten Vortrag hielt &lt;a href=&quot;https://github.com/pkalusek&quot;&gt;Phillip Kalusek&lt;/a&gt; über &quot;Data Migrations - Strategies, Tips &amp;amp; Tricks&quot; - nicht zu verwechseln mit Datenbank-Schema-Migrationen - in Laravel (&lt;a href=&quot;https://youtu.be/H-_D4UT9BsU?si=sJ_ecp0tMuzSvavc&quot;&gt;Video auf YouTube&lt;/a&gt;. Phillip gibt eine Menge sehr sinnvoller Praxis-Tipps zum Aufsetzen von Import-Workflows und beleuchtet Vor- und Nachteile verschiedener Ansätze. Dieser Vortrag hat mich letztendlich dazu inspiriert, diesen Artikel zu schreiben.&lt;/p&gt;

&lt;h2 id=&quot;worum-geht-s-&quot;&gt;Worum geht&#039;s?&lt;/h2&gt;

&lt;p&gt;Die Ausgangssituation war, dass eine große Anzahl beliebig strukturierter Daten aus externen Quellen in eine Anwendung, also meist in die zugrunde liegende Datenbank importiert werden sollte. Datenquellen gibt es viele, und die Daten liegen dabei in unterschiedlichen Strukturen vor. Sie können aus anderen Datenbanken stammen oder über APIs abgerufen werden. Nicht ungewöhnlich sind auch JSON- oder CSV-Dateien, welche die einzelnen Datensätze zeilenweise enthalten.&lt;/p&gt;

&lt;p&gt;Um diese Daten importieren und weiterverarbeiten zu können, müssen sie in der Regel transformiert und normalisiert werden. Zum einen müssen sie häufig an eine existierende Datenstruktur angepasst werden, zum anderen müssen die Datentypen der Werte validiert und gegebenenfalls umgewandelt werden.&lt;/p&gt;

&lt;p&gt;Beispiele:
* Datumsformat: Ein Datum liegt als String im Format Y-M-D H:i:s vor, es soll aber ein Timestamp (mit Timezone) importiert werden;
* Geokoordinaten: Zwei Werte für Längen- und Breitengrad sollen zu einem &lt;code&gt;Point&lt;/code&gt;-Datentyp in PostgreSQL vereint werden;
* Namen: Die Schreibung von Namen soll angepasst werden;
* Die Daten sollen mit bereits vorhandenen Daten angereichert werden;
* Es sollen fehlerhafte Datensätze erkannt und entfernt werden;
* usw.&lt;/p&gt;

&lt;p&gt;Der Schwerpunkt des Talks lag auf einem PHP-basierten Import-Prozess, wie ich ihn auch schon häufig so oder so ähnlich implementiert habe.&lt;/p&gt;

&lt;p&gt;Ich möchte aber noch auf einige andere Optionen eingehen, die ich in letzter Zeit verwendet habe und die unter Umständen performanter mit größeren Datenmengen umgehen können oder möglicherweise einfacher und präziser umzusetzen sind.&lt;/p&gt;

&lt;h2 id=&quot;beispieldatensatz&quot;&gt;Beispieldatensatz&lt;/h2&gt;

&lt;p&gt;Auf der Suche nach Beispieldaten bin ich auf den Datensatz &lt;a href=&quot;https://www.kaggle.com/datasets/piterfm/football-fifa-womens-world-cup-1991-2023&quot;&gt;Football - Women&#039;s FIFA World Cup &amp;amp; UEFA EURO&lt;/a&gt; gestoßen, und weil ja gerade die Fußball-Europameisterschaft stattfindet, habe ich ihn mir genauer angeschaut. Die Daten eignen sich sehr gut zum Experimentieren, denn es sind tatsächlich Geokoordinaten von Fußballstadien, Uhrzeiten in unterschiedlichen Zeitzonen, Spieldaten im JSON-Format und mehr enthalten. Weil ich mich der Einfachheit halber auf Europameisterschaften und nur die wichtigsten Eigenschaften der Spiele konzentrieren möchte, habe ich die Daten etwas bereinigt. So sieht unsere Ausgangsdatenlage aus (&lt;a href=&quot;https://codeberg.org/mattsches/data-importer-example/src/branch/main/data/euros.csv&quot;&gt;Link zur CSV&lt;/a&gt;):&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;csv&quot;&gt;id_match,home_team_code,away_team_code,home_score_total,away_score_total,winner_reason,date_time,utc_offset_hours,round,match_attendance,stadium_capacity,stadium_city,stadium_latitude,stadium_longitude,goals
54735,NOR,SWE,2.0,1.0,WIN_REGULAR,1987-06-14 16:00:00+02,2.0,FINAL,8408.0,27184.0,Oslo,59.9490472,10.7342139,&quot;[{&quot;&quot;phase&quot;&quot;: &quot;&quot;FIRST_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 28, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Trude Stendal&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;Stendal&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;NOR&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;SCORED&quot;&quot;}, {&quot;&quot;phase&quot;&quot;: &quot;&quot;SECOND_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 72, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Trude Stendal&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;Stendal&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;NOR&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;SCORED&quot;&quot;}, {&quot;&quot;phase&quot;&quot;: &quot;&quot;SECOND_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 75, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Lena Videkull&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;SWE&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;SCORED&quot;&quot;}]&quot;
54734,ENG,ITA,1.0,2.0,WIN_REGULAR,1987-06-13 13:30:00+02,2.0,THIRD_PLAY_OFF,500.0,6545.0,Drammen,59.7344111,10.2013194,&quot;[{&quot;&quot;phase&quot;&quot;: &quot;&quot;FIRST_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 4, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Kerry Davis&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;Davis&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;ENG&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;PENALTY&quot;&quot;}, {&quot;&quot;phase&quot;&quot;: &quot;&quot;FIRST_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 37, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Carolina Morace&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;Morace&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;ITA&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;SCORED&quot;&quot;}, {&quot;&quot;phase&quot;&quot;: &quot;&quot;SECOND_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 50, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Elisabetta Vignotto&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;Vignotto&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;ITA&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;SCORED&quot;&quot;}]&quot;
54733,SWE,ENG,3.0,2.0,WIN_ON_EXTRA_TIME,1987-06-11 19:00:00+02,2.0,SEMIFINAL,300.0,2470.0,Moss,59.4207583,10.6710111,&quot;[{&quot;&quot;phase&quot;&quot;: &quot;&quot;FIRST_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 7, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Karin Åhman-Svensson&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;Ahman-Svensson&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;SWE&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;FORWARD&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;SCORED&quot;&quot;}, {&quot;&quot;phase&quot;&quot;: &quot;&quot;FIRST_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 31, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Marieanne Spacey&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;Spacey&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;ENG&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;SCORED&quot;&quot;}, {&quot;&quot;phase&quot;&quot;: &quot;&quot;FIRST_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 42, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Gillian Coultard&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;Coultard&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;ENG&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;SCORED&quot;&quot;}, {&quot;&quot;phase&quot;&quot;: &quot;&quot;SECOND_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 53, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Anette Börjesson&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;Börjesson&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;SWE&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;SCORED&quot;&quot;}, {&quot;&quot;phase&quot;&quot;: &quot;&quot;EXTRA_TIME_FIRST_HALF&quot;&quot;, &quot;&quot;time&quot;&quot;: {&quot;&quot;minute&quot;&quot;: 100, &quot;&quot;second&quot;&quot;: 0}, &quot;&quot;international_name&quot;&quot;: &quot;&quot;Karin Åhman-Svensson&quot;&quot;, &quot;&quot;club_shirt_name&quot;&quot;: &quot;&quot;Ahman-Svensson&quot;&quot;, &quot;&quot;country_code&quot;&quot;: &quot;&quot;SWE&quot;&quot;, &quot;&quot;national_field_position&quot;&quot;: &quot;&quot;FORWARD&quot;&quot;, &quot;&quot;national_jersey_number&quot;&quot;: null, &quot;&quot;goal_type&quot;&quot;: &quot;&quot;SCORED&quot;&quot;}]&quot;
…usw
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Um einen ersten Überblick über den Inhalt von CSV-Dateien zu erhalten, bieten sich Kommandozeilen-Tools wie &lt;a href=&quot;https://github.com/YS-L/csvlens&quot;&gt;csvlens&lt;/a&gt; oder Datenbank-Tools wie &lt;a href=&quot;https://sqlite.org/&quot;&gt;SQLite&lt;/a&gt; und &lt;a href=&quot;https://www.duckdb.org/&quot;&gt;DuckDB&lt;/a&gt; an, die mit entsprechenden Import-Funktionen aufwarten können. Mit deren Hilfe lässt sich oft auf einen Blick einschätzen, welche Daten und Datentypen in der Datei vorhanden sind.&lt;/p&gt;

&lt;h2 id=&quot;daten-import-in-php&quot;&gt;Daten-Import in PHP&lt;/h2&gt;

&lt;h3 id=&quot;-klassische-foreach-schleife&quot;&gt;&quot;Klassische&quot; &lt;code&gt;foreach&lt;/code&gt;-Schleife&lt;/h3&gt;

&lt;p&gt;Die im Vortrag genannte Methode implementiert einen &lt;code&gt;NormalizationManager&lt;/code&gt;, dem ein oder mehrere &quot;Normalizers&quot; übergeben werden. Die &quot;Normalizer&quot; kümmern sich dann nacheinander um die Bereinigung (Normalisierung, Transformierung) der Daten. Die einzelnen Datensätze wurden in einer Schleife nacheinander von den einzelnen &quot;Normalizern&quot; bearbeitet.&lt;/p&gt;

&lt;p&gt;In meinem Code-Beispiel habe ich die Klassen &lt;code&gt;TransformerManager&lt;/code&gt; und &lt;code&gt;Transformer&lt;/code&gt; genannt, sie erfüllen aber dieselbe Aufgabe wie die &lt;code&gt;Normalizer&lt;/code&gt; aus dem Vortrag:&lt;/p&gt;

&lt;div class=&quot;php geshi&quot; style=&quot;text-align: left&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;class&lt;/span&gt; TransformerManager&lt;br /&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;private&lt;/span&gt; &lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$transformers&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#91;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#93;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;function&lt;/span&gt; registerTransformer&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;string &lt;span style=&quot;color: #000088;&quot;&gt;$name&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;,&lt;/span&gt; TransformerInterface &lt;span style=&quot;color: #000088;&quot;&gt;$transformer&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;:&lt;/span&gt; void&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #000088;&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;transformers&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#91;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$name&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#93;&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$transformer&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;function&lt;/span&gt; transformDirectly&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;:&lt;/span&gt; &lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;foreach&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;transformers&lt;/span&gt; &lt;span style=&quot;color: #b1b100;&quot;&gt;as&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$transformer&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;foreach&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt; &lt;span style=&quot;color: #b1b100;&quot;&gt;as&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$transformer&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;transform&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;return&lt;/span&gt; &lt;a href=&quot;http://www.php.net/array_filter&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array_filter&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;class&lt;/span&gt; DateTimeTransformer &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;implements&lt;/span&gt; TransformerInterface&lt;br /&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;function&lt;/span&gt; transform&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;:&lt;/span&gt; &lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;!&lt;/span&gt;&lt;a href=&quot;http://www.php.net/isset&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;isset&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#91;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;&#039;date_time&#039;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#93;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;return&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#91;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;&#039;datetime&#039;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#93;&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&lt;/span&gt; DateTimeImmutable&lt;span style=&quot;color: #339933;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;createFromFormat&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;&#039;Y-m-d H:i:sP&#039;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#91;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;&#039;date_time&#039;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#93;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;new&lt;/span&gt; DateTimeZone&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;&#039;Europe/Berlin&#039;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;return&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160;&lt;/div&gt;

&lt;p&gt;Der &lt;code&gt;TransformerManager&lt;/code&gt; enthält also die einzelen &lt;code&gt;Transformer&lt;/code&gt; wie hier den &lt;code&gt;DateTimeTransformer&lt;/code&gt;. Der Methode &lt;code&gt;transformDirectly()&lt;/code&gt; wird der Datensatz als &lt;code&gt;array&lt;/code&gt; übergeben. Dort wird dann über die &lt;code&gt;Transformer&lt;/code&gt; iteriert (in unserem Fall ist es nur einer), und jede Zeile unseres Datensatzes wird separat in der &lt;code&gt;transform()&lt;/code&gt;-Methode des Transformers behandelt. Zu guter Letzt sorgt der Aufruf von &lt;code&gt;array_filter()&lt;/code&gt; noch dafür, dass leere Datensätze entfernt werden.&lt;/p&gt;

&lt;h3 id=&quot;laravel-pipeline-helper&quot;&gt;Laravel Pipeline Helper&lt;/h3&gt;

&lt;p&gt;Was da beschrieben wurde, ist im Prinzip ist eine simple Implementierung des &lt;code&gt;Pipeline Design Patterns&lt;/code&gt;. Daher bietet es sich im Kontext von Laravel meiner Meinung nach an, den &lt;a href=&quot;https://laravel.com/docs/12.x/helpers#pipeline&quot;&gt;Pipeline Helper&lt;/a&gt; (bzw. die &lt;code&gt;Pipeline Facade&lt;/code&gt;) von Laravel zu verwenden. Das könnte dann entsprechend so ausssehen:&lt;/p&gt;

&lt;div class=&quot;php geshi&quot; style=&quot;text-align: left&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;use&lt;/span&gt; Illuminate\Support\Facades\Pipeline&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;function&lt;/span&gt; transformLaravelPipeline&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;:&lt;/span&gt; &lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;foreach&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt; &lt;span style=&quot;color: #b1b100;&quot;&gt;as&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&lt;/span&gt; Pipeline&lt;span style=&quot;color: #339933;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;through&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;transformers&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;thenReturn&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;return&lt;/span&gt; &lt;a href=&quot;http://www.php.net/array_filter&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array_filter&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;use&lt;/span&gt; Closure&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;class&lt;/span&gt; DateTimeTransformer &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;implements&lt;/span&gt; TransformerInterface&lt;br /&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;function&lt;/span&gt; __invoke&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;,&lt;/span&gt; Closure &lt;span style=&quot;color: #000088;&quot;&gt;$next&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;:&lt;/span&gt; &lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;transform&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;return&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$next&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;function&lt;/span&gt; transform&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;:&lt;/span&gt; &lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;!&lt;/span&gt;&lt;a href=&quot;http://www.php.net/isset&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;isset&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#91;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;&#039;date_time&#039;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#93;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;return&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#91;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;&#039;datetime&#039;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#93;&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&lt;/span&gt; DateTimeImmutable&lt;span style=&quot;color: #339933;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;createFromFormat&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;&#039;Y-m-d H:i:sP&#039;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#91;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;&#039;date_time&#039;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#93;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;new&lt;/span&gt; DateTimeZone&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;&#039;Europe/Berlin&#039;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;return&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160;&lt;/div&gt;

&lt;p&gt;Statt des &lt;code&gt;foreach&lt;/code&gt;-Loops über die Transformatoren kümmert sich hier die &lt;code&gt;through()&lt;/code&gt;-Methode des Pipeline-Helpers darum, dass die Daten durch alle verfügbaren Transformatoren geschickt werden. Die Transformatoren werden durch eine &lt;code&gt;__invoke()&lt;/code&gt;-Methode ergänzt, durch die ein zusätzlicher Paramter &lt;code&gt;Closure $next&lt;/code&gt; quasi durchgeschleift wird; sie verweist dabei auf den nächsten Transformator.&lt;/p&gt;

&lt;p&gt;In einer Laravel-Applikation nutzt dieser Ansatz die mitgebrachten Funktionen des Frameworks eher aus, aber möglicherweise ist die &lt;code&gt;foreach()&lt;/code&gt;-Schleife in manchen Szenarien flexibler - das müsst ihr für euch selbst entscheiden &lt;img src=&quot;plugins/serendipity_event_emoticate/img/emoticons/wink.png&quot; alt=&quot;;-)&quot; class=&quot;emoticon&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;pipe-operator-in-php-8-5&quot;&gt;Pipe Operator in PHP 8.5&lt;/h3&gt;

&lt;p&gt;Es wird aber noch besser: In PHP 8.5 (erscheint Ende des Jahres 2025) bekommen wir sogar einen nativen &lt;a href=&quot;https://php.watch/versions/8.5/pipe-operator&quot;&gt;Pipe operator&lt;/a&gt;: &quot;&lt;code&gt;|&amp;gt;&lt;/code&gt;&quot; Wer sich schon einmal mit Programmiersprachen wie &lt;a href=&quot;https://hexdocs.pm/elixir/1.18.4/enumerable-and-streams.html#the-pipe-operator&quot;&gt;Elixir&lt;/a&gt; beschäftigt hat, kennt den &lt;em&gt;Pipe Operator&lt;/em&gt; wahrscheinlich schon, denn dort ist er ein zentrales Element der Sprache. Mit seiner Hilfe lässt sich eine ähnliche Pipeline realisieren, die voraussichtlich so oder ähnlich aussehen könnte:&lt;/p&gt;

&lt;div class=&quot;php geshi&quot; style=&quot;text-align: left&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #000000; font-weight: bold;&quot;&gt;function&lt;/span&gt; transformNativePipeline&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;:&lt;/span&gt; &lt;a href=&quot;http://www.php.net/array&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;foreach&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt; &lt;span style=&quot;color: #b1b100;&quot;&gt;as&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#123;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #339933;&quot;&gt;|&amp;gt;&lt;/span&gt; fn&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;getTransformer&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;RemoveFutureMatchesTransformer&lt;span style=&quot;color: #339933;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;NAME&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;transform&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #339933;&quot;&gt;|&amp;gt;&lt;/span&gt; fn&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;getTransformer&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;GeoTransformer&lt;span style=&quot;color: #339933;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;NAME&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;transform&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #339933;&quot;&gt;|&amp;gt;&lt;/span&gt; fn&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;getTransformer&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;DateTimeTransformer&lt;span style=&quot;color: #339933;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;NAME&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;transform&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &lt;span style=&quot;color: #339933;&quot;&gt;|&amp;gt;&lt;/span&gt; fn&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt; &lt;span style=&quot;color: #339933;&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #000088;&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;getTransformer&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;CleanTransformer&lt;span style=&quot;color: #339933;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;NAME&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #004000;&quot;&gt;transform&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$d&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160; &amp;#160; &lt;span style=&quot;color: #b1b100;&quot;&gt;return&lt;/span&gt; &lt;a href=&quot;http://www.php.net/array_filter&quot;&gt;&lt;span style=&quot;color: #990000;&quot;&gt;array_filter&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#40;&lt;/span&gt;&lt;span style=&quot;color: #000088;&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#41;&lt;/span&gt;&lt;span style=&quot;color: #339933;&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #009900;&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br /&gt;&amp;#160;&lt;/div&gt;

&lt;p&gt;Jedes einzelne Daten-Array wird also einmal der Reihe nach durch die Transformatoren geschickt und entsprechend angepasst. Ich habe das Code-Beispiel bereits mit der ganz neuen PHP-Version &lt;code&gt;8.5.0alpha1&lt;/code&gt; getestet und - es funktioniert! 🎆&lt;/p&gt;

&lt;p&gt;Alle Code-Beispiele und ein &lt;code&gt;Dockerfile&lt;/code&gt; für einen Container mit PHP 8.5 habe ich &lt;a href=&quot;https://codeberg.org/mattsches/data-importer-example&quot;&gt;in einem Repository&lt;/a&gt; bereitgestellt.&lt;/p&gt;

&lt;h2 id=&quot;weitere-berlegungen&quot;&gt;Weitere Überlegungen&lt;/h2&gt;

&lt;p&gt;Es ist natürlich auch möglich, das komplette &lt;code&gt;$data&lt;/code&gt;-Array an die einzelnen Transformer zu übergeben und erst dort über die enthaltenen Spiele-Arrays zu iterieren. Aber auch das muss im Einzefall entschieden werden. Mir ist es wahrscheinlich lieber, auf mögliche Fehler bei der Bearbeitung eines einzelnen Datensatzes reagieren und die Pipeline dann entsprechend fortsetzen oder abbrechen zu können.&lt;/p&gt;

&lt;p&gt;Sollte nämlich während der Verarbeitung ein Fehler auftreten, muss dieser abgefangen werden. Entweder die komplette Verarbeitung stoppt, oder der fehlerbehaftete Datensatz wird übersprungen. Sinnvoll ist im Zusammenhang mit dem Datenbank-Insert auf jeden Fall, die Änderungen innerhalb einer Transaktion zusammenzufassen und, wenn nötig, einen &lt;code&gt;Rollback&lt;/code&gt; durchzuführen.&lt;/p&gt;

&lt;p&gt;Vor allem wenn die externen Daten &lt;em&gt;nicht&lt;/em&gt; aus einer Datenbank, sondern aus einer anderen Quelle stammen, ist es wahrscheinlich sinnvoll, sie in einer (versionierbaren) Datenbank zwischenzuspeichern. Die oben bereits erwähnten Syteme SQLite und DuckDB bieten sich hierfür ebenfalls an. Dabei genügt es unter Umständen, nur die nötigsten Transformationen durchzuführen. Die intermediäre Datenbank wird im Anschluss ihrerseits zur Datenquelle, und die Daten können dann mit allen Transformationen in die finale Datenbank überführt werden. Die dateibasierten Datenbankformate von SQLite und DuckDB eignen sich auch gut für die Versionierung, so dass etwaige Änderungen nachvollzogen werden können.&lt;/p&gt;

&lt;p&gt;Stammen die externen Daten aus einer Datenbank, erlauben Frameworks wie Laravel, zwei Datenbank-Verbindungen zu konfigurieren, und die Daten direkt zu transformieren und zu kopieren. Das muss auch gar nicht unbedingt in der &quot;großen Hauptanwendung&quot; geschehen, oft reicht es, ein kleines, aber spezialisiertes Konsolen-Tool mittels Laravel Zero o.ä. zu erstellen, das sich allein um den Daten-Import kümmert.&lt;/p&gt;

&lt;h3 id=&quot;dtos&quot;&gt;DTOs&lt;/h3&gt;

&lt;p&gt;Ein anderer möglicher Ansatz basiert auf Data Transfer Objects (DTOs). Dabei werden Ziel-Objekte mit typisierten Eigenschaften definiert. Die Quell-Daten werden dann mithilfe von Bibliotheken wie &lt;a href=&quot;https://spatie.be/docs/laravel-data/v4/introduction&quot;&gt;Laravel Data&lt;/a&gt; oder &lt;a href=&quot;https://github.com/CuyZ/Valinor&quot;&gt;Valinor&lt;/a&gt; auf die so definierten Objekte &lt;em&gt;gemappt&lt;/em&gt;. Diese können dann ihrerseits in einer Datenbank gespeichert werden.&lt;/p&gt;

&lt;h3 id=&quot;ausblick-es-muss-nicht-immer-php-sein&quot;&gt;Ausblick: Es muss nicht immer PHP sein&lt;/h3&gt;

&lt;p&gt;Es muss aber gar nicht unbedingt Laravel oder PHP sein - beispielsweise bietet Python hervorragende Tools zur Datentransformation, die ich vielleicht in einem späteren Artikel noch vorstelle. Genannt seien an dieser Stelle besonders  &lt;a href=&quot;https://github.com/pydantic/pydantic&quot;&gt;pydantic&lt;/a&gt;, &lt;a href=&quot;https://pandas.pydata.org/&quot;&gt;Pandas&lt;/a&gt; und &lt;a href=&quot;https://www.getdbt.com/&quot;&gt;dbt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Als weitere Möglichkeit, eine Pipeline zur Datentransformation zu realisieren, möchte ich außerdem wirklich gern einen genaueren Blick auf &lt;a href=&quot;https://duckdb.org/&quot;&gt;DuckDB&lt;/a&gt; werfen. Auch wenn Datenmigration nicht der eigentliche Einsatzzweck von DuckDB ist, lassen sich aufgrund der Flexibilität und Erweiterbarkeit des Tools schnell gute Ergebnisse erzielen.&lt;/p&gt;

&lt;p&gt;Für kontinuierliche Daten-Synchronisation, Daten-Replikation oder die Echtzeit-Aktualisierung eines Data Warehouses wiederum gibt es dedizierte Anwendungen, deren Fähigkeiten deutlich über die gezeigten Ansätze hinausgehen, die dafür aber auch aufwändiger aufzusetzen und zu konfigurieren sind.&lt;/p&gt;

&lt;h2 id=&quot;fazit&quot;&gt;Fazit&lt;/h2&gt;

&lt;p&gt;Web-Entwickler*innen mit PHP-/SQL-Kenntnissen können mithilfe der beschriebenen Patterns und Implementierungen Daten schnell und präzise zwischen verschiedenen Systemen migrieren. Mit der nächsten PHP-Version 8.5 wird der &lt;code&gt;Pipeline operator&lt;/code&gt; kommen, der solche Abläufe noch lesbarer und einfacher machen kann.&lt;/p&gt;
 
    </content:encoded>

    <pubDate>Mon, 07 Jul 2025 10:39:00 +0000</pubDate>
    <guid isPermaLink="false">content/1000553-guid.html</guid>
    <category>data pipelines</category>
<category>duckdb</category>
<category>laravel</category>
<category>migration</category>
<category>php</category>
<category>php8.5</category>
<category>sqlite</category>
<creativeCommons:license>http://creativecommons.org/licenses/by/4.0/deed.de</creativeCommons:license>
</item>
<item>
    <title>Wochenrückblick KW 27 / 2024</title>
    <link>content/1000552-Wochenrueckblick-KW-27-2024.html</link>
            <category>Deutsche Beiträge</category>
    
    <comments>content/1000552-Wochenrueckblick-KW-27-2024.html#comments</comments>
    <wfw:comment>wfwcomment.php?cid=1000552</wfw:comment>

    <slash:comments>0</slash:comments>
    <wfw:commentRss>rss.php?version=2.0&amp;type=comments&amp;cid=1000552</wfw:commentRss>
    

    <author>nospam@example.com (Matthias Gutjahr)</author>
    <content:encoded>
    &lt;p&gt;Es wird immer später, aber ich habe es noch geschafft, den Wochenrückblick zu schreiben. Es war eine sehr volle Woche, in der ich viel erlebt habe. Hier sind die Highlights:&lt;/p&gt;

&lt;h2 id=&quot;-feiern&quot;&gt;🎉 Feiern&lt;/h2&gt;

&lt;h3 id=&quot;geburtstag&quot;&gt;Geburtstag&lt;/h3&gt;

&lt;p&gt;Am Samstag waren wir auf einer sehr schönen Geburtstagsfeier mit vielen alten Freunden, die ich zum Teil seit Jahren nicht gesehen hatte. Die Party-Location auf dem Mainzer Berg war auch super mit ausgebauter Scheune, einem geschützten Innenhof und viel Natur, die vor allem von den Kindern ausgiebig erforscht wurde.&lt;/p&gt;

&lt;h2 id=&quot;-fahrrad&quot;&gt;🚴 Fahrrad&lt;/h2&gt;

&lt;h3 id=&quot;nibelungen-gravel-ride&quot;&gt;Nibelungen Gravel Ride&lt;/h3&gt;

&lt;p&gt;Aus diesem Grund habe ich schweren Herzens nicht am diesjährigen &lt;a href=&quot;https://www.pitpat-freizeitanlage.de/nibelungengravelride&quot;&gt;Nibelungen Gravel Ride&lt;/a&gt; teilgenommen. Ich habe aber mittlerweile schon viele tolle Fotos von der Veranstaltung gesehen und werde die Strecke vielleicht noch solo an einem anderen Wochenende in Angriff nehmen. teilgenommen.&lt;/p&gt;

&lt;h3 id=&quot;tour-de-france&quot;&gt;Tour de France&lt;/h3&gt;

&lt;p&gt;Andere betreiben das Radfahren beruflich, zum Teil bei der Tour de France. Am Sonntag stand dort die Etappe mit 14 Gravel-Passagen an, die neben sehr coolen Bildern auch einiges an Spannung lieferte. Wenn er nicht stürzt oder krank wird, dürfte Tadej Pogacar die Rundfahrt gewinnen, so dominant war wieder seine Leistung.&lt;/p&gt;

&lt;h3 id=&quot;-computer&quot;&gt;🖥️ Computer&lt;/h3&gt;

&lt;h3 id=&quot;zed&quot;&gt;Zed&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://zed.dev/linux&quot;&gt;Zed&lt;/a&gt; ist ein Open Source-Texteditor &lt;em&gt;slash&lt;/em&gt; IDE, der jetzt auch auf Linux-Systemen läuft und GitHub Copilot-Unterstützung direkt eingebaut hat. Soweit ich sehe, lässt sich dieses Feature auch nicht einfach deaktivieren; aber ich denke, die AI-Unterstützung in IDEs geht so schnell auch nicht weg. Ich habe mir Zed mal installiert und werde ihn in den nächsten Tagen testen. Dieser Post ist auch in Zed entstanden.&lt;/p&gt;
 
    </content:encoded>

    <pubDate>Fri, 12 Jul 2024 07:07:00 +0000</pubDate>
    <guid isPermaLink="false">content/1000552-guid.html</guid>
    <category>gravel bike</category>
<category>wochenrückblick</category>
<category>zed</category>
<creativeCommons:license>http://creativecommons.org/licenses/by/4.0/deed.de</creativeCommons:license>
</item>
<item>
    <title>Wochenrückblick KW 26 / 2024</title>
    <link>content/1000551-Wochenrueckblick-KW-26-2024.html</link>
            <category>Deutsche Beiträge</category>
    
    <comments>content/1000551-Wochenrueckblick-KW-26-2024.html#comments</comments>
    <wfw:comment>wfwcomment.php?cid=1000551</wfw:comment>

    <slash:comments>0</slash:comments>
    <wfw:commentRss>rss.php?version=2.0&amp;type=comments&amp;cid=1000551</wfw:commentRss>
    

    <author>nospam@example.com (Matthias Gutjahr)</author>
    <content:encoded>
    &lt;blockquote&gt;
  &lt;p&gt;What a week, huh?
  Captain, it&#039;s Wednesday!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;-computer&quot;&gt;🖥️ Computer&lt;/h3&gt;

&lt;h4 id=&quot;terminal-trove&quot;&gt;Terminal Trove&lt;/h4&gt;

&lt;p&gt;Auch wenn ich nicht hauptsächlich Server administriere, so bin ich doch viel auf der Linux &lt;em&gt;command line&lt;/em&gt; unterwegs. Dementsprechend freue ich mich immer wieder über neue Tools fürs Terminal. Mein Eindruck ist, dass es auch die zunehmende Popularität von Rust ist, die dafür sorgt, dass auch heute noch neue, nützliche Kommandozeilen-Programme erstellt werden. Seiten wie &lt;a href=&quot;https://terminaltrove.com/&quot;&gt;Terminal Trove&lt;/a&gt; stellen diese dann vor. Ich habe zuletzt gefunden und ausprobiert:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tconbeer/harlequin&quot;&gt;harlequin&lt;/a&gt;, eine SQL IDE fürs Terminal, die auch gut mit DuckDB und SQLite funktioniert; macht das Arbeiten mit DuckDB je nach Einsatzzweck noch komfortabler.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://termscp.veeso.dev/#get-started&quot;&gt;termscp&lt;/a&gt;, ein Dateitransfer-Tool fürs Terminal, ein bisschen ähnlich dem Midnight Commander; spricht auch mit WebDAV, S3, SMB und mehr.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/brittonhayes/pillager&quot;&gt;Pillager&lt;/a&gt;, ein in Go geschriebenes Tool, um Verzeichnisse rekursiv nach (vergessenen) Passwörtern und Keys zu durchsuchen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-haus-garten&quot;&gt;🏡 Haus &amp;amp; Garten&lt;/h3&gt;

&lt;p&gt;Ich habe ein bisschen was mit Holz gemacht, Terrassendielen mit der Japansäge gekürzt und grau lasiert, die Kreissäge konnte im Regal bleiben. Zuvor habe ich mich mithilfe einiger Video-Tutorials weiter in FreeCAD reingefuchst und das Modell im &quot;Parts&quot;-Arbeitsbereich erstellt. Wenn man mal weiß, wie es geht, geht es ganz gut. Blöd nur, wenn der Baumarkt das entsprechende Holz dann doch nicht vorrätig hat. Aber die Maße im Kopf schnell neu zu kalkulieren hat glücklicherweise gut funktioniert 😅&lt;/p&gt;

&lt;h3 id=&quot;-literatur&quot;&gt;📚 Literatur&lt;/h3&gt;

&lt;p&gt;Ich habe &lt;a href=&quot;https://www.luebbe.de/babel/&quot;&gt;&quot;Babel&quot;&lt;/a&gt; von Rebecca F. Kuang in der deutschen Übersetzung von Heide Franck und Alexandra Jordan gelesen und fand es sehr gut. Die Wikipedia hat den deutlich besseren Originaltitel &quot;Babel: Or the Necessity of Violence: An Arcane History of the Oxford Translators&#039; Revolution&quot; und eine gute Zusammenfassung des Inhalts:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Babel is set in an alternative-reality 1830s England in which Britain&#039;s global economic and colonial supremacy are fueled by the use of magical silver bars. Their power comes from capturing what is &quot;lost in translation&quot; between words in different languages that have similar, but not identical, meanings. Silver bars inscribed with such &#039;match-pairs&#039; can increase industrial and agricultural production, improve the accuracy of bullets, heal injuries, and more. To harness this power, Oxford University created the Royal Institute of Translation, nicknamed &quot;Babel&quot;, where scholars work to find match-pairs. The plot is focused on four new students at the institute, their growing awareness that their academic efforts maintain Britain&#039;s imperialist supremacy, their debate over how to prevent the Opium War, and the use of violence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Es geht um Imperialismus, Kapitalismus, Kolonialismus und Rassismus, aber das wird erst langsam im Lauf der Handlung klar und immer deutlicher. Diese spielt zwar vor zwei Jahrhunderten, ist aber in vielen Teilen immer noch aktuell. Und auch der (satirische) Blick auf den akademischen Betrieb ist lesenswert. Insgesamt eine Empfehlung, wenn euch die Themenfelder ansprechen und euch &lt;em&gt;speculative fiction&lt;/em&gt; gefällt.&lt;/p&gt;

&lt;h3 id=&quot;-board-games&quot;&gt;🎲 Board Games&lt;/h3&gt;

&lt;p&gt;Wir haben &lt;a href=&quot;https://pegasusshop.de/sortiment/spiele/familienspiele/11337/micromacro-crime-city-edition-spielwiese-spiel-des-jahres-2021&quot;&gt;MicroMacro: Crime City&lt;/a&gt; ausprobiert, eine Art Detektivspiel in Wimmelbild-Optik, und es hat uns viel Spaß gemacht. Die etwas heftigeren Fälle haben wir aber aus Rücksicht auf das Alter der Teilnehmenden weggelassen - die holen wir irgendwann nach. Ein Fall lässt sich auch &lt;a href=&quot;https://www.micromacro-game.com/de/index.html&quot;&gt;online&lt;/a&gt; lösen, die Spielmechanik lässt sich dort ganz gut kennenlernen.&lt;/p&gt;
 
    </content:encoded>

    <pubDate>Wed, 03 Jul 2024 12:19:00 +0000</pubDate>
    <guid isPermaLink="false">content/1000551-guid.html</guid>
    <category>wochenrückblick</category>
<creativeCommons:license>http://creativecommons.org/licenses/by/4.0/deed.de</creativeCommons:license>
</item>
<item>
    <title>Wochenrückblick KW 25 / 2024</title>
    <link>content/1000550-Wochenrueckblick-KW-25-2024.html</link>
            <category>Deutsche Beiträge</category>
    
    <comments>content/1000550-Wochenrueckblick-KW-25-2024.html#comments</comments>
    <wfw:comment>wfwcomment.php?cid=1000550</wfw:comment>

    <slash:comments>0</slash:comments>
    <wfw:commentRss>rss.php?version=2.0&amp;type=comments&amp;cid=1000550</wfw:commentRss>
    

    <author>nospam@example.com (Matthias Gutjahr)</author>
    <content:encoded>
    &lt;p&gt;Der Wochenrückblick kommt spät, aber er kommt, wenn auch nur in aller Kürze. Dabei war die Woche gar nicht langweilig, aber vieles passt mir hier gerade nicht rein.&lt;/p&gt;

&lt;h3 id=&quot;-computer&quot;&gt;🖥️ Computer&lt;/h3&gt;

&lt;h4 id=&quot;php-user-group-rheinhessen&quot;&gt;PHP User Group Rheinhessen&lt;/h4&gt;

&lt;p&gt;Der zweite Talk für unser &lt;a href=&quot;https://www.meetup.com/de-DE/php-user-group-rheinhessen/events/300487970/&quot;&gt;74. Meetup&lt;/a&gt; steht fest: Bastian Allgeier wird das Open Source CMS &lt;a href=&quot;Kirby&quot;&gt;Kirby&lt;/a&gt; vorstellen. Ich habe in letzter Zeit häufiger Seiten mit Kirby erstellt und finde es aus Entwickler-Sicht sehr angenehm, damit zu arbeiten - auch wenn es ein paar Konventionen gibt, mit denen ich noch nicht ganz warm werde.&lt;/p&gt;

&lt;h3 id=&quot;-kochen-backen&quot;&gt;🧑‍🍳 Kochen &amp;amp; Backen&lt;/h3&gt;

&lt;p&gt;Ich lese gerade &lt;a href=&quot;https://at-verlag.ch/buch/978-3-03800-075-4/chad-robertson-das-brot.html&quot;&gt;&quot;Das Brot&quot;&lt;/a&gt; von Chad Robertson, das für manche eine Art Kult-Buch in Sachen Brotbacken ist. Ich finde es inhaltlich zu dünn, aber habe das Sauerteigbrot-Rezept im gusseisernen Topf nachgebacken. Es ist gut geworden, obwohl ich mich nicht besonders genau ans Rezept gehalten habe.&lt;/p&gt;

&lt;h3 id=&quot;-sport&quot;&gt;🚴 Sport&lt;/h3&gt;

&lt;p&gt;Am Samstagabend haben wir die zweite &lt;a href=&quot;https://www.dtb.de/olympiaquali&quot;&gt;Olympia-Qualifikation im Gerätturnen der Frauen&lt;/a&gt; in Rüsselsheim besucht. Fast die gesamte deutsche Turn-Elite war vertreten, und am Ende hat &lt;a href=&quot;https://de.wikipedia.org/wiki/Helen_Kevric&quot;&gt;Helen Kevric&lt;/a&gt; verdientermaßen den dritten Startplatz für die Olympischen Spiele in Paris erkämpft. Sie hat seit gestern übrigens endlich einen Wikipedia-Eintrag, ansonsten hätte ich den jetzt angelegt.&lt;/p&gt;
 
    </content:encoded>

    <pubDate>Mon, 24 Jun 2024 20:44:00 +0000</pubDate>
    <guid isPermaLink="false">content/1000550-guid.html</guid>
    <category>wochenrückblick</category>
<creativeCommons:license>http://creativecommons.org/licenses/by/4.0/deed.de</creativeCommons:license>
</item>
<item>
    <title>Wochenrückblick KW 24 / 2024</title>
    <link>content/1000549-Wochenrueckblick-KW-24-2024.html</link>
            <category>Deutsche Beiträge</category>
    
    <comments>content/1000549-Wochenrueckblick-KW-24-2024.html#comments</comments>
    <wfw:comment>wfwcomment.php?cid=1000549</wfw:comment>

    <slash:comments>1</slash:comments>
    <wfw:commentRss>rss.php?version=2.0&amp;type=comments&amp;cid=1000549</wfw:commentRss>
    

    <author>nospam@example.com (Matthias Gutjahr)</author>
    <content:encoded>
    &lt;p&gt;In dieser Woche habe ich eine weitere Runde der Erde &lt;a href=&quot;https://songwhip.com/galliano/circles-going-round-the-sun&quot;&gt;um die Sonne&lt;/a&gt;¹ abgeschlossen. Abends gab es daher lecker Essen im &lt;a href=&quot;https://www.living-dining.de/&quot;&gt;Living &amp;amp; Dining&lt;/a&gt;, am Wochenende einen Besuch bei den Eltern. Entsprechend kurz bleibt der Rückblick.&lt;/p&gt;

&lt;h3 id=&quot;-computer&quot;&gt;🖥️ Computer&lt;/h3&gt;

&lt;h4 id=&quot;upscayle&quot;&gt;Upscayle&lt;/h4&gt;

&lt;p&gt;Für einen anderen, runden Geburtstag, zu dem ich in ein paar Wochen eingeladen bin, habe ich knietief in meinen Backups nach alten Fotos gesucht und trotz mangelnder Organisation einige interessante Bilder gefunden. Darunter auch Scans von Papierabzügen und Dias(!), fotografiert mit sehr einfachem Equipment. Um die zumindest teilweise aufzubessern, habe ich das Upscaling mit &lt;a href=&quot;https://github.com/upscayl/upscayl&quot;&gt;Upscayle&lt;/a&gt; ausprobiert und muss sagen: Das Ergebnis überzeugt. Nicht bei allen Vorlagen, aber es lohnt sich, das Tool mit kleineren Fotos zu testen.&lt;/p&gt;

&lt;h3 id=&quot;-fahrrad&quot;&gt;🚴 Fahrrad&lt;/h3&gt;

&lt;p&gt;Es gibt leider nicht viel zu berichten. Ich habe neue Bremsbeläge aufs Gravelbike getan, diesmal die &lt;a href=&quot;https://www.tektro.com/index.php/en/product/137&quot;&gt;Tektro P20.11&lt;/a&gt;, in der Hoffnung, dass die etwas länger halten. Geht ja doch viel hoch und runter hier im Taunus. Eingebremst habe ich sie schon, eine Testfahrt steht allerdings noch aus.&lt;/p&gt;

&lt;p&gt;Dafür habe ich am Sonntag wieder etwas &lt;a href=&quot;https://dotwatcher.cc/race/taunus-bikepacking-2024&quot;&gt;Dotwatching&lt;/a&gt; betrieben, auf der &lt;a href=&quot;https://www.followmychallenge.com/live/taunusbikepacking/&quot;&gt;Map&lt;/a&gt; und auch aus dem Dachfenster heraus. Das &lt;a href=&quot;https://taunus-bikepacking.com/&quot;&gt;Taunus Bikepacking&lt;/a&gt; ist nämlich gestartet (wenn ich groß bin, möchte ich daran auch mal teilnehmen!), und ich kenne zunehmenend Menschen, die da mitfahren. Entweder persönlich oder zumindest dem Namen nach oder von ihren Social Media-Kanälen. Letztes Jahr bei hochsommerlichen Temperaturen stand ich da im Wald und habe einige Teilnehmende mit Wasser und Snacks unterstützen können. Diesmal war ich nicht so gut vorbereitet, habe aber die Punkte auf der Karte und ein paar Fahrende aus der Ferne verfolgen können.&lt;/p&gt;

&lt;h3 id=&quot;-musik&quot;&gt;🎶 Musik&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.sperrobjekt.de/content/1000548-Wochenrueckblick-KW-23-2024.html&quot;&gt;Letzte Woche&lt;/a&gt; habe ich mich über das Verschwinden der Spotify-Playlisten beschwert, andere (Radiosender) hatten sich wohl über deren Existenz beschwert, weil sie ja nicht offiziell sind. Die gute Nachricht ist: Die Playlisten sind wieder da, haben jetzt halt nur andere, etwas kryptische Namen 🙌&lt;/p&gt;

&lt;p&gt;In dem Zuge habe ich mich für ein paar Stunden hingesetzt und mein eigenes Tool programmiert, das gespielte Songs eines Radiosenders einliest, den Titel bei Spotify sucht, und dann in eine Playlist einfügt. Das war gar nicht so schwierig und funktioniert bereits als &lt;em&gt;proof of concept&lt;/em&gt;. Eine große Hilfe waren die Bibliotheken &lt;a href=&quot;https://symfony.com/doc/current/components/browser_kit.html&quot;&gt;symfony/browser-kit&lt;/a&gt; einer- und &lt;a href=&quot;https://github.com/jwilsson/spotify-web-api-php&quot;&gt;jwilsson/spotify-web-api-php&lt;/a&gt; andererseits. Eigentlich müsste ich das jetzt gar nicht mehr weiterentwickeln, aber ich habe noch ein paar andere Ideen dafür. Mal sehen, ob und was daraus wird.&lt;/p&gt;

&lt;p&gt;¹ Galliano &lt;a href=&quot;https://brownswoodrecordings.com/news/2024/5/20/halfway-somewhere-the-return-of-galliano&quot;&gt;sind wieder da&lt;/a&gt;&lt;/p&gt;
 
    </content:encoded>

    <pubDate>Mon, 17 Jun 2024 09:21:00 +0000</pubDate>
    <guid isPermaLink="false">content/1000549-guid.html</guid>
    <category>wochenrückblick</category>
<creativeCommons:license>http://creativecommons.org/licenses/by/4.0/deed.de</creativeCommons:license>
</item>
<item>
    <title>Wochenrückblick KW 23 / 2024</title>
    <link>content/1000548-Wochenrueckblick-KW-23-2024.html</link>
            <category>Deutsche Beiträge</category>
    
    <comments>content/1000548-Wochenrueckblick-KW-23-2024.html#comments</comments>
    <wfw:comment>wfwcomment.php?cid=1000548</wfw:comment>

    <slash:comments>0</slash:comments>
    <wfw:commentRss>rss.php?version=2.0&amp;type=comments&amp;cid=1000548</wfw:commentRss>
    

    <author>nospam@example.com (Matthias Gutjahr)</author>
    <content:encoded>
    &lt;p&gt;Diese Woche war viel Arbeit und wenig Zeit für anderes, aber ein paar Themen gibt es dennoch zu verbloggen.&lt;/p&gt;

&lt;h3 id=&quot;-computer&quot;&gt;🖥️ Computer&lt;/h3&gt;

&lt;h4 id=&quot;php-user-group-rheinhessen&quot;&gt;PHP User Group Rheinhessen&lt;/h4&gt;

&lt;p&gt;Für den 10. Juli ist unser &lt;a href=&quot;https://www.meetup.com/de-DE/php-user-group-rheinhessen/events/300487970/&quot;&gt;74. Meetup&lt;/a&gt; geplant. Es wird diesmal wieder bei der UDG Rhein-Main GmbH im Mainzer Office am Zollhafen stattfinden, ein erster Talk ist auch schon öffentlich: &lt;strong&gt;&quot;State of TYPO3&quot;&lt;/strong&gt; von Anja Leichsenring und Mathias Reinhardt. Der zweite Talk wird demnächst bekanntgegeben, er wird sich ebenfalls um ein CMS drehen, soviel sei schon verraten.&lt;/p&gt;

&lt;h4 id=&quot;csvbase&quot;&gt;csvbase&lt;/h4&gt;

&lt;p&gt;Über meine Beschäftigung mit DuckDB bin ich auf &lt;a href=&quot;https://csvbase.com/&quot;&gt;csvbase&lt;/a&gt; aufmerksam geworden, die Selbstbeschreibung ist kurz und knapp:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;csvbase is a simple web database.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tabellarische Daten lassen sich sehr einfach hier hinterlegen, im Browser und vor allem über ein API abrufen und bearbeiten. Das soll auch gut mit Tools wie pandas, DuckDB oder einfach mit HTTP-Requests funktionieren. Coole Idee, simples API, und alles Open Source. Es lassen sich auch Daten aus GitHub importieren, ich habe das mal mit dem kleinen Dataset &lt;a href=&quot;https://csvbase.com/mattsches/equalstreetnames-wiesbaden&quot;&gt;&lt;code&gt;data.csv&lt;/code&gt;&lt;/a&gt; aus dem &lt;a href=&quot;https://github.com/EqualStreetNames/equalstreetnames-wiesbaden&quot;&gt;EqualStreetNames Wiesbaden&lt;/a&gt;-Projekt getestet. Schick!&lt;/p&gt;

&lt;h3 id=&quot;-haus-garten&quot;&gt;🏡 Haus &amp;amp; Garten&lt;/h3&gt;

&lt;p&gt;Ende April habe ich nach vielen Jahren Pause wieder einmal Bier gebraut, ein sommerliches Pale Ale, mit Mosaic gehopft. Es war ein relativ entspannter Brautag, und mit der neuen Kühlschlange aus Edelstahl war die Würze erstaunlich schnell auf Gärtemperatur heruntergekühlt. So ein Teil hätte ich mir schon viel früher gönnen sollen. Wie dem auch sei, das Bier ist mittlerweile trinkbar und: sogar richtig gut geworden. Auch die Nachbarn sind nach einer netten Bierprobe recht angetan 🍻&lt;/p&gt;

&lt;h3 id=&quot;-fahrrad&quot;&gt;🚴 Fahrrad&lt;/h3&gt;

&lt;p&gt;Am Samstag habe ich jemanden, den ich bisher nur von Mastodon kannte, in Niedernhausen abgeholt, und wir sind eine feine Runde über Idstein, Wallrabenstein und quer durch den Golfpark Idstein und wieder zurück gefahren. Meistens fahre ich ja allein, aber das war eine schöne Abwechslung. Wir haben uns viel unterhalten und auch seine kleine Tubeless-Panne beheben können - der &lt;a href=&quot;https://www.openstreetmap.org/node/10210318593&quot;&gt;Fahrrad-Werkzeug-Station&lt;/a&gt; in Idstein sei Dank.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.laelwilcox.net/&quot;&gt;Lael Wilcox&lt;/a&gt; fährt gerade in einem &lt;a href=&quot;https://www.laelwilcox.net/around-the-world&quot;&gt;Weltrekordversuch&lt;/a&gt; mit dem Fahrrad &lt;a href=&quot;https://www.followmychallenge.com/live/lael/rtw/?lat=49.532615&amp;amp;lng=5.299027&amp;amp;z=6.08&quot;&gt;&quot;um die Welt&quot;&lt;/a&gt; und wird voraussichtlich in ein paar Tagen in der Nähe von Bingen vorbeikommen. Winkt ihr mal und feuert sie an, falls ihr sie seht.&lt;/p&gt;

&lt;h3 id=&quot;-literatur&quot;&gt;📚 Literatur&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://pdjeliclark.com/&quot;&gt;P. Djèlí Clark&lt;/a&gt; schreibt Bücher (und auch ein &lt;a href=&quot;https://disgruntledharadrim.com/&quot;&gt;Blog&lt;/a&gt;), die in einer alternativen Steampunk-Welt angesiedelt sind, in der Magie in Form sämtlicher arabischer/islamischer Geistwesen Einzug existiert. &lt;em&gt;A Dead Djinn In Cairo&lt;/em&gt;, &lt;em&gt;A Master Of Djinn&lt;/em&gt; und &lt;em&gt;The Haunting of Tram Car 015&lt;/em&gt;, welches ich jetzt gelesen habe, spielt im Kairo des frühen 20. Jahrhunderts, dem kulturellen und technologischen Zentrum seiner Zeit. Über den Dächern der Stadt reisen die Menschen in kabelgeführten Straßenbahnen, die von magischen Kräften angetrieben werden. Die Novelle ist relativ kurz, daher solltet ihr bei Gefallen im Anschluss &lt;em&gt;A Master Of Djinn&lt;/em&gt; lesen, das bisherige Hauptwerk in diesem Universum.&lt;/p&gt;

&lt;p&gt;Meinen Lesefortschritt tracke ich übrigens auf &lt;a href=&quot;https://lesetagebu.ch/von/mattsches&quot;&gt;Lesetagebuch&lt;/a&gt;. Die nicht-kommerzielle Plattform kann auch einen passenden &lt;a href=&quot;https://phpc.social/@mattsches/112418991368767143&quot;&gt;Toot&lt;/a&gt; absetzen, wenn ich z. B. ein Buch als gelesen eintrage. Man kann auch Rezensionen verfassen, anderen Nutzern folgen, und erhält einen schönen Überblick über das eigenen Bücherjahr.&lt;/p&gt;

&lt;h3 id=&quot;-musik&quot;&gt;🎶 Musik&lt;/h3&gt;

&lt;p&gt;Meshell Ndegeocello setzt sich auf ihrem kommenden Album mit &lt;a href=&quot;https://de.wikipedia.org/wiki/James_Baldwin&quot;&gt;James Baldwin&lt;/a&gt; auseinander und hat schon einmal zwei Stücke vorab veröffentlicht. Das eine, &lt;em&gt;Raise The Roof&lt;/em&gt;, ist eigentlich eine bemerkenswerte Spoken Word-Aufnahme von &lt;a href=&quot;https://en.wikipedia.org/wiki/Staceyann_Chin&quot;&gt;Staceyann Chin&lt;/a&gt;. Ich empfehle aber diese, wie ich finde &lt;a href=&quot;https://soundcloud.com/elleepiphany/raise-the-roof-by-staceyann-chin&quot;&gt;noch beeinddruckendere Aufnahme&lt;/a&gt; von ihr mit Robert Glasper, Derrick Hodge und der Revive Big Band for Afropunk.&lt;/p&gt;

&lt;p&gt;Beim Arbeiten und auch sonst so höre ich oft Radio, besonders gern die Streams von &lt;a href=&quot;https://www.bbc.co.uk/sounds/play/live:bbc_6music&quot;&gt;BBC Radio 6 Music&lt;/a&gt; und &lt;a href=&quot;https://www.byte.fm/&quot;&gt;ByteFM&lt;/a&gt;. Genialerweise gab es auf Spotify ein automagisch erstelltes 7-Tage-Archiv der gespielten Tracks dieser und vieler anderer Sender in Form von ständig aktualisierten Playlisten von einem Account namens &lt;a href=&quot;https://open.spotify.com/playlist/47FEEVtiYTnR38AlGpFVnT&quot;&gt;&quot;Ohrenweide&quot;&lt;/a&gt;; ensprechende Bots posteten die Songtitel auch auf Mastodon. &quot;Gab&quot; und &quot;posteten&quot;, weil damit jetzt offenbar Schluss ist 😭 Die Playlisten sind auf &lt;em&gt;privat&lt;/em&gt; gestellt worden:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Last 💓 hearbeat of (now private) 🔊 live playlist creation: 10.06.2024 at 09:00 CET&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ohne diese Playlisten wird es hart. Falls jemand eine Alternative kennt, bitte, bitte her damit!&lt;/p&gt;

&lt;h3 id=&quot;-real-life&quot;&gt;👋 Real Life&lt;/h3&gt;

&lt;p&gt;Am Donnerstagabend war Besuch aus Berlin da, und wir haben einen schönen Abend bei leckerem Essen, kalten Getränken und guten Gesprächen in Wiesbaden verbracht. Ist immer schön, Menschen nach vielen Jahren rein virtuellen Kontaktes mal wieder im &quot;Meatspace&quot; zu treffen!&lt;/p&gt;

&lt;h3 id=&quot;-links&quot;&gt;🔗 Links&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Falls ihr mal ein Verkehrszeichen in Openstreetmap eintragen wollt, kann das &lt;a href=&quot;https://trafficsigns.osm-verkehrswende.org/&quot;&gt;OSM Traffic Sign Tool&lt;/a&gt; eine große Hilfe sein. Es enthält (bisher) nur deutsche VZ, aber auch Zusatzschilder, und spuckt die jeweils passenden Key-Value-Paare für eine Auswahl aus.&lt;/li&gt;
&lt;/ul&gt;
 
    </content:encoded>

    <pubDate>Mon, 10 Jun 2024 07:13:00 +0000</pubDate>
    <guid isPermaLink="false">content/1000548-guid.html</guid>
    <category>wochenrückblick</category>
<creativeCommons:license>http://creativecommons.org/licenses/by/4.0/deed.de</creativeCommons:license>
</item>

</channel>
</rss>
