(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Webbutvecklingsdagbok - efter studierna
~> Halvårskrönika
Med ~ menas ungefär och > menas större än i och med att denna bloggtråd skapades under juni och nu har det gått drygt sex månader sedan första inlägget (26:e juni 2024).
Under denna korta tid så har följande sammanfattningsvis ägt rum i form av en mycket kort dryg "halvårskrönika":
Jag fick ett kortvarigt åtta veckors IT-frilansuppdrag med helt respektabelt arvode där jag sedan fick en skriftlig rekommendation av Uppdragsgivaren på LinkedIn.
Jag deltog i ett kortvarigt ~åtta veckors IT-projekt i utvecklingen av en mobilapp från grunden med techstacken React Native vilket innebar att jag fick lära mig grunderna att skapa en mobilapp för iOS och Android i React Native ihop med utvecklingsramverket(?) Expo.
Jag har deltagit i ett par Fredagsfika i nätverkande syfte och har via det och kontakter på Discord kommit med en Slack-kanal för svenska frilansare och en ytterligare Discord för juniora utvecklare i Sverige.
Jag har haft en mycket enkel Afterwork med första Uppdragsgivaren där det diskuterats om eventuellt framtida Uppdrag redan tidigt under 2025.
Jag har lyckats klara ~7 dagar av Advent of Code 2024 till skillnad från fjolåret(?) då jag bara klarade ett par dagar. Tack vare detta så fick jag sug att lära mig mer om datastrukturer och algoritmer vilket jag aldrig fick fördjupa mig under min distansutbildning inom webbutveckling.
Jobb & Uppdrag
Jag har som tidigare sagt(?) skapat konton på olika nätbaserade frilanswebbplatser såsom Fiverr och UpWork nu. Jag har också fått tips direkt i denna tråd om att gå med olika svenska webbplatser för att söka uppdrag i Sverige.
Nästa år blir spännande inom webbutvecklingsmarknaden då jag sett hos CodeTubers att 2025 blir extra särskilt tufft för juniora utvecklare och andra webbutvecklingsintresserade.
En anmärkningsvärd aspekt som de amerikanska CodeTubers tar upp är just att "I dagens arbetsklimat så måste du mer eller mindre leva utveckling: föra din egen blogg, delta aktivt på sociala medier, bidra till Open Source - och allt detta utanför betald arbetstid!"
Det hela påminner lite om "leva för att jobba" i USA kontra "jobba för att kunna leva" i Norden. Samtidigt är ju konkurrensen större än någonsin i och med alla arbetslösa seniorer (allt IT-baserat varsel som ägt rum i år) vilket då nyutexaminerade juniorer sådana som mig ska konkurrera mot.
Då antar jag att det gäller att ligga på med ambitionsnivåerna samtidigt som om man inte vill framstå som om man bara lever för webbutveckling och inget annat. Nåja, bolag som vill att en ska arbeta gratis åt en älskar ju sådan ambitionsnivå så länge de slipper betala för det!😅
Nästa år blir det också både spännande och läskigt för mitt andra nuvarande uppdrag hos en Stockholmskund gällande mängden arbete jag kommer att få nästa år som sagt. Här måste det redas ut en viktig sak nu: Stockholmskunden kallas för Stockholmskunden för att Stockholmskunden har sitt bolag registrerat i Stockholm med fysiskt kontor i Stockholm där jag aldrig har hälsat på ännu. Kontaktpersonen hos Stockholmskunden är av rent sammanträffande(?) också från Stockholm.
Hobbyprojekt
När denna bloggtråd först skapades så var hobbyprojektet ett funktionsbaserat PHP-ramverk med en mycket förenklad template parser och supersimpel ORM parser (och till skillnad från Prisma(?), helt fri från Rust! ).
Detta lades ju på is så fort det lyckligtvis första betalda uppdraget dök upp. Sedan dök det där spännande IT-projektet upp om en mobilbaserad app via React Native och Expo vilket pågick i cirka åtta veckors tid innan jag fick. Sistnämnda borde jag ha gjort ett bättre "avslut" på.
Det var just den där lördagen(?) där jag insåg att "här sitter jag och utvecklar en mobilapp utan någon slags ersättning förutom en iPhone vars iOS-version inte ens fungerar med Expo Go-appen medan projektledaren skulle sitta och titta på Idol under tiden?!" Inte för att det var Idol utan snarare principen bakom det hela.
På tal om detta mycket kortvariga och samtidigt mycket lärorika IT-projekt så såg jag för ett par veckor sedan att projektledaren hade skrivit att personen nu helt plötsligt letade nya uppdrag (=arbeta mot ersättning) efter en viss tid arbete med denna spännande mobilapp.
Jag kikade nyss på Google Play Store och den mobilappen finns ej upplagd i någon pilot- eller betaversion. Jag misstänker att det blev utmanande att hitta någon annan frivillig mobilapputvecklare!😁
I och med till synes nedläggningen av IT-projektet så kan jag avslöja att det hela rörde sig om "en mobilapp inom hemmaodling av ätbara grödor". Sedan hur denna mobilapp tänkte stå ut från mängden inom nischen får projektledaren själv någon gång berätta.
Efter det IT-projektet så kom jag ju in på att skapa en träningsapp men har ej kommit till skott ännu med det. Och sedan dök ju Advent of Code 2024 upp vilket jag deltog i upp till cirka sju dagar. Det jag blev mest stolt över där var den visuella simuleringen av att med piltangenterna styra en robot som i sin tur styrde en annan robot vilket i sin tur tryckte på en knappsats.
Sedan provade jag att spela manuellt för att försöka göra de färst antal knapptryck men tydligen så var jag inte tillräckligt smart nog för det inskickade resultatet rapporterades som för lågt.
Jag frågade i AoC 2024-tråden om någon kunde prova stoppa in sina korrekta knapptryck för att se om det faktiskt skulle ha simulerats rätt då så jag vet att själva simuleringslösningen åtminstone hade fungerat (har ej fått någon återkoppling kring den förfrågan).
En annan sak jag lärde mig tack vare AoC 2024-tråden och i princip så kallad "kommenteringsprogrammering" (prompt programmering(?)) via chatGPT4o (gratisversionen) var koordinatsystem.
Klassiska grafer i matteböckerna så tänker du att origo 0,0 är längst nere till vänster medan i datorer så är det högst upp till vänster (är det progressive scan vi har att tacka detta för? ) vilket gjorde det lite mentalt utmanande först då jag är så van graferna från matteböckerna.
Vidareutbildning
Om jag blickar tillbaka cirka ett halvår i från skrivande stund så har jag lärt mig en hel del om funktionsbaserad programmering inom PHP, litegrann om datastrukturer och algoritmer, samt React Native och Expo-utvecklingsramverket(?).
Samarbetet med både Uppdragsgivaren och IT-projektet så fick jag lära mig att samverka med andra människor både på distans och fysiskt öga mot öga. Det har varit lärorikt och visst är det kul att hänga socialt också.
Jag tillhör den andel människor som känner störst tillfredsställelse/bekräftelse/"självbevaralsedriftdriv" av sociala interaktioner även om jag är smått "socialt trög" i början i och med min milda Asperger.
Således gillar jag paradoxalt nog att sitta i Discord-röstkanaler likt att sitta vid den fysiska lägerelden där folk kommer och går och inget känns som inbokade möten utan bara spontant "virtuellt häng". Jag saknar den biten från min gamla "virtuella skolklass" jag hade under Webbutvecklingsprogrammet på distans.🥲
Andra människor kan drivas mer av resultat och/eller makt på gott och ont. En vanlig tragedi här då är när människor som jag då träffar människor som drivs mer av makt och vill utnyttja min tillfälliga sociala naivitet innan jag upptäcker vad som inträffat och jag drar mig ut snabbare än Ruby skriven i Assembler.
Hursomhelst.
Nästa år accelereras Webbutvecklandet av mig - både på hobby- och yrkesnivå!🫡
Gott Nytt År Så Gott Som Det Går™ & På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
https://www.youtube.com/watch?v=opy_y0dkwmk
~> Halvårskrönika
Med ~ menas ungefär och > menas större än i och med att denna bloggtråd skapades under juni och nu har det gått drygt sex månader sedan första inlägget (26:e juni 2024).
Under denna korta tid så har följande sammanfattningsvis ägt rum i form av en mycket kort dryg "halvårskrönika":
Jag fick ett kortvarigt åtta veckors IT-frilansuppdrag med helt respektabelt arvode där jag sedan fick en skriftlig rekommendation av Uppdragsgivaren på LinkedIn.
Jag deltog i ett kortvarigt ~åtta veckors IT-projekt i utvecklingen av en mobilapp från grunden med techstacken React Native vilket innebar att jag fick lära mig grunderna att skapa en mobilapp för iOS och Android i React Native ihop med utvecklingsramverket(?) Expo.
Jag har deltagit i ett par Fredagsfika i nätverkande syfte och har via det och kontakter på Discord kommit med en Slack-kanal för svenska frilansare och en ytterligare Discord för juniora utvecklare i Sverige.
Jag har haft en mycket enkel Afterwork med första Uppdragsgivaren där det diskuterats om eventuellt framtida Uppdrag redan tidigt under 2025.
Jag har lyckats klara ~7 dagar av Advent of Code 2024 till skillnad från fjolåret(?) då jag bara klarade ett par dagar. Tack vare detta så fick jag sug att lära mig mer om datastrukturer och algoritmer vilket jag aldrig fick fördjupa mig under min distansutbildning inom webbutveckling.
Jobb & Uppdrag
Jag har som tidigare sagt(?) skapat konton på olika nätbaserade frilanswebbplatser såsom Fiverr och UpWork nu. Jag har också fått tips direkt i denna tråd om att gå med olika svenska webbplatser för att söka uppdrag i Sverige.
Nästa år blir spännande inom webbutvecklingsmarknaden då jag sett hos CodeTubers att 2025 blir extra särskilt tufft för juniora utvecklare och andra webbutvecklingsintresserade.
En anmärkningsvärd aspekt som de amerikanska CodeTubers tar upp är just att "I dagens arbetsklimat så måste du mer eller mindre leva utveckling: föra din egen blogg, delta aktivt på sociala medier, bidra till Open Source - och allt detta utanför betald arbetstid!"
Det hela påminner lite om "leva för att jobba" i USA kontra "jobba för att kunna leva" i Norden. Samtidigt är ju konkurrensen större än någonsin i och med alla arbetslösa seniorer (allt IT-baserat varsel som ägt rum i år) vilket då nyutexaminerade juniorer sådana som mig ska konkurrera mot.
Då antar jag att det gäller att ligga på med ambitionsnivåerna samtidigt som om man inte vill framstå som om man bara lever för webbutveckling och inget annat. Nåja, bolag som vill att en ska arbeta gratis åt en älskar ju sådan ambitionsnivå så länge de slipper betala för det!😅
Nästa år blir det också både spännande och läskigt för mitt andra nuvarande uppdrag hos en Stockholmskund gällande mängden arbete jag kommer att få nästa år som sagt. Här måste det redas ut en viktig sak nu: Stockholmskunden kallas för Stockholmskunden för att Stockholmskunden har sitt bolag registrerat i Stockholm med fysiskt kontor i Stockholm där jag aldrig har hälsat på ännu. Kontaktpersonen hos Stockholmskunden är av rent sammanträffande(?) också från Stockholm.
Hobbyprojekt
När denna bloggtråd först skapades så var hobbyprojektet ett funktionsbaserat PHP-ramverk med en mycket förenklad template parser och supersimpel ORM parser (och till skillnad från Prisma(?), helt fri från Rust! ).
Detta lades ju på is så fort det lyckligtvis första betalda uppdraget dök upp. Sedan dök det där spännande IT-projektet upp om en mobilbaserad app via React Native och Expo vilket pågick i cirka åtta veckors tid innan jag fick. Sistnämnda borde jag ha gjort ett bättre "avslut" på.
Det var just den där lördagen(?) där jag insåg att "här sitter jag och utvecklar en mobilapp utan någon slags ersättning förutom en iPhone vars iOS-version inte ens fungerar med Expo Go-appen medan projektledaren skulle sitta och titta på Idol under tiden?!" Inte för att det var Idol utan snarare principen bakom det hela.
På tal om detta mycket kortvariga och samtidigt mycket lärorika IT-projekt så såg jag för ett par veckor sedan att projektledaren hade skrivit att personen nu helt plötsligt letade nya uppdrag (=arbeta mot ersättning) efter en viss tid arbete med denna spännande mobilapp.
Jag kikade nyss på Google Play Store och den mobilappen finns ej upplagd i någon pilot- eller betaversion. Jag misstänker att det blev utmanande att hitta någon annan frivillig mobilapputvecklare!😁
I och med till synes nedläggningen av IT-projektet så kan jag avslöja att det hela rörde sig om "en mobilapp inom hemmaodling av ätbara grödor". Sedan hur denna mobilapp tänkte stå ut från mängden inom nischen får projektledaren själv någon gång berätta.
Efter det IT-projektet så kom jag ju in på att skapa en träningsapp men har ej kommit till skott ännu med det. Och sedan dök ju Advent of Code 2024 upp vilket jag deltog i upp till cirka sju dagar. Det jag blev mest stolt över där var den visuella simuleringen av att med piltangenterna styra en robot som i sin tur styrde en annan robot vilket i sin tur tryckte på en knappsats.
Sedan provade jag att spela manuellt för att försöka göra de färst antal knapptryck men tydligen så var jag inte tillräckligt smart nog för det inskickade resultatet rapporterades som för lågt.
Jag frågade i AoC 2024-tråden om någon kunde prova stoppa in sina korrekta knapptryck för att se om det faktiskt skulle ha simulerats rätt då så jag vet att själva simuleringslösningen åtminstone hade fungerat (har ej fått någon återkoppling kring den förfrågan).
En annan sak jag lärde mig tack vare AoC 2024-tråden och i princip så kallad "kommenteringsprogrammering" (prompt programmering(?)) via chatGPT4o (gratisversionen) var koordinatsystem.
Klassiska grafer i matteböckerna så tänker du att origo 0,0 är längst nere till vänster medan i datorer så är det högst upp till vänster (är det progressive scan vi har att tacka detta för? ) vilket gjorde det lite mentalt utmanande först då jag är så van graferna från matteböckerna.
Vidareutbildning
Om jag blickar tillbaka cirka ett halvår i från skrivande stund så har jag lärt mig en hel del om funktionsbaserad programmering inom PHP, litegrann om datastrukturer och algoritmer, samt React Native och Expo-utvecklingsramverket(?).
Samarbetet med både Uppdragsgivaren och IT-projektet så fick jag lära mig att samverka med andra människor både på distans och fysiskt öga mot öga. Det har varit lärorikt och visst är det kul att hänga socialt också.
Jag tillhör den andel människor som känner störst tillfredsställelse/bekräftelse/"självbevaralsedriftdriv" av sociala interaktioner även om jag är smått "socialt trög" i början i och med min milda Asperger.
Således gillar jag paradoxalt nog att sitta i Discord-röstkanaler likt att sitta vid den fysiska lägerelden där folk kommer och går och inget känns som inbokade möten utan bara spontant "virtuellt häng". Jag saknar den biten från min gamla "virtuella skolklass" jag hade under Webbutvecklingsprogrammet på distans.🥲
Andra människor kan drivas mer av resultat och/eller makt på gott och ont. En vanlig tragedi här då är när människor som jag då träffar människor som drivs mer av makt och vill utnyttja min tillfälliga sociala naivitet innan jag upptäcker vad som inträffat och jag drar mig ut snabbare än Ruby skriven i Assembler.
Hursomhelst.
Nästa år accelereras Webbutvecklandet av mig - både på hobby- och yrkesnivå!🫡
Gott Nytt År Så Gott Som Det Går™ & På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
Intressant att följa din resa där jag känner igen mig på flera punkter. Har inte själv någon officiell diagnos, men när jag nu passerat 50 och ser tillbaka på händelser genom livet, så inser jag att några extra bokstäver med stor sannolikhet är anledningen till en rad händelser och beslut. Både bra och dåliga.
Har själv varit inblandad i ett par projekt som gratis utvecklare, så att läsa om ditt hobbyprojekt träffar en fortfarande öm tå och jag skäms över hur jag kunde låta mig utnyttjas så enkelt.
Önskar dig all lycka till med din fortsatta resa
MSI PRO Z790-P WIFI | Intel i9 13900K | 128 GB DDR5
GTX 4070 12 GB
Samsung 990 Pro 4 TB | WD Black SN850X 2 TB Gen 4 | 2 x 1 TB Samsung 970 EVO Plus
3 x ASUS 27" | 1 x Philips 49"
Webbutvecklingsdagbok - efter studierna
Jobb & Uppdrag
I skrivande stund har jag avslutat deltagande i ett "Kodfika" av typen "#indiehackers" där jag fick kontakt med en person som jag tror är en spelprogrammerare men som hade ett intresse i att ta fram en särskild kommunikationsinsticksmodul till Slack efter att personen under "kodfikat" berättat om ett potentiellt problem han ville prova att lösa.
Om jag förstått det hela rätt så skrivs insticksmoduler till Slack främst i JavaScript och då jag behöver meningsfulla projekt att kunna visa upp så tänker jag att detta kan vara en rolig sak att prova på. Jag har inga ekonomiska förväntningar på detta och egentligen skulle det möjligen falla mer under "Hobbyprojekt" än här. Hursomhelst, mycket givande "Kodfika" med ett sådant återkommande om bara två veckor igen!
På den faktiska Jobb- & Uppdragssidan så har jag nu gått med Konsultmäklarna (mejlat intresseanmälan), och Ework (skapat grundläggande konto) och för någon vecka sedan hade jag ett samtal med min f.d. Uppdragsgivare om kommande Teams-möten en gång varannan vecka där det gäller att hålla kontakten "varm" för eventuella framtida uppdrag hos samma Uppdragsgivare. Det första återkommande mötestillfället går av stapeln i början på nästa vecka.
Hobbyprojekt
Jag läste i någon tråd om oönskade foruminlägg på grund av den medföljande stämningen och tänkte då på att uBlock Origin kan ju dölja webbelement baserat på skräddarsydd filtrering. Dessvärre hittade jag ingen officiell dokumentation om filtersyntaxen så jag bad chatGPT om hjälp. Det verkar vara klassisk CSS-stilselektering det bygger på:
Navigera uBlock Origin -> Settings -> My Filters -> Enable my custom filters:
! Sweclockers
sweclockers.com##div[id^="post"]:has(span[itemprop="name"]:has-text("användarnamn1"))
sweclockers.com##div[id^="post"]:has(span[itemprop="name"]:has-text("användarnamn2"))
sweclockers.com##div[id^="post"]:has(span[itemprop="name"]:has-text("användarnamnOSV"))
Filtret döljer alltså baserat på ett div-element med Regex-värdet: /post.+/ inuti dess id-attribut om det finns ett span-element med särskilt itemprop-attributvärde och statiskt textvärde (användarnamnet) oavsett djup från det första div-elementet.
Mycket viktigt: För att fortsätta stödja Sweclockers så ska du ha det avslaget för webbplatsen samtidigt som kosmetisk filtrering är påslaget så att dina skräddarsydda dynamiska filter kan dölja foruminlägg från oönskade användare.
Jag provade och det fungerade men jag har än så länge ingen användare vars inlägg jag vill dölja. Förhoppningen är att det inte ska behövas. Men allt handlar ju inte bara om mig så jag tänkte att någon annan kanske ville kunna skräddarsy sin Sweclockers-forumupplevelse!
Filterkoden går att extrapolera till andra webbplatser där det då gäller att hitta det div-element som motsvarar ett inlägg baserat på något som identifierar den eller det du vill dölja på samma elementnivå utan att råka dölja det du faktiskt vill se från andra på samma elementnivå.
Dock verkar det kunna krångla med att ha kosmetisk filtrering påslaget samtidigt som insticksmodulen är avslagen för webbplatsen (åtminstone i nuvarande Firefox-webbläsarversion med nuvarande uBlock Origin-version)?!🤔 Kan ej svara för andra webbläsare. Glöm inte heller Manifest V3 som träder i kraft i år!🤮
A BLAST FROM THE PAST!
En rolig sak jag lyckades göra efter den första lektionen i Introduktionskursen till JavaScript från september 2022 var en mycket förenklad binär sökfunktion i JavaScript (utan LLM-hjälp):
test = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30,
];
function binsearch(list, target) {
count = 1;
left = 0;
right = list.length - 1;
while (left <= right) {
middle = Math.floor((left + right) / 2);
console.log(
"Target: " +
target +
" | L: " +
left +
" | R: " +
right +
" | Mid: " +
middle,
);
if (list[middle] == target)
return `Found ${target} after ${count} searches!`;
if (list[middle] < target) {
count++;
left = middle;
} else {
count++;
right = middle;
}
}
return "Did not find " + target;
}
console.log(binsearch(test, 9));
console.log(binsearch(test, 15));
console.log(binsearch(test, 30));
console.log(binsearch(test, 4));
console.log(binsearch(test, 69));
Det härliga med den mycket simpla funktionen är att jag kunde "förstå" de tre "pekarna" (left, right och middle) och även det visuella i hur du kapar av halva från höger eller vänster och fortsätter så till att du antingen hittar det du söker eller inte.
Under första lektionen i första kursen under distansutbildningen 2022-2024 så visade just "KodGuden" (vad jag kallade läraren för) upp binär sökfunktion och sa att efter redan den kursen så skulle vi kunna skriva och förstå en sådan funktion. Inte jag förrän nu dock!
Mycket av mina plötsliga insikter av detta är YT-klipp om "vanliga designmönster för att lösa Leetcodes" där det handlar om att föreställa sig att du hanterar flera "pekare" i loopar samtidigt för att bearbeta data. Och så har vi även Advent of Code-deltagandet från förra året som satte sina matnyttiga utvecklingsspår!
99 % FUNCTIONAL && 1 % CLASS PHP FRAMEWORK!
Jag har funderat om på mitt Hobbyprojekt om det 99 % funktionsbaserade PHP-ramverket där tanken är att du bara konfigurerar på ett vis likt Grunt-filer inom JS. Min tanke är att det hela ska handla om att hantera tre globala variabler genom varje anropsflöde:
$r = null; // Rutt med autentisering och/eller auktorisering och/eller 404
$d = null; // Data efter giltig rutt med giltig autentisering och/eller auktorisering eller enbart 404
$p = null; // Sida att visas efter bearbetad data efter bearbetad rutt
I början av det Hobbyprojektet gjorde jag väldigt många undermappar för att det skulle vara välorganiserat. Men nu på senaste tid har jag insett att det också bara skapar onödiga krav på att det ska vara "spaghettifierat" och mer komplext än praktiskt nödvändigt.
Tanken är att först bara få till det mest fundamentala steg för steg:
Välj rätt rutt, autentisera/auktorisera vid behov.
Välj rätt data utifrån tidigare steg.
Välj rätt sida utifrån två tidigare steg.
När dessa tre fundamentala steg fungerar för det mesta simpla så går det att utöka komplexiteten i varje steg för fullända™ det 99 % funktionsbaserade PHP-ramverket mer.
En annan sak jag tänkt på är att ha ett simpelt men dedikerat GUI till det så att du i princip kan sköta konfigurering av varje rätt, data och sida på ett mer GUI-vis. Det du fortfarande gör manuellt är att skriva sidorna vilket sedan körs genom en så kallad Template Engine med livskvalitetsimplementationer såsom automatiska htmlspecialchars() på alla echo och dylikt.
Vidareutbildning
Jag ska läsa vidare i Nätverksboken om de olika OSI-lagren då jag i princip är färdig med transportlagret och allt som det innebär. Nu blir det nätverkslagret och här tänkte jag på en intressant tanke ur säkerhetstänk: Du kan IP-begränsa CRUD-anrop så att när någon gör ett metodanrop med stulen sessionskaka så kommer det ej att fungera om det är fel IP-adress angiven vid anropsförsöket.
Men! Tänk om du då finurligt kan manipulera paketen så du skickar med så att det är rätt IP-adress som du kom över när du väl stal sessionskakan? Visst, svaret kommer inte komma till bedragarens IP-adress men anropet kommer dessvärre att gå igenom. Här insåg jag då vikten av CSRF och särskilt om dessa då kan erhållas endast via begränsad IP-adress.
För då måste du även lyckas få det första svaret med CSRF som sedan skickas med de manipulerade nätverkspaketen för att göra det egentligen icke-tillåtna metodanropet. Exempel skulle kunna vara att du vill radera något i en databas där du inte bryr dig om något svar från servern utan du bara vill få skadan gjord.
Om det då krävs CSRF först i första IP-begränsade svaret för att sedan kunna skicka med det CSRF-värdet vid det faktiska metodanropet så ökar det åtminstone svårigheterna. Visst, lyckas sessionskakan stjälas inklusive nuvarande navigerade sidan där en CSRF har genererats, ja, då har bedragaren dessvärre lyckats.
Det bästa sämsta fallet är då en stulen sessionskaka men det är på en sida utan CSRF så inget faktiskt anrop mot databasen kan göras för så fort bedragaren provar navigera till annan undersida så kan bara svaret gå till den begränsade IP-adressen och när detta är fel så kan sessionskakan raderas genom att den ej matchar IP-adressen lagrad i sessionstabellen på serversidan som då säger åt webbläsaren att rensa sina kakor på webbplatsen.
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jobb & Uppdrag
Hittills under veckan har jag redan arbetat färdigt Stockholmskundens senaste veckoupplaga av arbete. I nedanstående underrubriker avhandlas ytterligare händelser under denna vecka hittills.
Icke-mysteriet med de försvunna temafilerna i WordPress
För några dagar sedan jag ett mejl från en tidigare kortvarig uppdragsgivare när jag fixade i en WordPress-temamapp för att möjliggöra spårningsintegrering via iframe-element och detta var hela vägen tillbaka till september 2024 efter att jag tagit en titt i webbläsarhistoriken då jag hade i princip glömt bort när jag hade gjort detta under fjolåret.
Det hela handlade om att uppdragsgivaren berättade att det jag hade gjort hade försvunnit ur WordPress-temamappen efter att temat hade uppdaterats av sig själv. Uppdragsgivaren hade dock lyckats hitta kopia av ena av de två olika template-filerna så det gick att återskapa den önskade funktionaliteten.
Jag misstänker att temauppdateringar WordPress är så simpelt som att radera hela mappen och bara installera allt på nytt igen och det var ju i temamappen template-filerna låg så så klart försvann de då vid uppdateringen. Jag sökte och läste snabbt på om så kallade Child Themes och lärde mig då att det är barnteman som ska nyttjas om egna modifikationer av ett givet tema vill göras och som ska finnas kvar vid framtida automatiska temauppdateringar.
Detta bad jag så hemskt mycket om ursäkt till uppdragsgivaren att jag ej kände till förrän då. Den andra slags template-filen gick att återskapa då det inte var lika komplicerat och uppdragsgivaren hade på egen hand lyckats få till det så allt var lugnt. Jag rekommenderade uppdragsgivaren att hädanefter nu ha kopior av dessa template-filer utanför temamappen och även på annan server för att följa god säkerhetskopieringssed.
Kommande miniuppdrag efter nästa veckas fysiska möte?!
Ett fysiskt möte med två personer och en från min tidigare uppdragsgivare kommer att äga rum någon gång i början på nästa vecka och detta i sin tur skulle kunna leda till ett nytt uppdrag från samma hittills mycket nöjda uppdragsgivare. Allt handlar om och hur finansieringen ska äga rum då uppdragsgivaren - hör och häpna - inte vill att jag ska jobba gratis!🤯
Spekulativt så kanske det rör sig om ett uppdrag varandes mellan en till tre veckor med ett arvode på 500 SEK/timme. Mer än noll veckor åtminstone om det blir av vill säga. En sak som efterfrågas vilket kommer diskuteras mer ingående nästa vecka är en chattfunktion och då funderar jag när det är Loopia som webbhotell utan någon VPS vilket innebär att det blir PHP, JS, HTML & CSS (Tailwind) som tillgänglig mjukvaruinfrastruktur.
Jag frågade på Discord och först pratades det om att Loopia lär garanterat ha begränsat hur länge PHP-kod får köras så det lär knappast gå att veva igång en PHP-fil som ska ageras om en stream_socket_server som WSS i JS ansluter till för att möjliggöra chattfunktionaliteten.
Sedan nämndes förslagsvis att använda tredjepartstjänster men då drog jag upp GDPR som motargument och då fick jag motargumentet att de (tredjepartstjänsterna) troligen redan hade tänkt på GDPR för att även kunna verka inom EU. Då gav jag motargumentet att de slags data som kan komma att finnas i dessa chatter kan komma att tillhör kategorin av vad som kallas för särskilt känsliga personuppgifter inom GDPR-lagarna så då önskas det att sådan data finns på en och samma svenska server för att endast behöriga ska kunna administrera rätt enligt GDPR.
Det finns två möjliga "fullösningar" för chattfunktionaliteten, men inga jag egentligen anser är realistiska eller professionellt lämpliga:
Veva upp en NodeJS som jag har tillgång till på Inleed som talar med databas hos Loopia där uppdragsgivarens chattdata finns.
Simulera realtidschatt via JS genom polling mot PHP vilket finns hos uppdragsgivarens mjukvaruinfrastruktur.
Första "fullösningen" är inte riktigt hållbar då det skulle käka på min bandbredd hos Inleed och det vore högst olämpligt att en kunds lösning körs på mitt webbhotellabonnemang? Andra "fullösningen" har den stora nackdelen att kunna leda till överbelastning mot databasen genom att hela tiden försöka hämta de senaste chattmeddelandena fast inga finns vilket då blir bokstavligen talat proportionellt illa för varje extra deltagare inne i samma chatt.
En verklig lösning men troligen ej inom snar framtid vore att uppdragsgivaren investerar i en VPS hos Loopia så det går att veva igång en realtidsinteraktiv server såsom NodeJS vilket kan agera som en chattserver då. Men som sagt var skulle det innebära en ytterligare månadskostnad på 339 kr/mån exklusive moms - bara för chattfunktionaliteten och då är det samtidigt oklart huruvida det skulle bli lönsamt först.
Så min tanke är att argumentera för att vänta med chattfunktionaliteten eller åtminstone berätta om vilka förutsättningar som finns i och med den nuvarande mjukvaruinfrastrukturen uppdragsgivaren betalar för just nu hos Loopia och vad det skulle kunna kosta bara för att få igång en "riktig" chattfunktionalitet med vedertaget beteende för att vara en realtidschatt.
"Kodstötinsatsen"
Okej, sanning med modifikation angående om att jobba gratis: Idag gjorde jag en två minuters "kodstötinsats" åt den hittills mycket nöjda uppdragsgivaren genom att uppdatera två paragrafer på deras webbplats då de ska ändra sin marknadsföring för att roffa åt sig sin allra första kund efter att jag tagit fram deras administrativa system såväl som deras webbaserade digitala spelmodul.
Hobbyprojekt
Kommunikationsinsticksmodulen i Slack?
För någon vecka sedan var jag med på ett Fredagsfika där jag fick kontakt med en person som har intresse att utveckla antingen en insticksmodul för Slack eller en fristående MVP/MLP-lösning för en särskild kommunikationsapplikation vars syfte är att underlätta under arbetstid på ett väldigt vagt beskrivet vis för att respektera personens möjligtvis unika idé.
Jag har nu i skrivande stund mejlat mer med personen och sagt att jag vill att personen ska äga denna fristående lösning så jag har bett personen att - om det existerar - bjuda in mig till dess GitHub-repo som kollaboratör. Jag vill ju som sagt var få regelbunden kontakt med mer erfarna utvecklare och även få till någon slags meningsfull utvecklingsportfölj istället för enbart klassiska revolutionerande banbrytande "Att göra"-appar skrivna i React och sjösatta på Vercel.
Tänk dig att kunna skicka QR-(de)krypterade lösenord direkt till webbläsaren?!
Ett annat hobbyprojekt som jag hade i mitt huvud som jag sedan insåg kanske blev onödigt osäkert i och med nätverkspaket och liknande var att jag tänkte mig att du skulle kunna skriva ut dina lösenord som rena krypterade QR-koder. Sedan har du en simpel mobilapp som sitter på dekrypteringsnyckeln och som samtidigt har kontakt med en webbläsarinsticksmodul (eng. "extensions") som då via P2P-anslutning kan mata in det dekrypterade lösenordet på valfri webbplats.
På frågan, "Varför inte bara använda Bitwarden?" så var svaret att jag vill ju helst att inga spår av lösenord ska finnas i webbläsaren. Fast då insåg jag att även om jag får till att skicka dekrypterat lösenord från mobilapp till en webbläsarinsticksmodul så betyder det att det finns i något möjligtvis "sniffbart" nätverkspaket såväl som erhållen data i tvåvägsanslutningen mellan webbläsarinsticksmodulen och mobilappen. Då är Bitwarden troligen det "bästa av det sämsta" gällande denna lösenordsgrej.
Men det hade ju varit så sjukt mysigt om jag bara kunde scanna in QR-kod från papper och så skrevs det in i valfri webbplats via ansluten webbläsarinsticksmodul!
Miniskvaller om tidigare hobbyprojekt
Jag såg att en person jag varit med i tidigare hobbyprojekt vilket jag berättat om vid annat tillfälle har nu roffat åt sig ett tillfälligt uppdrag från en tidigare kund - detta efter att ha lagt ned mobilappen (jag hittade ingen lanserad mobilapp men den presenterande webbplatsen om mobilappen finns fortfarande kvar) efter en viss tid efter att jag hade lämnat det hobbyprojektet.
Precis som i mitt fall så är det tidigare nöjda kunder som är de mest sannolika att få uppdrag hos igen! 🫡
Vidareutbildning
Vidareutbildningen denna gång blir att läsa vidare om nätverkslagret i OSI-modellen och börja få hum om hur mina nätverkspaket kan komma rätt och vilka för- & nackdelar som finns såväl som sårbarheter att ta höjd för utifrån webbutvecklingsperspektivet. Annan vidareutbildning är vad jag möjligen kan lära mig av personen som vill utveckla kommunikationsgrejen till Slack eller som fristående lösning då den person är helt klart mycket mer erfaren utvecklare än jag.
På återseende!
Mvh,
WKF.
P.S. I skrivande stund precis innan publicering såg jag om Nintendo Switch 2 här på Sweclockers, så blir att sätta sig in i det nu!😍
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Men! Tänk om du då finurligt kan manipulera paketen så du skickar med så att det är rätt IP-adress som du kom över när du väl stal sessionskakan? Visst, svaret kommer inte komma till bedragarens IP-adress men anropet kommer dessvärre att gå igenom.
Nej, då har du missat en del i hur klassisk TCP funkar. Three way handshake kräver i praktiken att man kan ta emot SYN+ACK-svaret innan man kan överföra data.
QUIC, multipath TCP, buggig sekvensnummergenerering och buggiga TCP-implementationer som levererar data från SYN-paketet till mottagaren innan handskakningen är avklarad kanske ändrat ekvationen (vad vet jag), men normalt kan man inte skicka data i HTTP(S) med förfalskade IP-headrar.
Men det spelar ingen roll, om man kan stjäla en sessionskaka så kan man antagligen initiera en uppkoppling från maskinen där man stal sessionskakan, dvs från rätt IP.
Och folk på mobila enheter kommer fortfarande att bli irriterade om du loggar ut dem bara för att de byter IP.
Webbutvecklingsdagbok - efter studierna
Jobb & Uppdrag
De två senaste veckorna har varit fyllda med veckojobb från Stockholmskund såväl som jakt efter fler uppdrag. Från den klassiska Uppdragsgivaren så har jag haft simpelt uppföljningsmöte via Teams och där framgår det att Uppdragsgivaren kommer att ansöka om finansiella medel och/eller ta ur eget bolag för att jag ska kunna utveckla något på tre veckor vilket betyder att deras budget kommer att ligga på att kunna täcka mig för tre veckor.
Jag tror jag tidigare nämnde om två personer som hade en idé som Uppdragsgivaren var först intresserad av men ville också höra min offert om vad en minsta version av det projektet skulle gå på i tid och kostnad. Då beräknade jag cirka fyra veckor och detta blev som sagt var nu ned till tre veckor och med färre saker inräknade då. Uppdragsgivaren har varit i kontakt med dessa två personer och bett dem att konkretisera sin affärsmodell mera innan Uppdragsgivaren känner sig redo för att våga satsa på det hela.
Samma Uppdragsgivaren har också haft möten med flera potentiella kunder och söker även aktivt efter så kallade referensuppdrag för att kunna få in foten på denna särskilda nisch inom organisationsutveckling eller vad det skulle kunna klassas in i för slags marknadssegment. Det ska poängteras att Uppdragsgivaren har trots allt varit aktiv i över 20 år så det borde gå förr eller senare tänker jag?🤔
På tal om Stockholmskunden så brukar jag få massa PDF-filer varje vecka och dessa kommer lustigt nog med "PDF_" i början av filnamnen trots att deras filtyp redan är PDF. På grund av det så tog jag hjälp av chatGPT-gratisversionen och fick fram följande [i].bat[i]-fil:
_Remove_PDF__In_Filenames.bat
@echo off
setlocal enabledelayedexpansion
rem Step 1: Remove "PDF_" prefix
for %%f in (PDF_*.pdf) do (
set "filename=%%~nxf"
set "newname=!filename:PDF_=!"
ren "%%f" "!newname!"
)
rem Step 2: Replace spaces with underscores
for %%f in (* *.pdf) do (
set "filename=%%~nxf"
set "newname=!filename: =_!"
ren "%%f" "!newname!"
)
rem Step 3: Add "-WKF-s" to the end of all filenames
for %%f in (*.pdf) do (
set "filename=%%~nxf"
set "basename=%%~nf"
set "newname=!basename!-WKF-s.pdf"
ren "%%f" "!newname!"
)
echo Removed _PDF and whitespaces and added WKF with s at the end.
pause
Den plockar bort "PDF_" i början av filerna, ersätter mellanslag med "_" och lägger till "-WKF-s" i slutet av filerna för jag anger antalet PDF-sidor framför "s.pdf". Simpel liten sak som gör arbetslivet effektivare. Jag har även optimerat hur jag förbereder inför månadsfakturorna. (Angående första månaden för 2025 så har denna månad varit godkännande bra utan några anmärkningar!)
Jag har gått med en ytterligare Slack-grupp som specifikt fokuserar på utveckling/programmering där jag diskuterat bland annat kommande Deepseek V3 & R1 (mer om det i nedanstående underrubriker). I en annan nuvarande "IT-frilansar-skrå" till Slack-grupp så har jag fått kontakt med en potentiellt mellanhandsuppdragsgivare (=uppdrag via mellanhand) där jag möjligen kan få ett första riktigt(?) IT-uppdrag mot en slutkund. Möjligen hör denna mellanhandsuppdragsgivare av sig senare idag tack vare mitt lockande erbjudande.
På tal om det lockande erbjudandet så tar detta fram en klassisk rosa stor elefant i Slack-gruppen som jag tänkte ta upp här. Det kan uppfattas som en slags "fräckhet" från min sida att som nyutexaminerad junior Webbutvecklare från maj 2024 komma in i en Slack-grupp där högst troligt majoriteten av användarna är erfarna (webb)utvecklare vars arvoden förhoppningsvis reflekterar detta.
Jag som "färsking" inom IT-frilansarvärlden (inte själva frilansandet på distans för övrigt dock) kommer då in där och kan erbjuda vad som skulle kunna klassas nästan som "skambud" till arvode jämfört med de mycket mer erfarna utvecklarna. På så vis skulle arvodena kunna bli skeva på grund av att jag lägger mig nästan 30-50 % lägre i arvode. Samtidigt kommer vi tillbaka till att:
"Ingen vill egentligen köpa det allra billigaste, men gärna det näst billigaste om det går och finns!"
Jag sa till och med till mellanhandsuppdragsgivaren att jag befinner mig i denna nuvarande "rävsax" vilket är varför jag lägger mig på den nivån jag lade mig i mitt lockande erbjudande. Ur mitt perspektiv på lång sikt så vill jag ju kunna lägga mig på liknande arvodesnivå som de mer erfarna i den Slack-gruppen men för närvarande så vore det professionellt oetiskt av mig att göra, eller? Å andra sidan så kanske jag är "oetisk" som "orättvist" kan lägga mig så lågt i arvode vid konkurrens om de få IT-distansuppdrag som faktiskt finns?🤔
[u]"SÖK JOBB OCH ARBETA NÅGRA ÅR FÖRST!!!"[/u] kan jag tänka mig att Du kanske tänker högt nu. Nu har jag valt den vägen och den "leken" jag valt så jag står mitt tärningskast och därmed den leken få tåla. Frågan är om jag "förstör för andra" och förstör den IT-frilansande distansmarknaden genom mina möjligen "skamfulla arvoden" på cirka 500 SEK/timme? Vad skulle du säga om det?🤔
Hobbyprojekt
Ett litet tillfälligt hobbyprojekt på senaste tid har varit att komma igång med lokala LLM-modeller stora som små och nu när Deepseek V3 R1 dök upp så provade jag dess 32B- och 70B-varianter på mitt RTX3090. Förstnämnda gick rätt så snabbt medan det andra producerade enbart några tokens per sekund i taget men det gick ändå.
Det jag inte fått till ännu är att slippa själva tänka-taggen när jag vill använda det likt Copilot används via Continue-tillägget i VSCode, det vill säga "autocompletandet". Förr eller senare får jag nog till det. Sedan det här med censurerad modell så hoppas jag så klart på ocensurerad modell för maximal åtkomst men jag kommer ju använda det (99+(1-10^(-1000)))% till utveckling och inte geopolitiska diskussioner.
Då veckorna var fyllda så hade jag ej besvarat på personen som en intressant mångplattformsapp för kommunikation vars mejl jag håller på att besvara nu i samma veva som jag skriver detta inlägg här på Sweclockers. Då det ska vara en realtidsapp för kommunikation och jag enbart har erfarenhet med NodeJS för backend så tänker jag att socket.io är av intresse där WSS från frontend används? Men om jag förstått rätt så används Elixir för Discord-servrar så är det egentligen det som gäller om man vill göra rätt redan från början?🤔
Vidareutbildning
De senaste två veckorna har jag fortsatt med datastrukturer plus något nytt och spännande: "dataorienterad programmeringsdesign". Detta är när du via dina datastrukturer optimerar programmeringen genom hur dina datastrukturer då lättare och snabbare via processorns antal cykler kan lättare komma åt data på rätt ställe, t.ex., via processorns olika inbyggda caches.
Mycket av det hela handlar om hur datastrukturer skrivs beroende på hur de placeras ut (och därmed deras åtkomst) i minnet av processorn. Jag kikade på: https://www.youtube.com/watch?v=IroPQ150F6c och https://www.youtube.com/watch?v=WwkuAqObplU och insåg då hur lite jag faktiskt förstår om processorn och dess hantering av minne och hur detta påverkas av ens lågnivåkod.
När jag frågade ChatGPT-gratisversionen om det fanns liknande datastrukturmässiga optimeringar att göra i exempelvis JS och/eller PHP så påstod den att det troligen inte fanns det. I ett av de YT-klippen jag kikade på så argumenterades det även att det i vissa omständigheter skulle gå fortare för processorn att bara beräkna om ett värde än att hämta dess lagrade värde från det fysiska minnet.
Samtidigt så har jag sett när React-kompilator används (vilket då nyttjar useMemo vid behov) och inte och hur det påverkar prestandan i frontend när tusentals komponenter omrenderas på grund av något helt orelaterat ändrat tillstånd i appen. Så någonstans blir det hela mer komplex och kanske inte lika relevant i högnivåspråk såsom JS och/eller PHP kontra lågnivåspråk såsom C(++), Rust, med flera?🤔
Nu får det räcka med allt svammel och/eller rapporterande för denna gång!
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Nej, då har du missat en del i hur klassisk TCP funkar. Three way handshake kräver i praktiken att man kan ta emot SYN+ACK-svaret innan man kan överföra data.
QUIC, multipath TCP, buggig sekvensnummergenerering och buggiga TCP-implementationer som levererar data från SYN-paketet till mottagaren innan handskakningen är avklarad kanske ändrat ekvationen (vad vet jag), men normalt kan man inte skicka data i HTTP(S) med förfalskade IP-headrar.
Men det spelar ingen roll, om man kan stjäla en sessionskaka så kan man antagligen initiera en uppkoppling från maskinen där man stal sessionskakan, dvs från rätt IP.
Och folk på mobila enheter kommer fortfarande att bli irriterade om du loggar ut dem bara för att de byter IP.
Tjo!
Sammanträffande nog så hade jag precis innan jag läst ditt inlägg läst i Computer Networking-boken om överbelastningsattacken SYN flooding och hur nätverksverktyget nmap använder sig just av SYN+ACK för att kolla vilka portar som troligtvis är öppna för någon form av nätverkskommunikation genom att antingen få tillbaka ett SYN+ACK eller inte.
En intressant säkerhetsaspekt då är att istället för att enbart försöka skydda mot ens sessionskakor blir stulna och missbrukade så tänker jag snarare implementationer på front- & backendsidan för att vid senare tillfälle kunna återhämta - "data recovery strategies" - som följd av stulen och/eller missbrukad sessionskaka.
Den första implementationen - via frontend - är en annan inloggningssajt på annan enhet vars enda syfte är att kunna logga ut ens tidigare sessioner och möjligen även eventuellt kunna låsa sitt konto där för att sedan kunna få någon form av IT-support. Om jag minns rätt så har Göggel detta och säkert många andra IT-inriktade webbtjänster.
Den andra implementationen - via backend - är att endast tillåta exempelvis SELECT, INSERT och UPDATE för databasanslutningen vanliga användare använder sig av vid webbplatsbesök. Då blir lyckade SQL-injektioner som DROP, ALTER, CREATE, etc. meningslösa även om de kommer förbi Prepared Statements på något vis.
I samma veva kommer så klart frågan: "DELETE då?". Istället för faktisk DELETE av just den databasanslutningen så blir det istället en UPDATE på en boolean om att just den databastabellraden ska plockas bort manuellt efter manuell granskning. För UPDATE kan också den tidigare databastabellradens data finnas kvar för att kunna återställa om någon data skulle ha försökts ändras. Utöver detta tillkommer så klart klassiska 3-2-1-säkerhetskopieringstänket om nu allt på sjösatt backend skulle ha "utplånats" på något vis.
Mvh,
WKF.
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jag måste läsa igenom mitt tidigare så jag inte upprepar mig i onödan!
Jobb & Uppdrag
Om jag räknat rätt så var förrförrförra veckan fullspäckad med jobb från Stockholmskund medan veckan därpå mindre och förra veckan mycket lite jobb. Får se hur mycket det dyker upp imorgon från Stockholmskund. I skrivande stund så klev jag nyss av ett kort Teamsmöte med den än så länge mycket nöjda Uppdragsgivaren.
Det börjar hända saker såsom att Uppdragsgivaren har börjat bearbeta flera potentiella kunder och att de två personerna som önskade en webbappsversion av en annars mobilapp nu har lämnat in en affärsmodell som Uppdragsgivaren ska granska för att avgöra huruvida Uppdragsgivaren vill investera i deras affärsidé.
Uppdragsgivaren berättade också att de är i kontakt med ett svenskt bolag som omsätter hundratals miljoner SEK - #humblebrag - kan behöva hjälp med något internt system. Här kände jag mig tvungen att fråga varför de inte redan hade tagit itu med det i och med deras oförnekliga tillgängliga kassaflöde?🤔
Möjligen är det något lågprioriterat internt system som nu helt plötsligt måste fixas. Där skulle jag endast vilja vara en del i ett större kugghjul för det är inte rimligt överhuvudtaget att lilla juniora webbutvecklaren jag skulle kunna fixa något internt system åt ett svenskt mångmiljardbolag. (flåsviskar Skolplattformen mig nu i nacken?)
Ska väl vara om det interna systemet handlar om ett bokningssystem för vem som köper in nästa veckas fika!?😂 Hursomhelst, så hade varit oerhört kul och erfarenhetsmässigt matnyttigt att få vara en del av utvecklingen av något internt system där då förhoppningsvis seniora utvecklare redan finns som kan lära en ett och annat.
På tal om annat kul så berättade även Uppdragsgivaren under det korta Teamsmötet idag att det finns ett investmentbolag där de är med i vilket i sin tur har köpt andelar i ett annat slags investmentbolag vilket i sin tur nu söker samarbetspartners till ett unikt utvecklingsprojekt i staden där jag bor.
Förra gången så visade jag ju en .bat-fil som fixade med erhållna PDF-filer från min Stockholmskund. Jag har nu tagit fram två ytterligare med hjälp av gratisversionen av chatGPT4.
_2Move_PDF_Files_In_Created_Folders.bat
@echo off
cd /d "%~dp0"
setlocal enabledelayedexpansion
for %%f in (*.pdf) do (
rem Extract the file name without the extension
set "basename=%%~nf"
rem Create a folder with the same name if it doesn't exist
if not exist "!basename!" (
mkdir "!basename!"
)
rem Move the PDF file into the folder
move "%%f" "!basename!\"
)
echo Moved all PDFs into their respective folders.
pause
_3Copy_And_Move_HTML_And_DOC_Files.bat
@echo off
cd /d "%~dp0"
setlocal enabledelayedexpansion
rem Set the template file names (adjust these if your template names are different)
set "templateHTML=ISBN-Boknamn-v1-WKF.html"
set "templateDOC=ISBN-Info-BokNamnSVENSK-v1-WKF-Ljudbok.doc"
rem Loop through each directory (folder) in the current folder
for /d %%D in (*) do (
rem Store the folder name in a variable
set "foldername=%%D"
rem --- Copy the HTML file ---
rem The new copy gets the same name as the folder, with .html extension.
copy /y "!templateHTML!" "%%D\!foldername!.html"
rem --- Copy and rename the DOC file ---
rem We need to insert "-INFO" after the ISBN digits.
rem Use a for /f loop to split the folder name into the ISBN (digits) and the rest.
for /f "tokens=1* delims=_" %%a in ("!foldername!") do (
set "isbn=%%a"
set "remainder=%%b"
)
rem Construct the new DOC file name.
set "newDocName=!isbn!-INFO_!remainder!.doc"
copy /y "!templateDOC!" "%%D\!newDocName!"
rem ) <- End of optional check if needed.
)
echo Copies created for each folder.
pause
Dessa två .bat-filer och den från tidigare inlägg går sannolikt att slå ihop till en och samma enstaka .bat-fil. Det som sker då när alla dessa tre körs inuti samma mapp med PDF-filerna är att de namnformateras på särskilt vis och så skapas mappar och två filer kopieras in till vardera mapp där varje enskild PDF-fil sedan förflyttas in.
Tidigare gjorde jag detta manuellt vilket kostade minuter som annars kunde ha lagt på det faktiska arbetet. För jag debiterar ej tiden för att bara förbereda filerna även om det går att argumentera för att jag borde göra det.
En annan sak jag tog mig till att äntligen få gjort trots onödig rädsla var att ändra mitt GitHub-namn från WebbKodsLarlingen till WebbKodsFrilansaren. Jag trodde nämligen att jag skulle förlora mitt GitHub CoPilot-konto från universitetet som följd av namnbytet men inte än så länge! 😬
Någon gång i mars så kan det alltså dyka upp nya uppdrag från den än så länge nöjda Uppdragsgivaren! 🫡
Hobbyprojekt
Då jag numera hänger oftare på LinkedIn som följd av Uppdragsletandet så råkade jag se denna otroligt fräcka kodportfölj(?) på LinkedIn och jag blev så klart avis att jag själv inte har samma "designkänsla" som självutnämnd "fullstackutvecklare"! 🥲 Jag är duktig på att bygga vidare designmässigt med något som redan finns, men att börja från en tom "digital canvas" gör mig mentalt infantil sett till mina så kallade "designförmågor".
Jag har fått svar från personen gällande realtidskommunikationsapp men jag har inte besvarat det ännu. Det är så mycket jag ibland vill göra och att tankarna kring allt som skulle kunna gå och göra leder till att ingenting blir gjort. En slags självförvållad tankeförlamning?! Nu senast så blev jag lite "sne" på Bitwarden Vault som kräver kontoskapande men det verkar handla om att det är klient-server-baserat och det hela går att konfigurera och sjösätta för lokal användning.
En tanke jag har haft då är att skriva en simpel FireFox Extension som talar med en hemlig svensk NodeJS-server som stödjer WSS vilket i sin tur talar med en simpel React Native Expo-mobilapp vilket i sin tur innehåller en dekrypteringsnyckel som används för att dekryptera inlästa QR-koder vilket är krypterade lösenord på utskrivna papper.
På så vis skulle inget lagras på datorn då FireFox Extension skulle erhålla dekrypterat lösenord vilket den sen stoppade in i förvalda inmatningsfält för inloggning med kontroller innan att dessa inte finns inuti något iframe-element eller att något iframe-element överhuvudtaget existerar på webbplatsen (vilket kanske då kan begränsa dess användbarhet). Andra kontroller är också att det faktiskt är rätt webbplats i domänet förknippat med lösenordet så QR-koden kan innehålla information om webbplatsen och lösenordet fördelat med en |-tecken och så kan FireFox Extension kontrollera att det är rätt webbplats vars lösenord försöks skickas för.
Då störde jag mig på en annan sak när jag tänkte på det hela: "Varför kan jag se erhållen JSON-data i klartext via Network Monitor i Web Developer Tools i FireFox?" Så när FireFox Extension gjort sin grej med erhållet dekrypterat lösenord så vill den få "variabelsophämtaren" (eng. garbage collector) i JavaScript att agera blixtfort så inget skulle kunna läcka ut ur webbläsaren. Här verkar sätta variabeln till null att kunna få vissa JS-motorer att då utföra sin "variabelsophämtning" snabbare än vanligt? 🤔
Å andra sidan så har jag nog värre problem om minnesläckage från webbläsaren är något jag oroar mig över. En sak du säkert nu funderar över är vad som lagras på den hemliga svenska servern för hur ska rätt användare kunna ta emot rätt kopplade dekrypterade lösenord från de fysiska QR-koderna? Jo, det blir liknande simpel autentisering precis som Bitwarden Vault kör med.
Vill jag nu med underförstådd fräckhet försöka påstå att min lösning är bättre och/eller säkrare än Bitwarden Vault? Absolut inte. Jag kör med påtvingad HTTPS, WSS, och den största faran är egentligen mjukvaran i Android-telefonen där React Native Expo-appen ska skicka iväg dekrypterade lösenord i klartext för det skulle ju kunna ske en MITM på OS-nivå om vi leker konspiratörens lilla djävulska advokat! 😈😏
Vidareutbildning
Jag tittar just nu på djupdykning i LLM:er på YT vilket faktiskt går igenom rätt så överskådligt och samtidigt rätt konkret hur det går från insamling av data, förträning och sedan efterträning av vad som senare blir LLM:er.
Annars så har jag bara kikat på 3Blue1Browns videoklipp om neurala nätverk och LLM:er vilket går mer in på matematiken med matriser och sådant men avsaknad av att faktiskt kunna göra något konkret av det hela efteråt. Python verkar vara det primära språket om en vill pyssla med AI-relaterad mjukvaruutveckling.
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jag glömde ta med två saker från gårdagens uppdatering!
Detta inlägg markerar inlägg #69 vilket betyder:
Nästa milstolpar är #420 respektive #1337!
Jobb & Uppdrag
Igår efter avslutat Teamsmöte med den än så länge mycket nöjda Uppdragsgivaren så var det en samarbetspartner där som berättade om något projekt i USA som kanske ska komma till Sverige och att det då kan finnas programmeringsbehov för att kunna leverera samma webbaserade webbtjänst. Personen är i kontakt med någon kontaktperson där som då kanske vill ta det hela till den svenska marknaden.
För flera veckor sedan så rapporterade jag om två potentiella uppdrag via Slack-grupper jag är med i: Båda handlade om att koda på distans åt IT-bolag. Dessvärre fick jag - icke-förvånande nog - avslag på båda. Ena svarade att, "Tyvärr kan jag ej hjälpa dig!", medan andra efter ett tag svarade att dess slutkund önskat sig en mer erfaren utvecklare så därför valdes jag bort.
Ework ger ju inte heller några gladare nyheter med varje tillgängligt uppdrag som kräver flera års erfarenhet som om det alltid är ett garanterat mått på ett bra utfört jobb. Självklart är det mycket mer vägledande än vad jag vill påstå att jag kan (lära mig) än så länge! 😬
Men jag ska inte gnälla mer om den saken för det är jag som har valt den väg jag har valt efter avslutade Webbutvecklingsstudier - ingen har tvingat mig till det så! 🫡
Förhoppningsvis dyker det upp lite jobb idag från Stockholmskunden innan månadens faktura som vanligt!
Hobbyprojekt
På morgontvisten idag har jag "ordbråkat" med Gemini 2.0. Flash Thinking Experimental om kryptografiskt säkra algoritmer för att skapa huvudnycklar som sedan konverteras till en partiell nyckel där med hjälp av ett inmatat lösenord kan återställa huvudnyckeln på nytt.
Min idé var att efter att huvudnyckeln skapats som kan användas för att kryptera och dekryptera krypterade QR-koder så kommer en partiell nyckel att skapas i samband med valt lösenord där själva lösenordet på något kryptografiskt vis innehåller "instruktionerna" för att kunna återställa huvudnyckeln igen.
Först var min tanke att på något vis kunde det inmatade lösenordet - exempelvis "PLiXaa" - innehålla bokstäver som motsvarar positioner i huvudnyckeln vars tecken skulle plockas bort och som kunde stoppas in igen efter korrekt inmatat lösenord. Då måste så klart lösenordet vara tillräckligt långt samtidigt som det bör vara lätt att komma ihåg och antalet teckenpositioner måste vara tillräckligt utifrån lösenordets längd.
Här är problemet som jag ser det dock: Om jag skapar en huvudnyckel och sedan ett lösenord som jag vill återanvända för framtida nya huvudnycklar så kommer positionerna att bli lika då de utgår från lösenordets värden för varje bokstav men huvudnyckelns tecken vid samma positioner kommer ju att vara helt olika?!
Exempel av pseudokod (skrevs just nu i inlägget):
// Huvudnyckel
masterkey = "3F1E4B9D2A5C8BFAD7C39EF2076B2D1A";
masterkeyB64 = "Px5LnSpci/qXw57wHa0a";
// Lösenord som använder matchande tecken
// från Base64-versionen av huvudnyckeln.
userpassword = "PLiXaa";
// Kod som tar bort tecknen i den ordning som de förekommer
// i masterkeyB64 och sedan lagrar det nya:
partialkeyB64 = "x5nSpc/qw57wH0";
// Lagra nu den partiella nyckeln på Android-telefonen
secureStorage(partialkeyB64);
Lägg märke till problematiken här: "Hur ska någon funktion veta var någonstans dessa bokstäver från "userpassword" ska sättas in i "partielkeyB64" innan den konverteras tillbaka till hexadecimalhuvudnyckeln igen?" 🤔 Det går ju att lösa det genom att även läsa in positionerna för tecknen vid återskapandet av huvudnyckeln.
Exempel på UPPDATERAD pseudokod som lösning (skrevs just nu i inlägget):
// Huvudnyckel
masterkey = "3F1E4B9D2A5C8BFAD7C39EF2076B2D1A";
masterkeyB64 = "Px5LnSpci/qXw57wHa0a";
// Lösenord som använder matchande tecken
// från Base64-versionen av huvudnyckeln.
userpassword = "PLiXaa";
// Kod som tar bort tecknen i den ordning som de förekommer
// i masterkeyB64 och sedan lagrar det nya:
partialkeyB64 = "x5nSpc/qw57wH0";
// Sifferkod som markerar positionerna för tecknen
partialkeyPositions = [1,4,9,12,18,20];
// Lagra nu den partiella nyckeln på Android-telefonen
// Vi lagrar ej nyckelpositionerna för de ska minnas precis som lösenordet.
secureStorage("pk",partialkeyB64);
Men då kommer vi till det klassiska problemet igen: "Hur kommer man ihåg både koden och sifferkombinationen som är så himla lång?" 🤔 Nästa steg blir då att låta en matematisk funktion få använda simpel logik så bara ett helt nummer behövs kommas ihåg:
Exempel på UPPDATERAD IGEN pseudokod som lösning (skrevs just nu i inlägget):
// Huvudnyckel
masterkey = "3F1E4B9D2A5C8BFAD7C39EF2076B2D1A";
masterkeyB64 = "Px5LnSpci/qXw57wHa0a";
// Lösenord som använder matchande tecken
// från Base64-versionen av huvudnyckeln.
userpassword = "PLiXaa";
// Kod som tar bort tecknen i den ordning som de förekommer
// i masterkeyB64 och sedan lagrar det nya:
partialkeyB64 = "x5nSpc/qw57wH0";
// Sifferkod som markerar positionerna för tecknen fast vi tar bara
// högra siffran efter 9 så 2 är egentligen 12, 8 är 18 och 0 är 20!
// Här har vi då någon användning av modulus i praktiken? 🤔
partialkeyPositions = 149280; // minns som "149 280"
// Lagra nu den partiella nyckeln på Android-telefonen
// Vi lagrar ej nyckelpositionerna för de ska minnas precis som lösenordet.
secureStorage("pk",partialkeyB64);
Pseudokoderna ovanför är skrivna från scratch ovan för de reflekterar mitt stegvisa tänkande medan jag "ordbråkade" med att förstå mig på vad Gemini 2.0 Flash Thinking Experimental menade med att ett "hashat" lösenord kunde innehålla instruktionerna för hur med en partiell nyckel i kombination med lösenordet tagits fram från den först genererade huvudnyckeln.
I "kodexemplen" från denna gratis LLM så verkade den insistera på att algoritmlösningarna kunde komma ihåg exakt vilka tecken - även när ny huvudnyckel skapats och samma lösenord använts - som skulle stoppas in på rätt plats för att kunna återgenerera huvudnyckeln utifrån partiell nyckel plus användarinmatat lösenord.
När jag frågade exakt vilka algoritmer som användes för det och hur det kunde se ut kodmässigt så blev den så rädd att den skrek i sina kommentarer om "EJ PRODUKTIONSFÄRDIG KOD - LIVSFARA - ANVÄND EJ!!!" och jag blev så trött på den. Jag kände mig behandlad som någon som aldrig tas seriös på grund av fördomar! 😂
Jag försöker ju inte återskapa faktiska kryptografiska funktioner utifrån matematiskt bevisade teorem utan snarare nyttja industristandarderkända kryptografiska kodbibliotek i ett särskilt flöde som gör det lite säkrare om än mer mentalt krävande än att bara komma ihåg ett lösenord. Vad det verkar handla om är KDF eller Key Derivation Function där tanken är att generera nycklar utifrån en given funktion och den funktion ifråga jag söker är funktionen som kan kombinera en partiell nyckel med inmatat lösenord för att på så vis återgenerera huvudnyckeln.
Om Du vet exakt vad det är för kryptografiska funktioner jag är ute efter att kombinera här så får du gärna berätta vilka algoritmer jag verkar försöka syfta på och om möjligt - ange gärna vilka standardbibliotek som rekommenderas för detta. När jag ber LLM:en om den så blir den så rädd och orolig att den vägrar ge mig några konkreta svar och den tror att jag ska sjösätta skrattretande dåligt skräddarsydda kryptografiska funktioner kodat med förbundna ögon med nanoGPT?! 🫠
Jag vet ju att huvudnyckeln ska vara tvåvägs/symmetrisk(?) så den kan både kryptera och dekryptera medan jag förstår att lösenord faktiskt lagras envägs med hackningsfunktioner (eng. hash functions). Jag känner till "PBKDF2" som KDF och frågan är om det är den som på något vis ska kombineras med den partiella nyckeln och det användarinmatade lösenordet? 🤔
Vidareutbildning
Inget nytt att rapportera ännu.
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jobb & Uppdrag
Förrförra samt förra veckan fick jag gott om jobb från Stockholmskunden medan denna vecka så blev det avsevärt mycket mindre. Förhoppningsvis blir sista veckan denna månad tillräckligt för att inte behöva börja blöda buffert.
Jag har nu två .bat-filer som innehåller alla de tre första stegen när jag mottagit jobb från Stockholmskunden och sedan för att kunna automatisera saker när jag själv vet antalet sidorna inuti varje PDF-fil:
rem "_123_Prepare_Ebooks.bat"
@echo off
setlocal enabledelayedexpansion
rem Step 1: Remove "PDF_" prefix
for %%f in (PDF_*.pdf) do (
set "filename=%%~nxf"
set "newname=!filename:PDF_=!"
ren "%%f" "!newname!"
)
rem Step 2: Replace spaces with underscores
for %%f in (* *.pdf) do (
set "filename=%%~nxf"
set "newname=!filename: =_!"
ren "%%f" "!newname!"
)
rem Step 3: Add "-WKF-s" to the end of all filenames
for %%f in (*.pdf) do (
set "filename=%%~nxf"
set "basename=%%~nf"
set "newname=!basename!-WKF-s.pdf"
ren "%%f" "!newname!"
)
echo Removed _PDF and whitespaces and added WKF with s at the end.
cd /d "%~dp0"
for %%f in (*.pdf) do (
rem Extract the file name without the extension
set "basename=%%~nf"
rem Create a folder with the same name if it doesn't exist
if not exist "!basename!" (
mkdir "!basename!"
)
rem Move the PDF file into the folder
move "%%f" "!basename!\"
)
echo Moved all PDFs into their respective folders.
rem Set the template file names (adjust these if your template names are different)
set "templateHTML=ISBN-Boknamn-v1-WKF.html"
set "templateDOC=ISBN-Info-BokNamnSVENSK-v1-WKF-Ljudbok.doc"
rem Loop through each directory (folder) in the current folder
for /d %%D in (*) do (
rem Store the folder name in a variable
set "foldername=%%D"
rem (Optional) You could add a check here if you only want to process folders that contain a PDF
rem e.g. if exist "%%D\*.pdf" (
rem --- Copy the HTML file ---
rem The new copy gets the same name as the folder, with .html extension.
copy /y "!templateHTML!" "%%D\!foldername!.html"
rem --- Copy and rename the DOC file ---
rem We need to insert "-INFO" after the ISBN digits.
rem Use a for /f loop to split the folder name into the ISBN (digits) and the rest.
for /f "tokens=1* delims=_" %%a in ("!foldername!") do (
set "isbn=%%a"
set "remainder=%%b"
)
rem Construct the new DOC file name.
set "newDocName=!isbn!-INFO_!remainder!.doc"
copy /y "!templateDOC!" "%%D\!newDocName!"
rem ) <- End of optional check if needed.
)
echo Copies created for each folder.
pause
rem "_1234_AddPageNumbersToFiles.bat"
@echo off
setlocal enabledelayedexpansion
rem Function that renames all files in current directory with the provided page number (%1)
:RenameFiles
set "current=%1"
for %%f in (*.*) do (
if /i not "%%~nxf"=="_4AddPageNumbersToFiles.bat" (
set "filename=%%~nxf"
set "newname=!filename:WKF-s=WKF-%current%s!"
ren "%%f" "!newname!"
echo Renamed "%%~nxf" to "!newname!"
)
)
rem Loop through all directories and use the indexed array value using the i variable as counter
for /d %%d in (*) do (
echo.
echo Entering folder: %%d
set "folderName=%%d"
set "temp=!folderName:-WKF-=#!"
for /f "tokens=2 delims=#" %%x in ("!temp!") do set "extractedNumber=%%x"
set "extractedNumber=!extractedNumber:s=!"
echo Extracted Number: !extractedNumber!
pushd "%%d"
call :RenameFiles !extractedNumber!
popd
echo Exited folder: %%d
)
echo Added Page Numbers to Files.
endlocal
pause
En annan sak jag försökt automatiserat med AutoHotKey (AHK) är att kunna lägga in kommentarer snabbare i PDF-filerna men dessvärre så blir det fel med hur AHK tolkar x- & y-koordinaterna inuti Adobe Acrobat-klienten.
Igår så hade jag min allra första telefonintervju som självutnämnd IT-frilanskonsult med en ny potentiell Uppdragsgivare inom webbutveckling vilket kom som resultatet av en person jag har kontakt med i en viss Discord-grupp.
Jag fick frågor om vad jag pysslade med om dagarna, även min tidigare universitetsutbildning inom folkhälsa vilket ej är relevant längre. Men den nya potentiella Uppdragsgivaren tog upp det då det möjliga uppdraget på mellan 1-2 månader på antingen del- eller heltid med ett arvode på mellan 500-600 kr/tim är just inom hälsa.
Denna Uppdragsgivare väntar dock själv på finansieringsbesked från slutkund och utan det så blir det inget uppdrag där jag skulle kunna bli den valda frontend-utvecklingskandidaten vilket är vad rollen skulle infatta. Frontend-utveckling inom senaste versionen av ReactJS och React Native. (ja, en webbapplikation plus en mobilapplikation!)
Den nya potentiella Uppdragsgivaren är medveten om att jag är en nyutexaminerad junior fullstacks-webbutvecklare sedan maj 2024 och har erhållit min GitHub där jag då lagt upp tidigare relevant ReactJS-projekt. Personen kommer att komma med återkoppling om ett par veckor även om det innebär att bara meddela att de gått vidare med en annan kandidat.
Hobbyprojekt
Tre icke-rekursiva funktioner för att hitta ett bestämt Fibonacci nummer - utan LLM-hjälp!
När jag skummade igenom en mycket förenklad programmeringskurs i Python så kom ämnet rekursion upp i kursen och det klassiska exemplet på att hitta Fibonacci nummer x. Problematiken här är så klart att med rekursion så blir det stacköverflöde omedelbart.
Kursen bad en då att pausa och fundera hur en icke-rekursiv funktion kunde skrivas för att hitta Fibonacci nummer x. Jag lyckades då skriva tre olika icke-rekursiva funktioner som kan hitta Fibonacci nummer x vid anrop utan att drabbas av stacköverflöde - och detta utan någon hjälp av LLM. Däremot googlade jag upp metoderna för arrayer i JS. Självfallet kraschar även dessa implementeringar vid tillräckligt stora Fibonacci nummer som sökes.
De tre olika varianterna är:
1. Endast med primitiva variabler.
2. Array utan stack.
3. Array med stack.
const fibb = 6;
console.time("test");
console.log("Fibbo PRIM nr("+fibb+"): " + fibboPrim(fibb));
console.timeEnd("test");
function fibboPrim(n)
{
let tempPrev = 1;
let tempNext = 1;
let twoLast = 0;
if(n == 0) {return 0;}
else if(n == 1 || n == 2) {return 1;}
for(i=2; i < n; i++)
{
twoLast = tempPrev+tempNext;
tempPrev = tempNext;
tempNext = twoLast;
console.log("TempPrev: " + tempPrev + " | tempNext: " + tempNext + " | twoLast/Sum: " + twoLast);
}
return twoLast;
}
console.time("test2");
console.log("Fibbo ARR nr("+fibb+"): " + fibboArray(fibb));
console.timeEnd("test2");
function fibboArray(n){
const arr = [0,1,1];
if(n == 0) {return 0;}
else if(n == 1 || n == 2) {return 1;}
for(i=2; i < n; i++)
{
arr.push(arr[arr.length-1]+arr[arr.length-2])
}
return arr[arr.length-1];
}
console.time("test3");
console.log("Fibbo STACK nr("+fibb+"): " + fibboStack(fibb));
console.timeEnd("test3");
function fibboStack(n){
let num = 0;
const stack = [1,1];
if(n == 0) {return 0;}
else if(n == 1 || n == 2) {return 1;}
for(i=2; i < n; i++)
{
let next = stack.pop();
let prev = stack.pop();
num = next+prev;
if(i > n){break;}
stack.push(next);
stack.push(num);
}
return num;
}
Jag kallar första funktionen för primitiva tal då jag endast "växlar" med att lagra de två senaste numren som sedan då kan generera nästa nummer tills önskvärd Fibonacci nummer x har uppnåtts eller programmet kraschat.
Observera i den tredje funktionen där en array med stack tillämpas - det vill säga kika i loopen. En liten detalj för att verkligen "optimera" användningen av stackens storlek. Den andra funktionen är den som visades upp som alternativ icke-rekursiv funktion men där insåg jag då att då växer arrayen och därmed rymdkomplexiteten (eng. space complexity).
Frågan är då istället om det bara blir något annat som ökar i Ordo i och med elementen som tas bort och läggs till i array-stacken? 🤔 Om jag minns rätt så är Array.pop() och Array.push() båda Ordo(1).
9 Månader sedan senast Git Commit i projekt efter studierna!?
I skrivande stund är det exakt nio månader sedan jag gjorde mitt första Hobbyprojekt på GitHub efter avslutade webbutvecklingsstudier på distans. Se första bilden nedan:
Jag hade en väldigt omfattande katalog- och filstruktur för min egen del tycker jag:
Det av intresse är hur jag nu när jag tittar tillbaka på vissa delar är att jag nu förstår viss kod mycket bättre nu än jag gjorde då. Koden var "kommentarsskriven LLM-kod" vilket betyder att jag skrev kommentarer och sedan autoifylldes resten av CoPilot:
Koden från de två ovanstående bilderna visar en funktion vars "enda syfte" egentligen var att matcha en URI-rutt vare sig det är dynamiskt (t.ex. "/users/{id}") eller statiskt (t.ex. "/login").
Dessvärre gör funktionen lite för mycket och en kodrad jag först inte kunde förstå riktigt för nio månader sedan även fast jag kunde läsa den var raderna 79-80 där den först ersätter alla {dynamiska_parametrar_i_rutten} med Regex-vänliga strängvärden för att sedan testa att matcha nuvarande inkommen URI-rutt mot den.
På något vis så "fattade" jag inte riktigt första raden (79) trots att jag läste stirrandes på den. Nu "kopplar" det helt enkelt även fast jag fattade vad båda raderna tillsammans gjorde för syfte. Det var nog att jag inte bara kopplade exakt hur* det gick till.
(* Sanning med modifikation så klart eftersom jag varken förstår Zend-motorn ut och in och/eller den faktiska maskinkoden som körs!😬)
Hursomhelst, vad som behövs för det 99 % funktionsbaserade och mycket avskalade PHP-ramverket är lustigt nog smalare funktioner - mer åt "Single Responsibility"-hållet.
Tanken är att endast matcha en hårdkodad rutt mot den nuvarande URI-rutten även om den hårdkodade rutten ifråga har dynamiska strängar inuti sig. Sedan när det är klart så ska andra funktioner hantera saker som att extrahera information om vad som då gäller för den matchade rutten vilket nu denna rutt försöker hålla på med vilket blir för mycket för att hålla reda på inuti samma funktion upplever jag.
Med andra ord kan en annan funktion extrahera alla konfigurerade variabler till den matchade rutten eftersom den nu är känd och så kan en ytterligare annan funktion extrahera potentiella {dynamiska_parametrar_värden} från den matchade rutten då detta brukar behövas för att sedan dynamiskt kunna hämta relevant databasdata.
En annan sak jag funderat på är något jag sett på YT sedan nio månader gått vilket är att, "Börja med att bara få det att fungera", dvs., MVP eller kanske MLP för att krydda till det hela lite. När jag började med detta projekt för nio månader sedan så kunde jag fundera på allt jag ville skulle fungera direkt vid första steget i "Matcha rutt - hämta data - svara med förrenderad HTML".
Exempelvis ville jag redan kontrollera mot databas för autentisering trots att jag inte ännu hade funderat ut på alla möjliga former av autentiseringsstöd, rollbaserad autentisering, hur tabellerna skull se ut standardmässigt, och så vidare.
Det får helt enkelt bli en mindre sak i taget och sedan bara få ihop den så kallade "Hello World!"-versionen av RDP vilket jag kallar projektet för vilket står för "RouteDataPage" då flödet på webbplatser tenderar att följa den vägen. (ja, vissa har bara RouteData i fallen med API:er!)
Vidareutbildning
En sak jag tänkt på gällande "Konsten att tänka som en programmerare" är när vi zoomar in mer på "algoritmtänkandet", dvs., när vi väl ska tackla ett givet mindre problem som vi fått fram genom att delat upp ett större problem i mindre och sannolikt mer lösbara problem.
Jag reflekterar tillbaka till något YT-klipp jag såg om "Vanliga olika algoritmmönster för att lösa majoriteten av Leetcodes" och när jag såg det så inser jag nu att det är ju inte bara för det ändamålet: Det är ju faktiska "stegmönster" att följa i samband med framtagningen av en given algoritm för att lösa ett mindre problem.
Ett mönster - fritt översatt på svenska av mig - kallas för "Skjutfönstret" vilket handlar om att föreställa sig ett "fönster som skjuts åt sidan och varje gång det skjuts åt sidan så kontrollerar du vad du fokuserat fönstret på".
Med andra ord? En loop där du kontrollerar ett bestämt antal steg framåt via index trots att indexet kanske bara ökar med 1 i taget. Exempel på där detta algoritmmönster kan vara användbart är om du vill kontrollera om något ord i en viss storlek finns inuti ett annat.
Säg att du vill kontrollera för tre tecken i taget då vill du kanske gå ett steg i taget för i i loopen för en sträng men du vill ändå kontrollera i+1 och i+2. Ja, innan detta vill du också kolla att strängen som kontrolleras är åtminstone tre tecken lång!😉
Och går vi ned på lite detaljer så blir det lite som att nästan varje algoritmmönster (en funktion som gör något med något mottaget argument i sin parameter och sedan returnerar ett värde) har följande komponenter pga. hur kod måste skrivas för att kunna kompileras/tolkas till maskinkod:
- Variabeln som innehåller argumentet eller variablerna för argumenten
- Initierad returvariabel som returneras via retursatsen
- Tillfälliga variabler som behövs för att kunna bearbeta argumentvariablerna
- En eller flera kontrollflöden (t.ex. loop-,while-satser, och/eller if-elseif-else-satser)
- I vissa fall går det att köra "en lång kedjad one-liner" (se vissa Advent of Code-lösningar)
Å ena sidan är detta rätt så "meh.... duh???!". Å andra sidan så tycker jag att detta är något som borde på något vis tas upp tidigt i kurserna om programmering för att nöta som bra riktlinjer. Du får data/bytes som du vill kunna bearbeta på något vis för att kunna returnera antingen ny eller ändrar data. Det sistnämnda fallet innebär referens som t.ex. &$variabel i PHP.
För att kunna bearbeta data du tagit emot så behöver du på något vis "representera" hur du ska bearbeta all data. I fallet med Fibonacci-numren så behövde jag "representera" två redan färdiga Fibonacci-nummer för att sedan kunna använda dessa för att skapa nästa Fibonacci-nummer tills att det önskade kunde hittas och därmed skickas tillbaka i retursatsen där returvariabeln fanns.
Av någon anledning så hade jag inte dessa riktlinjer införstådda i mig under min Webbutvecklingsutbildning utan det blev snarare något som jag kom på i efterhand med erfarenhet, nöta YT-klipp, nöta allmän info om programmering, läsa här på Sweclockers och på diverse andra programmeringssällskap ute på stora vida webben!
Om Du känner till något intressant och/eller vanligt "algoritmmönster" så får du jättehjärna dela med dig! \q[^_^]p/
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
En snabb, liten och snärtig uppdatering innan morgondagens vedertagna Första aprilskämt som vanligt... i framtiden!
Jobb & Uppdrag
Jag har varit borta på annan ort och arbetat med en rejäl veckas jobb från Stockholmskunden. Allt det klart och fakturerat för månaden. Nu är jag på hemmafronten igen redo för nya tag.
Inget nytt från den än så länge mycket nöjda Uppdragsgivaren om erhållet referensuppdrag vilket också skulle innebära ansökan om ny innovationscheck vilket jag skulle få ta del av för att utveckla en digital del inom området Uppdragsgivaren verkar inom.
Nästa månad så kommer jag att få återkoppling om huruvida jag blir frontend-utvecklaren för ett 1-2 månaders uppdrag 100 % på distans antingen hel- eller deltid. Den potentiella Uppdragsgivaren vilket även då kommer att agera som mellanhand mellan mig som utvecklare och deras slutkund kommer att meddela mig på något vis huruvida jag blev antagen eller ej och jag tar ej illa upp om någon annan väljs framför mig.
Denna månad fick jag snabbt besked från en person om möjligt kort referensuppdrag inom tillgänglighet då denna person har en slutkund som behövde "externa tredjepartsögon" för att kontrollera webbtillgänglighet för några nätaffärer. Jag erbjöd mig men fick avslaget men tack för visat intresse. Inte helt oväntat, men alltid mer tråkigt än kul.
"Jag har en plan!", som Sickan brukar säga (har tittat på en hel del gamla Jönssonligan-filmer med nära och kära på sistone). Planen är minst sagt potentiellt kontroversiell då jag ämnar till att "Differentiera eller dö" (Jack Trout-bok) från andra juniora fullstacks-webbutvecklare där jag erbjuder mig att ta mig an korta men signifikanta referensuppdrag för att kunna ha något att konkurrera med mot mer erfarna juniorer och/eller seniorer. (Jag vet; "dröm vidare, hörrö va?"😂)
Första steget i planen är att faktiskt få några portföljprojekt gjorda som är utöver det vanliga. Jag har två saker: först det PHP-baserade 99 % funktionella ramverket. Det andra är ett faktiskt projekt jag kommer att vilja nyttja och som jag tror andra kommer att vilja nyttja (till skillnad från PHP-grejen vilket troligen är mer "for the memes"?).
Det andra handlar om en lokal LLM som talar med någon GUI-klient (Python eller Electron? Vet ej vad som blir enklast/snabbast) där du i GUI-klienten väljer mappar med bilder och sedan skickas varje bild till din lokala LLM (t.ex. Gemma 3 27B Instruct) som sedan skickar tillbaka en fördefinierad JSON-sträng med allmän beskrivning av bilden, filnamnet, vilken mapp den ligger i, samt förvalda etiketter.
Tanken är sedan att du ska kunna söka efter bilder utifrån olika etiketter och/eller ord som finns i någon beskrivning - men allt ska ske lokalt av uppenbara sekretesskäl. I och med JSON-data som även innehåller filnamn och vilken mapp (kanske t.o.m. absolut filsökväg) så ska det även gå att exportera det för att möjliggöra för import till liknande program i framtiden. Då kan självfallet dataomvandling ske i JSON-strukturen för att bli kompatibelt med andra förutbestämda JSON-datastrukturer i andra liknande program.
Det där programmet tror jag bannemig flera av mina nära och kära skulle ha nytta av med tanke på deras tusentals bildarkiv av liknande bilder men ingen organisering eller förmåga att söka efter det ena eller andra. Mycket garanterat lär sådant här redan finnas men det vore en fräck grej att få till som kan köras lokalt av egenvald LLM vilket betyder att det kan bli bättre i takt med bättre modeller! 😎
Andra steget i planen är sedan att när dessa två projekt är färdiga så blir det att publicera detta såväl som att göra min röst hörd på diverse kanaler (Slack och LinkedIn bland annat) om att jag letar efter korta referensuppdrag i egenskap av junior fullstacks-webbutvecklare och vänligen spana in mina förhoppningsvis smått "imponerande" sidoprojekt varav ena även använder lokala LLM:er utan att enbart bara vara någon löjlig ChatGPT-wrapper du skriver mot vilket är så 🙄 enligt mig!
Tredje steget i planen är att fortsätta med det jag redan gör och ta mig an YouTube, Fiverr och UpWork vilket är egentligen det läskigaste steget i hela planen för då måste jag äntligen(?) visa mitt runda tryne för resten av omvärlden - på gott och ont! 🤣
Det blir både som jag gör det till och som det blir i samma veva! 🫡
Hobbyprojekt
På tal om sido-/hobby-/planprojekten nödvändiga för den geniala "planen planerad in i minsta detalj" så har jag tänkt ut själva DX-biten (eng. Developer Experience = Utvecklarupplevelsen) i PHP-hobbyprojektet. En sak som jag stört mig smått på i olika ramverk personligen är att det är rörigt med mappar och filer och det finns ingen uppenbar logisk struktur förrän du lärt dig den.
Tänk då - trots att det kommer att svida i ögonen och inte vinna några fina självgodhetsknarkande designpriser - om du enkelt kunde konfigurera i olika steg för varje Rutt -> Data -> Sida? Det heter ju Route Data Page eller "RDP" förkortat. Ja, i vissa fall vill du bara returnera data direkt utan någon sida (t.ex. API:er) men det är inga problem för när du returnerar något med din echo så är du klar med den nuvarande HTTPS-förfrågan.
Så filstrukturen där du som utvecklare då sitter och konfigurerar innan du skriver din egen data/controller och/eller sida/view består av (möjligen kan detta bli lite annorlunda och få understeg t.ex. step1.1, step1.2 etc.,):
- "step0_session_and_db.php",
- "step1_ip_filtering.php",
- "step2_rate_limiting.php",
- "step3_route_matching",
- "step4_authentication_and_authorization.php",
- "step5_data_matching.php",
- "step6_data_validation.php"
- "step7_page_matching.php"
- "step8_page_include.php"
- "step9_profit.php"
(Ha lite kul för bövelen! )
En annan sak är det allmänna designmönstret. Tanken är ju att det är funktioner som returnerar värden och vad för mönster på returvärdena är att föredra här? Jag tog hjälp av Gemini 2.0. Thinking-gratisversionen och kom sedan fram till när jag duschade tidigare idag att vad som ska returneras är antingen:
// När det är OK (ej felmeddelande)
return [data => null];
// När det INTE är OK (felmeddelande)
return [err => "[Funktionsnamn]: Felet enligt funktionen"];
// Funktion att kontrollera om vi kan anta data/OK eller fel/inte OK!
<?php
function ok($var) {
return !isset($var['err']);
}
?>
Notera en viktig sak här och en extrapolering jag gör: Antingen får du tillbaka nyckeln data vilket kan vara null så vi kan INTE använda isset() på den utan istället använder vi den på err för antingen får vi tillbaka data eller så vet vi per automatik utifrån det allmänna designmönstret med alla funktioner att är något fel någonstans i körflödet av funktioner. err-nyckeln kommer aldrig att vara null och därmed kommer isset() att fungera korrekt.
Detta betyder också att det är upp till utvecklaren hur de vill hantera detta. De kan själva bestämma under data- och/eller sidstegen hur de vill betrakta olika felmeddelanden. Tack vare att alla err-nycklar alltid börjar med funktionsnamnet inuti hakparenteser och sedan ett kolon därefter så kan de kontrollera utifrån det och visa egna felmeddelanden eller bara helt annat.
Ta exempelvis att det inte kunde hämtas något från databas så kan bara allmänt visas om att det misslyckades att hämta men om det är ett API så kanske något mer specifikt vill anges. Detta kommer att - Famous Last Words™ - fungera alldeles utmärkt med detta tänkta designmönster jag har bestämt mig för. Det påminner om Result Pattern om LLM:erna har "tokeniserat"rätt.
Mer om LLM-Bildigenkänningsprogrammet vid ett senare tillfälle!
Vidareutbildning
För länge sedan när jag använde ett bibliotek för att hantera uppladdade Excel-filer för att hämta kontaktuppgifter utifrån bestämda celler och kolumner så trodde jag att jag bara kunde gå in i källfilerna och omvandla det till en [i]simpel™[i] funktion.
Problematiken som uppstod då var att jag har ingen aning - vilket visar hur lite jag egentligen fortfarande kan om programmering - om hur man ska "tugga igenom"/"bearbeta" Excel-filer för att kunna hämta ut det en vill ha. Först upptäckte jag att det finns något som heter Magic Bytes vilket oftast(?) är i början av filerna för att ange filtypen så att program kan tolka dem korrekt.
Liknande problematik uppstod för något tag sedan då jag funderade på hur jag kunde nyttja Powershell för att utläsa antalet sidor i PDF-filer. Enligt diverse LLM:er så finns det ingen specifik byte eller bytes i PDF-filer som berättar om antalet sidor utan detta måste härledas genom att "tugga igenom" PDF-filen ifråga.
Dessutom vet jag från erfarenhet med hundratals PDF-filer att paginering kan vara olika: först kan det börja med romerska siffror, sedan vanlig paginering, och i vissa fall kan det bara bokstäver med nummer som t.ex. A1, A2, A3, B1, B2, och så vidare. Samtidigt kan diverse olika PDF-program lyckas komma fram till rätt antal PDF-sidor så någonstans måste det finnas en korrekt väg att bearbeta alla dessa strömmar av bytes inuti respektive PDF-fil (såväl som i Excel-filer).
Kikar jag på YT-klipp om "parse excel php" eller "parse pdf php" så blir det så klart det du misstänker nu: Exakt! Endast hur du använder färdiga bibliotek utan en suck förståelse om vad som egentligen händer nere på byte-nivå... Det är nästa steg i min "kodkarriär" - att börja arbeta med strukturer av bytes för det kommer även leda till att jag kan börja arbeta med andra strömmar av bytes (t.ex. video- och ljudströmmar vilket öppnar upp för apputveckling av saker och ting som folk faktiskt bryr sig om i vardagen!).
Om Du vet någon bra källa för att komma igång med att börja tugga igenom datastrukturer av bytes för olika filtyper för att lära sig "programmera på riktigt™" (jag skämtar!😬) så hör gärna av dig här!
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
100 % CERTIFIED SPAGHETTI CODE Feat. Leif Mannerström
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Inledningsvis vill jag buga, bocka och tacka för snart 10 000 visningar sammanlagt med cirka 100 visningar i snitt vid nytt mustigt blogg(forum)inlägg! \q[^_^]p/
Jobb & Uppdrag
Imorgon kommer jag att delta i nytt möte med den än så länge nöjda tidigare Uppdragsgivaren. Förhoppningsvis får jag vissa indikationer på om eventuellt nytt men troligen kortvarigt uppdrag.
Stockholmskunden kom med inget nytt jobb denna vecka - inget sent aprilskämt - så då började jag äntligen(!) lägga krut på vad jag kallar för FunkPHP (se hobbyprojektdelen för mer nedan).
Kommande veckor kommer jag att delta på något lokalt Dragon Tech nere vid stan såväl som digitala möten via så kallad "Kodfika" och "#Indiehackers". Möjligen kan komma att få visa upp dåvarande skicket av FunkPHP vilket är 100 % OSS.
Hobbyprojekt
På https://github.com/WebbKodsFrilansaren/FunkPHP finns det 100 % fria 99 % funktionsbaserade PHP-ramverket vars syfte är egentligen att ha något att visa upp och/eller prata väldigt arkitekturmässigt kring. Igår satt jag runt 8 timmar och idag har jag suttit 4 timmar med att få till en hel del.
I skrivande stund går det egentligen bara att göra följande efter lokalkloning med färst PHP-version 8.2 lokalt eller på valfri kompatibelt webbhotell:
- Konfigurera globala IP-adresser att blockera utifrån vad den börjar på, vad den slutar på, eller exakt matchning.
- Konfigurera globala User Agents att blockera utifrån om de innehåller de någonting i User-Agent-headern.
Det hela är 100 % spagettikod eller vad Leffe skulle kalla det för?🤔 I princip försöker jag ta fram den funktionsbaserade och konkurrerande PHP-ramverket mot det annars klassbaserade Laravel. Självklart lär jag aldrig nå dit utan att hinna bli vräkt men det är inte tanken.
Jag måste ju visa vad jag går för och ännu mer så nu när arbetsmarknaden snart kommer att överflödas Vibe-programmerare som okunniga IT-rekryterare kommer att råka anställa med panikångestfylld ånger kort mycket senare! 😅
De två huvdsakliga funktionerna att beakta i nuvarande mycket begränsade version av FunkPHP är följande:
// / PATH: src/funkphp/functions/h_helpers_funs.php
// This function works as a pipeline and processes the request through a series of functions
// The "outer" part means this is the one that takes the main step functions whereas
// the "inner" part means this is the one that takes the inner step functions such as
// the "o" (option(s)) key in the array each specific step function might come across!
function outerFunktionTrain(&$req, &$d, &$p, $globalConfig, $listOuterFunctionsNamesAsKeysWithTheirArgsAsAssociatedValues)
{
// Loop through "$listOuterFunctionsNames" and turn the function names into the key of corresponding
$fns = [];
// Populate the $fns array with function names, arguments, and initial return value
foreach ($listOuterFunctionsNamesAsKeysWithTheirArgsAsAssociatedValues as $functionName => $args) {
$fns[$functionName] = [
"fn_name" => $functionName ?? null,
"args" => $args ?? [],
"return_value" => "UNDEFINED",
"o_ok" => h_has_ok_options($args[0]) ?? [],
"o_fail" => h_has_fail_options($args[0]) ?? [],
];
}
// Now, you would typically loop through the $fns array (or based on a priority list)
// to execute the functions and update their return values.
// Pass reference to modify the original array!!!
foreach ($fns as $functionName => &$functionData) {
if ($functionData["fn_name"] == null) {
echo "<br>Function name is null for function $functionName!<br>"; // REMOVE LATER!!!
return fail("[outerFunktionTrain]: Function name is null for function $functionName!");
} else if (!function_exists($functionName)) {
echo "<br>Function $functionName does not exist!<br>"; // REMOVE LATER!!!
return fail("[outerFunktionTrain]: Function $functionName does not exist!");
}
$argsToPass = $functionData["args"];
$returnValue = call_user_func_array($functionName, $argsToPass);
$functionData["return_value"] = $returnValue;
// Check if user closed the connection (e.g., browser closed) and exit script so no further processing is done
if (connection_aborted()) {
break;
exit;
}
// Check current return_value that it is not "UNDEFINED" and also NOT "err" key but true or 1:
if ($functionData["return_value"] !== "UNDEFINED" && ($functionData["return_value"] === true || $functionData["return_value"] === 1)) {
// Call the optional "o_ok" functions if they exist
if (!empty($functionData["o_ok"])) {
h_run_ok_functions($functionData['o_ok'], $functionName, $req, $d, $p, $globalConfig);
}
}
// Check current return_value that it is not "UNDEFINED" and also NOT "err" key but false or 0:
else if ($functionData["return_value"] !== "UNDEFINED" && ($functionData["return_value"] === false || $functionData["return_value"] === 0)) {
// Call the optional "o_fail" functions if they exist
if (!empty($functionData["o_fail"])) {
h_run_fail_functions($functionData['o_fail'], $functionName, $req, $d, $p, $globalConfig);
}
}
// If value IS "UNDEFINED" here
else if ($functionData["return_value"] === "UNDEFINED") {
}
// Return value is "err" key here
else {
fail("[outerFunktionTrain]: Return value is an error key when running function $functionName!");
}
// REMOVE LATER!!!
echo "<br>Return value of $functionName: " . strval($functionData["return_value"]) . "<br>";
}
}
Den första ovan är kärnan i detta 99 % funktionsbaserade då den är en kombination av Pipeline Pattern + Strategy Pattern (sedan hur väl den fungerar i praktiken är en helt annan femma än de flashiga slagorden!🤣)
Tanken är följande: Den börjar med att plocka in all konfiguration från utvecklarslutanvändaren vilket redan hämtades med hjälp av en annan funktion (h_load_config($configArrayString)) vilket bara slår samman variabler utifrån $GLOBALS förutsatt att de existerar och är manuellt valda!
Funktionen går sedan igenom varje funktion och anropar relevanta funktioner med redan förvalda satta anropsargument. Vad som sedan sker är att den andra funktionen nedan (och en "h_run_ok_functions"-variant av den).
Exempelvis har du första variabel som du kan konfigurera:
$fphp_ips_filtered_globals = [
'denied' => [
"ip_starts_with" => ["127.0"],
"ip_ends_with" => [],
"exact_ips" => [],
"o_ok" => ["code=418", "ilog=Denied IP blocked!", "redirect=https://www.lol.com"],
"o_fail" => ["ilog=ok, IP is not blocked", "redirect=https://www.lol.com", "code=201"],
],
];
En vald funktion körs som hämtar "fphp_ips_filtered_globals"-nyckeln redan inbakad i den stora konfigurationsvariabeln $fphp_global_config och om funktionen returnerar sant så körs valfria funktioner inuti "o_ok"-nyckeln i en förkonfigurerad ordning! (detta kan utvecklarslutanvändaren välja själv).
Om samma funktionen istället returnerar falskt så är det "o_fail"-nyckelns funktioner som körs på liknande vis med en prioritetsordning som går att konfigurera efter egna önskemål. Exempelvis ser en sådan fil ut så här:
$fphp_o_fail_priorities = [
"r_match_denied_global_ips" => [
"ilog" => 1,
"code" => 2,
"redirect" => 3,
]];
Notera "r_match_denied_global_ips" vilket är namnet på funktionen som anropade och detta skickas med till den andra funktionen som du kan läsa mer om nedanför:
// / PATH: src/funkphp/functions/h_helpers_funs.php
// Two functions to run the fail and ok functions that are inside
// of the "o_ok" and "o_fail" options of the outer functions!
function h_run_fail_functions($fnNameWithArg, $callerName, &$req, &$d, &$p, $globalConfig)
{
$runPriority = $globalConfig['fphp_o_fail_priorities'][$callerName] ?? null;
$failFns = $fnNameWithArg ?? null;
if ($runPriority == null || $failFns == null) {
return fail("[h_run_fail_functions]: Optional Fail Function or its priorities not found for function $callerName.");
}
// failFns is an array where each element is "fnName=value" format so we need to iterate through it
// and split each element by "=" to get the function name and its argument(s)
$parsedFailFunctions = [];
foreach ($failFns as $failFn) {
$parts = explode("=", $failFn, 2); // Split by "=" and limit to 2 parts
if (count($parts) == 2) {
$parsedFailFunctions[$parts[0]] = $parts[1];
}
}
// Now we have an associative array where the key is the function name and the value is the argument(s)
// We need to order the functions based on their priorities. The final key sort (ksort) will be done later
// in the code so that we can run them in the order of their priorities.
$orderedArgs = [];
foreach ($runPriority as $fnName => $priority) {
if (isset($parsedFailFunctions[$fnName])) {
$orderedArgs[$priority] = [$fnName => $parsedFailFunctions[$fnName]];
}
}
// REMOVE LATER!!!
echo "<br>UnOrdered Args: <br>";
var_dump($orderedArgs);
ksort($orderedArgs);
echo "<br>Ordered Args: <br>";
var_dump($orderedArgs);
// REMOVE LATER!!!
}
Det den andra funktionen ovan och dess motsvarande variant vid positiva funktionsresultat är att den kollar i globala konfigurationsfilen efter funktionsnamnet och dess prioritetsordning av övriga funktioner som får köras och i sådana fall vilken ordning. Det fina där är då att du kan ändra/konfigurera prioritetsordningen på funktionsnivå!
Dvs., samma typiska övriga funktioner kan ha olika prioriteringar eller inte ens ingå för en annan funktion om så önskas. Du har säkert redan upptäckt att funktionen ovan inte anropar några funktioner ännu vilket blir nästa steg och så måste jag komma till fund med att bestämma vilken slags "Result Pattern" jag vill följa.
Alltså, ska allt antingen returnera err-nycklar vid fel/misslyckanden/falskt och data-nycklar vid framgångar/sant? Som vanligt så tycker jag fortfarande att det som tar längst tid är att hantera alla specialfall och att göra det säkert när användaren antingen med flit eller inte försöker sabba koden/skriva slarvig kod (ska jag säga!?😆)
Jag satt och "bråka" med Gemini 2.0 Thinking Flash OCH även Gemini 2.5 Pro i drygt någon timme om hur jag skulle kunna sortera följande till rätt prioriteringsordning där det som skulle köras i en viss ordnig också använde värdena efter delat likhetstecken(=):
// Tre funktioner som körs om deras "föräldrafunktion" returnerade sant eller 1
"o_ok" => ["code=418", "ilog=Denied IP blocked!", "redirect=https://www.lol.com"];
// I prioriteringsfilen när föräldrafunktionen (r_match_denied_global_ips) körts så ska dess konfigurerade funktioner prioriteras så här:
"r_match_denied_global_ips" => [
"ilog" => 1,
"code" => 2,
"redirect" => 3,
],
// Alltså vill vi få dataomvandlingen:
[1] => "ilog" => "Denied IP blocked!",
[2] => "code" => 418,
[3] => "redirect" => "https://www.lol.com",
Den lösning jag har ovan krävde först separera funktion från dess värde efter likhetstecknet och sedan var det jag bråkade med LLM:erna om hur jag skulle tilldela prioriteringsnycklarnas namn då deras värden är själva prioriteringarna.
Jag ville vid skapandet av varje arrayelement som t.ex. ["code" => 418] kunna tilldela [2] direkt framför det så det genast blev: [2] => "code" => 418 men det verkade inte gå först. Så jag fick köra två foreach-loopar för ändamålet. Varje extra onödig loop får Ordo()-gudarna att rynka på sina digitala ögonbryn om vi tänker tiotusentals anrop varje sekund och inte några F5-spam enbart.
Det här ville jag i princip kunna göra:
$runPriority = $globalConfig['fphp_o_fail_priorities'][$callerName] ?? null;
$parsedFailFunctions = [];
$finalFns = [];
foreach ($failFns as $failFn) {
$parts = explode("=", $failFn, 2); // Split by "=" and limit to 2 parts
if (count($parts) == 2) {
$parsedFailFunctions[$parts[0]] = $parts[1];
/// HÄR: Använd samma nyckelnamn t.ex. "ilog" från $runPriority som det index
/// variabeln $parsedFailFunctions[$parts[0]] ska få framför
/// sig så vi fårtill exempel: [2] => "code" => 418 | Sortera
/// sedan om så index-värdena blir i rätt ordning! Kanske:
$finalFns[$runPriority[$parts[0]]] = $parsedFailFunctions[$parts[0]];
}
}
Antingen går det jag vill göra eller så försöker jag göra det omöjliga rent programmeringsmässigt? 🤔
PRECIS NÄR JAG SKRIVER DETTA INLÄGG OCH RESONERAR: Så lyckas jag utan LLM-hjälp få fram:
function h_run_ok_functions($fnNameWithArg, $callerName, &$req, &$d, &$p, $globalConfig)
{
$runPriority = $globalConfig['fphp_o_ok_priorities'][$callerName] ?? null;
$okFns = $fnNameWithArg ?? null;
if ($runPriority == null || $okFns == null) {
return fail("[h_run_fail_functions]: Optional Ok Function or its priorities not found for function $callerName.");
}
// okFns is an array where each element is "fnName=value" format so we need to iterate through it
// and split each element by "=" to get the function name and its argument(s)
$parsedokFunctions = [];
$finalFns = [];
foreach ($okFns as $okFn) {
$parts = explode("=", $okFn, 2); // Split by "=" and limit to 2 parts
if (count($parts) == 2) {
$parsedokFunctions[$parts[0]] = $parts[1];
$finalFns[$runPriority[$parts[0]]][$parts[0]] = $parsedokFunctions[$parts[0]] ?? null;
}
}
ksort($finalFns);
}
Detta lyckades nu lösa det jag ville få löst. Anmärk nu: $finalFns[$runPriority[$parts[0]]][$parts[0]] = $parsedokFunctions[$parts[0]] ?? null; i vad jag ville göra som "inte ens" (i den utsträckning det är värt att påstå om LLM:er) Gemini 2.5 Pro kunde tillhandahålla trots att jag skrev väldigt långa och (tycker jag) exakta prompts.
Så kan det gå vid manuellt mänskligt resonerande samtidigt som jag skriver "bloggforuminlägg" och vilken tur att dessa träningsdata kan sparas i min hjärna utan något större watt-krav till skillnad från dagens LLM:er!😱😨😅
Avslutningsvis så är kodsnutten nedan den fil (funkphp_start.php) som allt omdirigeras till från .htaccess:
// PATH: /src/funkphp/funkphp_start.php
<?php // ENTRY POINT OF EACH HTTPS REQUEST thanks to ".htaccess" file
include_once __DIR__ . '/functions/_includeAll.php';
include_once __DIR__ . '/dx_steps/_includeAll.php';
// Load configurations and global variables
$fphp_global_config = h_load_config($fphp_all_global_variables_as_strings);
if (!ok($fphp_global_config)) {
return_code(418);
exit;
}
// Run the main function to handle the request which is a pipeline of functions
// where each function can also call optional functions to handle the request!
outerFunktionTrain(
$req,
$d,
$p,
$fphp_global_config,
[
"r_match_denied_global_ips" // Deny IPs filtering globally
=> [
$fphp_global_config['fphp_ips_filtered_globals'],
$req['ip']
],
"r_match_denied_global_uas" // Deny UAs filtering globally
=>
[
$fphp_global_config['fphp_uas_filtered_globals'],
$req['ua']
],
]
);
// This part is only executed if the request was not properly handled by the pipeline!
// Feel free to add your own error handling here and/or easter egg!
echo "YOU SHOULD NOT SEE THIS! SO ERROR!<br>";
Du är varmt välkommen att bajsa på min "kodparad" då jag är Junior-fullstacks-webbutvecklare vilket enligt så kallade "tankeledande internetkodare" innebär att jag är lika duktig på programmering som någon som aldrig ha kodat. Åtminstone är det så juniorer framställs på internet när någon måste syndabockas! 🙄
(Detta säger jag så klart med "cyniska glimten i sarkasm-ögat"! )
Det som förvånar mig mest nu med dramat här ovan är att varför kunde inte de mångmiljonkostsamma LLM:erna till slut komma med det förslaget? Var är extrapolerandet och/eller de emergerande programmeringsgenierna massa LLM-förespråkare hela tiden hävdar ska "sno våra jåbb!!"?
Vidareutbildning
Inget nytt att rapportera förutom att jag håller på att läsa en "hjärnvänlig" bok om mjukvaruarkitektur efter boktips i Slack-grupp. Det är Raju Gandhi, Mark Richards, Neal Ford (2024) - Head First Software Architecture A Learners Guide to Architectural Thinking och den sammanfattningsvisa kapiteldelen "The Two Laws of Software Architecture: Everything’s a Trade-Off" summerar väl det mesta det jag kommit till fund själv.
Vad jag än väljer att göra, hur jag än väljer att göra så väljer jag automatiskt bort något. Då jag varken kan göra allting på en gång och det finns inte oändliga resurser av något slag (inte ens universums livslängd(!)) så är det övervägda kompromisser som gäller från start till slut!
På återseende!
(Ja! Det är ingen idé att du ens tänker tanken: Jag äger redan FunkPHP.com! 😎🕺🎶🌈)
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Detta blir en kortare uppdatering, med mest fokus på hobbyprojektet: "FunkPHP"-ramverket.
(Så här i efterhand: definiera, "kortare"...) (-_- #) |
OBS: Rättstavningsinsticksmodulen fungerade ej för mig i FireFox när jag skulle korrekturläsa detta. Så Du Vet!🤪
Jobb & Uppdrag
Jag hade möte tidigare denna vecka med tidigare än så länge nöjd Uppdragsgivare och de verkar ha rott i hamn ett referensuppdrag så att de senare kan ansöka om finansiella medel för att anlita mig ett par veckor. Men det återstå att se i och med rådande geoekonomiska tider.
Jag skrev till en person på LinkedIn då denna person frågade om denne kunde ta sig an att göra en mobilapp med hjälp av Vibe Coding (AI-slarvviruset sprider sig allt mer!😂) medan jag då i mitt meddelande argumenterade för en enklare webbapp som prototyp - dvs., att jag gör det som ett enklare och kortare referensuppdrag.
Jag Inväntar fortfarande svar från en annan angående eventuellt 1-2 månaders distansuppdrag inom frontend-utveckling. Förra veckan fick jag inget jobb från Stockholmskund men denna vecka fick jag och det är färdigt nu så att jag kan fokusera på Webbutvecklingsfrilansandet vilket börjar med steg 1 fortfarande: bygga upp något som står ut från mängden bland andra Fullstacks-Junior-Webbutvecklare! 🫡
Hobbyprojekt
Likt inledningar i Nintendo Directs så säger jag nu: Ta först en titt på det här:
<?php // "compiled_route_trie.php"
return [
'GET' => [ // This is "/" level
'users' => [
'|' => [], // Middleware applies at /users level (sibling key)
':' => [
'id' => [],
],
],
'test' => [],
'about' => [],
],
'POST' => [
'users' => [
'|' => [], // Middleware applies at /users level (sibling key)
':' => [
'id' => [],
],
],
],
];
Detta är en Trie-datastruktur med två vanliga HTTP-metoder som rotnoderna. Sedan har de tre olika slags "tokens" som tolkas vid varje nivå:
'[a-zA-Z_-0-9]' = Exakt rutt. Exempelvis ser du 'users' vilket betyder rutten "/users" och ja: rutten "/" är antydd genom om du befinner dig först i "GET"-rotnoden. Strängen för begärd rutt blir först i små UTF-8 bokstäver och sedan är det bara att matcha järnet! Andra strängrutter på samma nivå är alltså "/test" och "/about".
':' = Dynamisk rutt-avskiljare. Nästa steg för en rutt är dynamiska rutter vilket inleds med ":" där "ruttmatchningsfunktionen" då inser att, "Jaha, dynamisk rutt på nästa nivå, då tar jag dess nyckelvärde vilket i sin tur blir nyckelvärdet för dess tillhörande värde från den delen i den begärda rutten!". Precis när jag skrev detta så insåg jag att "Vad händer om jag lägger till en ytterligare ':'? Den kraschar inte, och finner en matchning i den kompilerade ruttfilen men inte i utvecklarens rutter då båda måste matcha. (mer om det snart!)
'|' = Middleware-avskiljare. Denna är nog min favorit för vad den helt enkelt indikerar i den kompilerade ruttfilen är att, "Alla ruttsträngar du har lagt till hittills tillhör en middleware-rutt i middleware-ruttfilen för utvecklaren!" (se "middleware_routes.php"-filen) Så då tar den de hittills matchade ruttsegmenten och lagrar i en förberedd array och ställer den sig på '|' som ny nuvarande nod och fortsätter som om ingenting.
Funktionen som sköter allt detta är denna:
<?php // "function r_match_denied_global_uas($denied_uas, $ua)" in "r_route_funs.php"
// Match Compiled Route with URI Segments, used by "r_match_developer_route"
function r_match_compiled_route(string $requestUri, array $methodRootNode): ?array
{
// Prepare & and extract URI Segments and remove empty segments
$path = trim(strtolower($requestUri), '/');
$uriSegments = empty($path) ? [] : array_values(array_filter(explode('/', $path)));
$uriSegmentCount = count($uriSegments);
// Prepare variables to store the current node,
// matched segments, parameters, and middlewares
$currentNode = $methodRootNode;
$matchedPathSegments = [];
$matchedParams = [];
$matchedMiddlewares = [];
$segmentsConsumed = 0;
// EDGE-CASE: '/' and include middleware at root node if it exists
if ($uriSegmentCount === 0) {
if (isset($currentNode['|'])) {
array_push($matchedMiddlewares, "/" . implode('/', $matchedPathSegments));
}
return ["route" => '/', "params" => $matchedParams, "middlewares" => $matchedMiddlewares];
}
// Iterate URI segments when more than 0
for ($i = 0; $i < $uriSegmentCount; $i++) {
$currentUriSegment = $uriSegments[$i];
/// First try match "|" middleware node
if (isset($currentNode['|'])) {
array_push($matchedMiddlewares, "/" . implode('/', $matchedPathSegments));
}
// Then try match literal route
if (isset($currentNode[$currentUriSegment])) {
$matchedPathSegments[] = $currentUriSegment;
$currentNode = $currentNode[$currentUriSegment];
$segmentsConsumed++;
continue;
}
// Or try match dynamic route ":" indicator node and
// only store param and matched URI segment if not null
if (isset($currentNode[':'])) {
$placeholderKey = key($currentNode[':']);
if ($placeholderKey !== null && isset($currentNode[':'][$placeholderKey])) {
$matchedParams[$placeholderKey] = $currentUriSegment;
$matchedPathSegments[] = ":" . $placeholderKey;
$currentNode = $currentNode[':'][$placeholderKey];
$segmentsConsumed++;
continue;
}
}
// No matched "|", ":" or literal route in Compiled Routes!
return null;
}
// EDGE-CASE: Add middleware at last node if it exists
if (isset($currentNode['|'])) {
array_push($matchedMiddlewares, "/" . implode('/', $matchedPathSegments));
}
// Return matched route, params & middlewares
// if all consumed segments matched
if ($segmentsConsumed === $uriSegmentCount) {
if (!empty($matchedPathSegments)) {
return ["route" => '/' . implode('/', $matchedPathSegments), "params" => $matchedParams, "middlewares" => $matchedMiddlewares];
}
// EDGE-CASE: 0 consumed segments
// matched, return null instead of matched
else {
return null;
}
}
// EDGE-CASES: Return null when impossible(?)/unexpected behavior
else {
return null;
}
return null;
}
Funktionen har på ett ungefär O(n) där n är antalet URI-segment att matcha mot. Och det jag personligen är stolt över - även om Go-webbservrar får PHP-servrar att framstå som snigelservrar - är faktumet att den kan skapa tre saker samtidigt under tiden:
1) Matchad rutt i den kompilerade ruttfilen.
2) Matchade middlewares som också kan köras sedan i rätt ordning.
3) Matchade och extraherade dynamiska rutterparametrar.
(Rolig notis: Jag velade fram och tillbaka om jag skulle köra {id} eller :id och jag valde det sistnämnda för att spara en byte för varje implementering! Jag inbillar mig också att: "$matchedPathSegments[] = ":" . $placeholderKey;" är snabbare än att köra "substr($placeholderKey,1,-1);" p åsamma variabel för att associera nyckelvärdet med nyckelnamnet!)
En trevlig bonus är att den har väldigt svårt för att krascha. Snarare kraschar kraschar du först servern genom att skicka ogiltiga tecken i HTTP-anropet.
En anledning till varför den inte kan krascha riktigt är för att går bara så djupt som antalet URI-segment det finns, det vill säga för varje "/". Och skulle du skicka något i stil med "/users/10////" så kommer den att ta bort överflödet av skiljetecknen och även beroende på konfigurerad webbserver så kan andra säkerhetsåtgärder vidtas.
Den andra anledningen är att när den nu vet hur många segment den får iterera igenom så kan den antingen gå tillräckligt djupt i Trie-strukturen tills den får slut på segment eller tills att den inte hittar nästa segment i Trie-strukturen. Antingen hittar den en matchning eller inte.
Och ja, specialfallet när du bara navigerar till bara "/" är hanterat inklusive specialfallet när du vill ha en '|'-middleware direkt i rotnoden såväl som i slutet av den sista matchande noden i Trie-strukturen och inga fler URI-segment finns kvar att matcha!
Vänligen ta nu en titt på dessa två filer:
<?php // "single_routes.php"
return [
'GET' => [
'/users' => ['handler' => 'USERS_PAGE', /*...*/],
'/users/:id' => ['handler' => 'USER_ID_PAGE', /*...*/],
'/about' => ['handler' => 'ABOUT_PAGE', /*...*/],
],
'POST' => [
'/users' => ['handler' => 'post_create_user', /*...*/],
'/users/:id' => ['handler' => 'post_update_user', /*...*/],
],
'PUT' => [
'/users/:id' => ['handler' => 'put_update_user', /*...*/],
],
'DELETE' => [
'/users/:id' => ['handler' => 'delete_delete_user', /*...*/],
],
];
<?php // "middleware_routes.php"
return [
'GET' => [
'/' => ['handler' => 'GET_CHECK_CONTENT_TYPE', /*...*/],
'/users' => ['handler' => ['GET_VALIDATE_USER_IP', 'GET_VALIDATE_USER_AU'], /*...*/],
'/users/:id' => ['handler' => 'GET_MIDDLEWARE_USER_ID', /*...*/],
],
'POST' => [
'/users' => ['handler' => 'POST_MIDDLEWARE_USER', /*...*/],
'/users/:id' => ['handler' => 'POST_MIDDLEWARE_USER_PROFILE_TEST', /*...*/],
],
'PUT' => [
'/users/:id' => ['handler' => 'PUT_MIDDLEWARE_USER', /*...*/],
],
'DELETE' => [
'/users/:id' => ['handler' => 'DELETE_MIDDLEWARE_USER', /*...*/],
],
];
Dessa två är alltså filerna där Utvecklaren måste hårdkoda in enskilda rutter såväl som mellanrutterna (eng. middlewares). En detalj jag älskar här är att mellanrutterna kommer före singelrutterna både i filhanteringen i mappen där de ligger såväl som i hur Utvecklaren bör skriva dem.
Om du jämför mellanrutterna mot singelrutterna så ser du en intressant detalj i denna:
[GET => ['/users' => ['handler' => ['GET_VALIDATE_USER_IP', 'GET_VALIDATE_USER_AU'], /*...*/]];
Den har alltså två stycken inuti handler-nyckeln medan övriga bara har en. Hur Utvecklaren väljer att implementera dessa är upp till denne. Det smidiga är att mellanrutterna med kortare ruttsträng kommer att hamna först i arrayen som innehåller alla matchade mellanrutter!
På så vis kan du ha en autentiseringsrutt för "/users/" som då kan köras före de övriga i en logisk ordning. Eller ja, ¯\_(ツ)_/¯ , du kan ju lika gärna bara anropa den mellanruttsfunktion som du vet att du vill köra först bara du vet vart den är i arrayen. Se det lite mer som en QoL-implementering.
För att testköra rutterna så går det att ta en titt på "funkphp_start.php" vilket är filen som körs för alla omdirigerade HTTP-anrop:
<?php // ENTRY POINT OF EACH HTTPS REQUEST thanks to ".htaccess" file
include_once __DIR__ . '/_internals/functions/_includeAll.php';
include_once __DIR__ . '/dx_steps/_includeAll.php';
//REMOVE AFTER TESTING!
// echo "User IP:'" . $req['ip'] . "'<br> ";
// echo "User URI:'" . $req['uri'] . "'<br> ";
// echo "User Query: " . $req['query'] . "<br> <br>";
// Load configurations and global variables
$fphp_global_config = h_load_config($fphp_all_global_variables_as_strings);
if (!ok($fphp_global_config)) {
return_code(418);
exit;
}
// Developer's route definitions
// Middlwares
$developerMiddleRoutes = include __DIR__ . '/routes/middleware_routes.php';
// Singles
$developerSingleRoutes = include __DIR__ . '/routes/single_routes.php';
// Compiled Trie structure where "#" indicates dynamic route and "|" indicates middleware
$compiledTrie = include __DIR__ . '/_internals/compiled_route_trie.php';
// --- Test Cases ---
echo json_encode(r_match_developer_route($req['method'], $req['uri'], $compiledTrie, $developerSingleRoutes, $developerMiddleRoutes), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
En sak jag lärde mig då vilket jag skäms över att jag inte fattat och använt innan är att du kan inkludera returvärden direkt i variabler när du använder "include"-direktivet i PHP!? (-‸ლ) 🤦♂️ Detta fick jag veta av Gemini 2.5 Pro vilket jag använt mig av för att kunna förstå all kod jag har använt mig av.
Den gav mig först en bra start men det var jag personligen som upptäckte, insåg och sedan fixade de olika specialfallen. I takt med det så blev jag också mer insatt i flödet och vissa gånger så förstörde Gemini 2.5 Pro min kod som jag ej höll med men det var ju bara att ignorera så.
Nu ser du så klart funktionen "r_match_developer_route" vilket är något helt annat vilket avslöjas enligt nedan:
<?php // FUNCTION FROM: "r_route_funs.php"
function r_match_developer_route(string $method, string $uri, array $compiledTrie, array $developerSingleRoutes, array $developerMiddlewareRoutes, string $handlerKey = "handler", string $mHandlerKey = "handler")
{
// Prepare return values
$matchedRoute = null;
$matchedRouteHandler = null;
$matchedRouteParams = null;
$matchedMiddlewareHandlers = [];
$routeDefinition = null;
$noMatchIn = ""; // Use as debug value
// Try match HTTP Method Key in Compiled Routes
if (isset($compiledTrie[$method])) {
$routeDefinition = r_match_compiled_route($uri, $compiledTrie[$method]);
} else {
$noMatchIn = "COMPILED_ROUTE_KEY (" . mb_strtoupper($method) . ") & ";
}
// When Matched Compiled Route, try match Developer's defined route
if ($routeDefinition !== null) {
$matchedRoute = $routeDefinition["route"];
$matchedRouteParams = $routeDefinition["params"] ?? null;
// If Compiled Route Matches Developers Defined Route!
if (isset($developerSingleRoutes[$method][$routeDefinition["route"]])) {
$routeInfo = $developerSingleRoutes[$method][$routeDefinition["route"]];
$matchedRouteHandler = $routeInfo[$handlerKey] ?? null;
$noMatchIn = "BOTH_MATCHED";
// Add Any Matched Middlewares Handlers Defined By Developer
if (
isset($routeDefinition["middlewares"]) && !empty($routeDefinition["middlewares"] && is_array($routeDefinition["middlewares"]))
) {
foreach ($routeDefinition["middlewares"] as $middleware) {
if (isset($developerMiddlewareRoutes[$method][$middleware]) && isset($developerMiddlewareRoutes[$method][$middleware][$mHandlerKey])) {
$matchedMiddlewareHandlers[] = $developerMiddlewareRoutes[$method][$middleware][$mHandlerKey] ?? null;
}
}
}
} else {
$noMatchIn .= "DEVELOPER_SINGLE_ROUTES";
}
} else {
$noMatchIn .= "COMPILED_ROUTES";
}
return [
"route" => $matchedRoute,
"$handlerKey" => $matchedRouteHandler,
"params" => $matchedRouteParams,
"middlewares" => $matchedMiddlewareHandlers,
"no_match_in" => $noMatchIn, // Use as debug value
];
}
Det är den där funktionen som först anropar matchningsfunktionen mot den kompilerade Trie-strukturfilen (eller ja, returvärdet i en variabel i detta fall). Sedan när den får tillbaka något värde (även null) så returnerar den en associativ array med matnyttiga variabler för att nyttja (se bild nedan):
I svarsobjektet syns exempelanvändning av en anropad rutt lokalt och hur parametrarna ur den dynamiska delen har extraherats, rätt matchad rutt finns att nyttja, dess "handler" (och du kan döpa om det där nyckelnamnet vid varje funktionsanrop om du vore så Frisk & Galen™), mellanruttarna och en sak som får en att ta sig om sin nyrakade haka:🤔
"no_match_in": "BOTH_MATCHED"
Det där är endast ett "debugvärde" vilket även står i kommentaren i slutet av funktionen. Tanken är att om du skulle ha skrivit fel så kommer du internt (ej i produktion, förhoppningsvis!) att få se en sträng som säger om det är den kompilerade ruttfilen som ej matchade eller Utvecklarens egna singelruttar.
Samtidigt kan det ju också användas för att genomföra mer komplex matchning om första matchningen skulle misslyckas? I slutändan är allt detta enbart två funktioner och några filer med (mellan)rutterna och en fil dit alla HTTP-anrop kan omdirigeras till via en .htaccess-fil.
En självkritisk sak ska nämnas och jag kunde inte få något vettigt ur Gemini 2.5 Pro tills att jag fick slut på gratisanvändningen av den och det berör så kallada Adaptiva Trie-datastrukturer. Exempelvis skulle vissa rutter i den kompilerade ruttfilen slås ihop men hur ska det kontrolleras utan att behöva göra intensiv strängbearbetning än den HashTable Lookup-elegans den gör just nu?! 😵
Om till exempelvis hypotesrutten "/blog" bara har underutten "/posts" så skulle båda kunna slås ihop till "blog/posts". Utmaningen då är att det skulle innebära att kika inuti varje nod om den innehåller ett "/" alternativt dela upp URI-segmenten till inte bara enskilda segment utan också parsegment så att "blog/posts" då skulle bli ett. Men då skulle vi behöva göra en ytterligare kontrollmatchning och frågan är hur mycket vi vinner på det hela genom att bara ha en ytterligare HashTable Lookup för att spara några bytes varje gång det tillämpas i den kompilerade ruttfilen?
Nåja, det är bättre än ingenting och i takt med hundratals eller till och med tusentals rutter så blir foreach inte lämplig utan detta har varit några dagars mycket givande tillfälle att nöta YT-klipp, kika i artiklar samt tagit hjälp av gratis LLM:er för att förstå mig på Trie-datastrukturer på en mycket grundläggande nivå.
Dessa går även att applicera till att begränsa olika omfång av IP-adresser t.ex. matcha mot "80.XXX.Y" där X matchar endas nästa 0-9 siffra exakt medan Y innebär matcha resten av IP-strängen. Här gäller det att vara försiktig så att man inte råkar låsa ute nästan hela sin användarbas på grund av ett felplacerat Y i en Trie-datastruktur!🤣
Avslutningsvis för denna gång så är detta bara platshållartext:
<br>YOU SHOULD NOT SEE THIS! SO ERROR!<br>
Den indikerar att du har nått slutet av PHP-filen som ska hantera alla HTTP-anrop och där får det läggas in något standardsvar, vare sig det är HTTP-svarskod 418 eller kanske en ASCII-bild på en glad pingvin? 🐧
Vidareutbildning
Inget nytt att rapportera förutom att jag snart har lärt mig grunderna i att applicera Trie-datastrukturer i praktiken!
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
VARNING: Detta inlägg och möjligen nästa innehåller en gigantisk uppdatering av hobbyprojektet FunkPHP-ramverket - det 99 % funktionsbaserade ramverket helt i PHP!
Jobb & Uppdrag
Igår fick jag en jättelast av jobb från Stockholmskunden och jag betade av ett par igår. Jag fortsätter med att beta av resterande efter att jag har skrivit detta och eventuellt ett ytterligare inlägg på rad redan samma dag! 🤯
Kortfattad uppdatering här är att imorgon den 17:e april 2025 ska jag delta i Indiehackers videogruppträff och den 28:e april 2025 kommer jag att delta i en så kallad Dragon Tech-lunchträff med helt okända men teknikinsatta människor.
Frågan är om jag på något vis skulle kunna visa upp mitt 99 % funktionsbaserade PHP-ramverk då samtidigt som jag också lyfter fram att jag är "färsking" inom Webbutvecklingsmarknaden som nyutexaminerad sedan maj 2024? 🤔
Nu vidare till vad som är del 1 av 2 delar under samma dag!
Hobbyprojekt
Se Figur 1 (bilden nedan) så ser du det allmänna flödesschemat för FunkPHP för varje inkommen HTTP-förfrågan:
Stegen omfattar följande exekvering i den ordning de beskrivs nedan:
Kontrollera först om detta är rätt steg och kör i sådana fall allt inuti kodsatsen och avsluta den med att sätta nästa steg. Ladda in den globala $c-variabeln (vissa kodarkitekturerskulle kalla denna för "context variable") där i princip allt finns gällande HTTP-förfrågan, matchade (dynamiska) rutter, erhållna databasdata samt sida att returnera om inte JSON redan returnerades (ja, så API-stöd finns). Allt laddas inte in samtidigt för - förhoppningsvis - optimal prestanda. Här omdirigeras även förfrågan om den ej är i HTTPS. Sessionskakans parametrar sätts, databasanslutning anropas och lagras i $c['db'] med anslutet databasanslutningsobjekt eller null, säkra svarshuvuden (eng. "headers") sätts och osäkra sådana raderas (t.ex. "Server", "X-Powered-By". Sedan förbereds och lagras en förberedd URI-sträng via:$c['req']['uri'] = r_prepare_uri($_SERVER['REQUEST_URI'], $c['BASEURLS']['BASEURL_URI']); Till sist i steg 1: $c['req']['next_step'] = 1;. Det sistnämnda är vedertaget för övriga steg förutom det sista/sjätte steget!
Kontrollera steg och kör kodsats om rätt steg. Avsluta med sätta nästa steg. Tre funktioner: r_match_denied_methods();, r_match_denied_exact_ips(); och r_match_denied_uas_fast();
Kontrollera och kör endast rätt steg. Sätt alltid nästa steg. Ladda in kompilerade, singel- och mellanrutter via $c['ROUTES'] = [...];. Matcha mot giltig rutt r_match_developer_route(); och lagra sedan i $c['req'].
Start enligt föregående steg. Om det fanns mellanruttskod (eng. "middleware code") till matchande rutt så kör dem i den ordning de lades till av Utvecklaren under utveckling. INFO: Detta steg kommer troligtvis att slås ihop med föregående steg så att det sammanlagt blir fem steg i slutändan!
Start enligt föregående steg. Ladda in kompilerade, singel- och mellanrutterna för datasteget i $c['ROUTES']['DATA']. Matcha med: d_match_developer_data_route();. Om det finns ej ännu körda mellanrutter från föregående matchningssteg så slå ihop arrayerna, annars lägg bara till. Lagra övriga datastegsrelevanta data i och med matchningsfunktionen. TODO: Än så länge körs inte mellanruttskoden förknippad med datasteget, men det implementeras troligen mycket snart!™
Start enligt föregående steg förutom inget nytt nästa steg att sätta i slutet av kodsatsen! Ladda in kompilerade, singel- och mellanrutter för sidorna (det motsvarande till "views/vyer") i: $c['ROUTES']['PAGE'] = [...]. Matcha med p_match_developer_page_route(); - ej implementerad/skriven ännu - och lagra nödvändiga sidstegsrelevanta data i och med den snart™ skrivna matchningsfunktionen. Avsluta det hela med att returnera en sida/vy och/eller köra förknippad mellanruttskod. TODO: Än så länge körs inte mellanruttskoden förknippad med sidsteget, men det implementeras troligen mycket snart!™
De sex stegen - (deras stegfiler - i skrivande stund - ligger i dx_steps) - ovan kompletteras med bilden nedan som visar den allmänna mappstrukturen i ett FunkPHP-projekt där det mesta liknar klassiska MVC förutom att "Models" heter "data" och "Views" numera heter "pages":
En sak jag lyckades få till efter jag läst och tittat på YT-klipp om datastrukturen Trie (ej Radix dock!) är vad som illustreras i bilden nedan:
Notera följande utdrag ur den associativa arrayen:
return 'GET' =>['users' => [':' => ['id' => ['test' => ['|' => [],], '|' => [],],],],];
(Rolig skrivincidens: Jag råkade skriva om troute_route.php-filen vilket har annorlunda struktur så fick skriva om!)
GET innebär anropsmetoden, medan varje "nyckeldjup" (arraynyckeln inuti) innebär rutt delat med ett "/". Men vänta...! (som resonerande LLM:er säger) Vad är ":" och vad sjutton är "|" i detta ruttutdrag?
Det förstnämnda indikerar för matchningsfunktionen att det nästa efter en ny "/" (eftersom vi gick ett ytterligare nyckeldjup) är en dynamisk rutt så du (matchningsfunktionen) ska tolka detta som giltig matchning och bara gå vidare till nästa nyckeldjup "/".
Vad händer då när nästa nyckeldjup är "|"? |-nyckeln säger till matchningsfunktionen att här går skiljelinjen för mellanruttskoder lagrade i mellanruttsfilen att köra. När |-nyckeln ligger på samma nivå så innebär det specialfallet när du har mellanruttskod för exempelvis GET/. Du kanske även ser i bilden att en |-nyckel ligger på samma nivå som GET/users/:id/test?
Här hanteras två specialfall: Du navigerade till GET/ som har tillhörande mellanruttskod eller du navigerade till sista nyckeldjupet och det finns en |-nyckel där som du inte kontrollerat för i och med att du gått igenom alla "/"-segment:
// Från: /src/funkphp/_internals/functions/r_route_funs.php - r_match_compiled_route();
// EDGE-CASE: Add middleware at last node if it exists
if (isset($currentNode['|'])) {array_push($matchedMiddlewares, "/" . implode('/', $matchedPathSegments));}
Det fungerar - än så länge - förvånansvärt bra och då stegen för rutt, data och sidor kan ha helt olika mellanrutter förknippade med dem så har de också sina egna kompilerade Trie-datastrukturer i src/funkphp/_internals/compiled/.
Bilden nedan visar hur du kan ha helt andra rutter i exempelvis ruttmatchningssteget (steg 3) jämfört med vilka förknippade rutter som finns i datasteget (steg 5 - ja, du "hör" nu hur skumt detta kan låta; det blir troligen att slå ihop vissa steg och inte börja räkna från noll dessutom i filerna! 😂):
Fast vänta nu... Detta låter motsägelsefullt: Anta att vi har en rutt GET/users/:id men ingen djupare än så för GET/users. Hur kan det då finnas en mellanrutt (vilket bara kan köras om själva singelrutten först matchas!) i nästa steg? Den kommer ju aldrig att matchas i singelrutten till att börja med och då tar den onödig textplats och dessutom förvirrar utvecklaren!
Hur kan vi konsekvent lägga till singel- och mellanrutter med ett enkelt klick? Klick blir nog svårt... Vad sägs om att bara kunna skriva i något slags skräddarsytt CLI som kommer med när du klonar repot? 🖵
Tillåt mig få introducera FunkCLI och om du tittar i första bilden i detta första inlägg av två delar så kommer du att se en fil utan filtyp som heter inte mer eller mindre än självaste FunKCLI! Den ska vi ta en närmare titt på i nästa andra del av detta inlägg som är uppdelat i två delar så att säga! 🫡
Fortsättning följer direkt i nästa inlägg efter detta! Här: #20841913
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Hobbyprojekt (fortsättning från: #20841909 )
Ta först en titt på de tillgängliga funktionerna relaterade till FunkCLI:
Du har funktioner för att kunna:
- Kompilera rutter. [/i](skapa den så kallade Trie-strukturen)[/i]
- Skapa filer av kompilerade rutter. (skapa filerna i src/funkphp/_internals/compiled/-mappen)
- Säkerhetsgranska alla rutter innan de ens får kompileras. (EJ SKRIVEN ÄNNU!!!)
- Konvertera fula och äckliga PHP-arrays() till eleganta PHP-arrays[]. (den används för att du inte ska se array() överallt i Trie-filerna vilket sparar på utrymme, men säkert lika svårläst)
- Flera funktioner för att i vald Terminal konsekvent visa olika färger och textmeddelanden beroende på typ av händelse: ogiltigt syntax, giltigt kommando fick fel vid körning i röd text, giltigt kommando lyckades köras i grön text, information om något i blå text, info om något över flera rader i blå text (anropa alltså den flera gånger i följd och avsluta manuellt med exit;) och avslutningsvis kan varning skrivas ut i röd text.
Hur ser då starten ut för FunkCLI? Jo, så här ser det ut först i början:
Sedan behövs det ju en bra struktur för att lättsamt kunna lägga till nya kommandon. Tack vare Trie-datastrukturtänkandet där du vill uppnå O(1) så tänkte jag något, skrev lite och sedan skickade vidare till Gemini 2.0 Thinking Version vilket då gav mig den mycket eleganta och O(1)-vänliga strukturen enligt bilden nedan:
(ja, jag ser "NAJS"-felet på rad 69 och det är redan åtgärdat när du läser detta!)
I bilden ovan får du exempel på när flerradsfunktionen används för att skriva ut och manuellt avsluta skriptet. Du ser även "add", "change", "delete" och "help" nedan där endast "help" är implementerat. De övriga kommer sedan och det är dessa som kommer att göra FunkPHP faktiskt användarvänligt.
Istället för att du manuellt ska komma ihåg om du matat in samma rutt i alla steg (routes, data, pages) så ska du bara behöva skriva något i stil med följande i din PHP-kompatibla terminal:
php funkcli add route GET/rutt/:underrutt/ mws:log,auth
Terminalkommandot ovan ska då skapa en rutt som heter /rutt/:underrutt under GET-nyckeln och sedan två tillhörande mellanruttsfiler: log och auth. Standardbeteendet här - efter vedertagen datavalidering av kommandots olika argument - kommer bli att alla tre troute-filer kommer att få samma /rutt/:underrutt under respektive GET-nyckel men bara routes/route_middleware_routes.php-fil kommer att få mellanrutterna log och auth till sin GET/rutt/:underrutt/.
Självfallet kommer även information att ges om det redan finns rutter och du meddelas även om att när det lyckats så måste du manuellt lägga till eventuella mellanrutter till data och pages för den tillagda GET/rutt/:underrutt. En sak du kanske funderar nu på eftersom det handlar om att manipulera filer lokalt är: Vad ska hända vid change och/eller delete?
Det har jag tänkt på: Eftersom change och delete kan betraktas som destruktivt och misstag kan inträffa (exempelvis radera helt fel rutt och därmed all förknippad information vilket kan vara plågsamt att få tillbaka) så kommer dessa kommandon givetvis att skapa säkerhetskopieringar i en undermapp som jag nyss lade till (i skrivande stund(!)) - src/funkphp/_BACKUPS/ - med stora bokstäver så Utvecklaren klart och tydligt ser vart det kan finnas säkerhetskopieringar av olika slags.
Då filerna kommer att vara datumbaserade i filnamnets slut: FILNAMN-YYYY_MM_DD_HH_MM_SS.php så kommer det ju gå att skriva funktioner såsom restore som kan återställa den senaste filen så du i princip får ett "Ångra"-kommando rakt i ditt FunkCLI!
"Vänta nu här Herr Färsking_Till_FullStacksWebbJunior! Hur tänker du nu? Vad händer med filen som återställts då och vad händer om återställningen i sig är ett rent och skärt misstag?" Därför finns det redan en sista utväg: /src/funkphp/_BACKUPS/_FINAL_BACKUPS/ där då allt i princip skulle kunna läggas förutsatt att Utvecklaren är medveten om påfrestningen det kan ha på operativsystemets I/O-hantering och/eller själva lagringsutrymmet i sig.
Ja, just det: Jag glömde visa dig sista delen i FunkCLI:
Här lagras då först kommandona och saknas det tredje argumentet - $argv[2] - eller är null så körs kommandon som ej tar något argument som exempelvis php funkcli help. Övriga funktioner som kan ta flera argument utöver sina (sub)kommandon kan hantera sin datavalidering där istället för här längst ned i CLI-skriptet.
Det finns en viktig sak som FunkCLI löser förutom att göra det bekvämt för Utvecklaren och för att någon ens kanske skulle orka vilja prova på FunkPHP med tanke på det vardagliga memes såsom: "Baby, wake up, another JS Framework just dropped, KEKW!!!".
Vad den löser är min första tanke med gui-mappen då jag tänkte att det behövdes något mer för att kunna stå ut från mängden av alla existerande ramverk idag, vare sig det är JS, PHP eller något annat. PHP Laravel har ju exempelvis php artisan och jag FunkCLI uppstod som följd av att jag ville bannemig kunna "få till" något liknande även om det kanske inte nödvändigtvis någonsin kommer att bli lika omfattande och/eller komplext? 🤔
Ett GUI vore 420 % mer fräckt att ha och kunna visa upp som portfölj men då jag inte primärt är frontend-lagd så anser jag att det är fel väg att försöka gå med detta hobbyprojekt. Planen är sedan att jag faktiskt själv kommer att använda detta FunkPHP-ramverk till ett par av mina enklare webbplatser.
Det sista jag för denna gång vill nämna om FunkPHP är att det kommer att "lanseras" med följande inkluderade mellanruttskoder så det snabbt går att nyttja autentisering och/eller auktorisering:
- MW_R_AUTH_ONLY_COOKIES.php
- MW_R_AUTH_ONLY_DB.php
- MW_R_AUTH_COOKIES_AND_DB.php
Så var är då:"MW_R_AUTH_ONLY_JWT.php" och "MW_R_AUTH_JWT_AND_DB.php" någonstans? Självfallet vore det rejäl hybris att jag skulle kunna lansera något skräddarsytt som kräver mycket exakt hantering av JSONWebTokens när det gäller skapande, signering, såväl som verifiering. Där saknar jag den nödvändiga förståelsen i den utsträckning jag behöver förstå den för att kunna använda på vedertaget vis enligt bäst branschpraxis.
(Jag använder ju beprövade PHP-funktioner som password_hash(), verify_password() och sodium_compare() vid behov istället för att tro att jag kan skriva några egna kryptobaserade funktioner som ska korrekt ha entropi, vara kvantsäkert, såväl som kunna stå emot så kallade timing-attacker!)
Avslutningsvis för denna gång så vill jag säga att en sak jag insåg med det första utkastet av FunkCLI är att jag förstår nu varför många hyllar textterminaler framför läckra men kanske också samtidigt mycket komplexa grafiska användargränssnitt.
Om du - i fallet med FunkPHP när du klonat repot - ska lägga till tre rutter vilket då ska skrivas i tre olika filer samt kompileras om med säkerhetskopior i fall du ångrar dig eller något gick snett, så är det ju mycket mer angeläget att bara kunna skriva ett kort kommando så allt detta sköter sig själv och så får du info om allt som lyckades såväl som allt som misslyckades och antingen provar du igen men lite annorlunda eller bara öppnar ett Issue i mitt repo!😅😬
Vidareutbildning
Inget nytt att rapportera förutom att jag har lärt mig att skriva CLI:er i PHP som går att köra i VS Code Terminal förutsatt att PATH System-variabeln till PHP-tolken är korrekt konfigurerad.
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jobb & Uppdrag
Den än så länge nöjda Uppdragsgivaren
Förra gången jag skrev här så fick jag kort därpå samma dag ett kortvarigt samtal från än så länge nöjd tidigare Uppdragsgivare om eventuellt framtida uppdrag om denne nu kanske rott ett referensuppdrag och därmed sin första kund i hamn.
Ett litet orosmoln dock från mitt håll är att Uppdragsgivaren funderade på att använda AI för ett särskilt ändamål där sedan AI:n skulle bli bättre (=dvs., på något vis få träningsdata i samband med sin användning) i sin textanalys. Till saken hör att jag ej är säker på att det fungerar med de nuvarande tjänsterna såsom OpenAI, Gemini, m.fl.
Visst, kan du mata in data så att de gigantiska språkmodellerna blir "bättre", men det är ju inte så att just Du får ta del av de bättre versionerna bara för det. Personligen är jag inte heller helt 100 % insatt i hur det fungerar med att förbättra nuvarande LLM-modeller (även lokala sådana). Vad jag sett från något långt YT-klipp kring en ordentlig genomgång av LLM:er så är det första träningen som är det tyngsta/dyraste steget i processen. Sedan är det fine-tuning, och sedan lansering?🤔
Vad Uppdragsgivaren vill analysera gällande textinnehållet är också något som inte bara kräver att analysera klassiska samband mellan ord och mönster mellan ord utan även en djupare förståelse i texten. Vi pratar alltså om så kallad extrapolerande förståelse där det inte bara handlar om vilka ord som är mest statistiskt sannolika att komma efter varandra utifrån redan innött träningsdata utan också en djupare mänskligare analys av det hela - kvalificerad psykologi i princip.
Det lilla orosmoln för mig här tror jag främst bottnar i att det möjligen inte kommer att kunna bli så fantastiskt och därmed kanske ekonomiskt hållbart som Uppdragsgivaren tänkt sig och då kan jag få dåligt rykte. Uppdragsgivaren har i samma veva dock klargjort för mig att denne inte förväntar sig att jag ska vara någon "AI-expert" utan sådde bara ett frö (hähä, minns du de gamla åtta veckorna? ) om att någon form av AI-implementering kan vara önskvärt. Suck eller inte suck?!😅
Inlagd som eventuell framtida kandidat i en svensk Databas
För några dagar sedan lade jag ut en "spontanansökan med en 'glimten i ögat'-rant" i en Slack-grupp då jag självförvållande är än så länge en IT-frilansande junior-fullstacks-webbutvecklare vilket relativt objektivt talat är en rätt så idiotisk väg att gå direkt som nyutexad.
Samtidigt har jag fått uppfattningen av min begränsande omvärldsbevakning att det är minst(?) lika tufft för icke-IT-frilansande juniorer att få sitt första IT-utvecklingsrelaterade jobb såsom den "våghalsiga" väg jag har valt att gå. Hursomhelst så finns nu min e-post inlagt i en svensk databas i hopp om någon framtida distansroll inom webbutveckling hos ett svenskt företag någonstans nere i södra Sverige om jag minns rätt.
Hobbyprojekt
Långsam vs snabb UA-matchning
En kodsnutt jag har glömt bort att vissa som skrevs för någon vecka eller två sedan är funktioner som ska matcha mot otillåtna Användaragenter (UAs) och först skrev jag en riktigt långsam för jag trodde jag kunde skriva en snabbare än en som bara använde inbyggda str_contains-strängmetoden i PHP:
function r_match_denied_uas_fast()
{
$ua = $_SERVER['HTTP_USER_AGENT'] ?? null;
if ($ua === "" || $ua === null || !is_string($ua)) {
return true;
}
$ua = mb_strtolower($ua);
$uas = include dirname(dirname(__DIR__)) . '/config/BLOCKED_UAS.php';
if ($uas === false) {
return ["err" => "[r_match_denied_uas]: Failed to load list of blocked UAs!"];
}
foreach (array_keys($uas) as $deniedUa) {
if (str_contains($ua, $deniedUa)) {
return true;
}
}
return false;
}
En rolig kuriosa med den snabba versionen är att vid tester så visade sig att bara denna kodrad ökade tiden med mellan - ge och ta - 20 - 25 %:
$ua = mb_strtolower($ua);
Att bara göra om de UTF-8-teckenkodade tecknen till motsvarande gemener visar sig vara riktigt tungdrivet och det tar mig tillbaka till Advent of Code 2024 när det pratades om hur tungt det är med strängsammanfogningar (eng. "string concatenations") i samband med ett kodpussel där du i princip behövde slå samman strängar för att pröva dig fram till rätt resultat.
Den andra jag skrev för att lära mig(?) skriva strängbearbetningsfunktioner (eng ."String Parsers"(?)) visade sig vara mycket långsammare då jag i princip försökte skriva str_contains-strängmetoden med PHP-kod istället för att använda den redan inbyggda optimerade C-skrivna?!:
// Try match against denied UAs globally (slower version apparently)
function r_match_denied_uas_slow_test($ua)
{
if ($ua === "" || $ua === null || !is_string($ua)) {
return null;
}
$startTime = microtime(true);
$uas = include dirname(__DIR__) . '/compiled/uas.php';
if ($uas === false) {
return ["err" => "[r_match_denied_uas]: Failed to load compiled UAs!"];
}
$ua = mb_strtolower($ua);
$uaArrayToCompareAgainst = [];
$ibleArr = [];
$ible1Word = [];
$ible2Words = [];
$ible3Words = [];
$ible4Words = [];
$ible5Words = [];
$bot1WordLeft = [];
$bot2WordsLeft = [];
$bot3WordsLeft = [];
$bot4WordsLeft = [];
$bot5WordsLeft = [];
$botArr = [];
$iblePos = -1;
$botPos = -1;
$len = mb_strlen($ua);
for ($i = 0; $i < $len; $i++) {
$char = $ua[$i];
if ($iblePos === -1) {
if ($char === 'i' && count($ibleArr) === 0) {
$ibleArr[] = 'i';
} elseif ($char === 'b' && count($ibleArr) === 1 && isset($ua[$i - 1]) && $ua[$i - 1] === 'i') {
$ibleArr[] = 'b';
} elseif ($char === 'l' && count($ibleArr) === 2 && isset($ua[$i - 1]) && $ua[$i - 1] === 'b' && isset($ua[$i - 2]) && $ua[$i - 2] === 'i') {
$ibleArr[] = 'l';
} elseif ($char === 'e' && count($ibleArr) === 3 && isset($ua[$i - 1]) && $ua[$i - 1] === 'l' && isset($ua[$i - 2]) && $ua[$i - 2] === 'b' && isset($ua[$i - 3]) && $ua[$i - 3] === 'i') {
$ibleArr[] = 'e';
} elseif ($char === ';' && count($ibleArr) === 4 && isset($ua[$i - 1]) && $ua[$i - 1] === 'e' && isset($ua[$i - 2]) && $ua[$i - 2] === 'l' && isset($ua[$i - 3]) && $ua[$i - 3] === 'b' && isset($ua[$i - 4]) && $ua[$i - 4] === 'i') {
$iblePos = $i;
$ibleArr = [];
} else if ($char !== 'i') {
$ibleArr = [];
}
}
if ($botPos === -1) {
if ($char === 'b' && count($botArr) === 0) {
$botArr[] = 'b';
} elseif ($char === 'o' && count($botArr) === 1 && isset($ua[$i - 1]) && $ua[$i - 1] === 'b') {
$botArr[] = 'o';
} elseif ($char === 't' && count($botArr) === 2 && isset($ua[$i - 1]) && $ua[$i - 1] === 'o' && isset($ua[$i - 2]) && $ua[$i - 2] === 'b') {
$botPos = $i;
$botArr = [];
} else if ($char !== 'b') {
$botArr = [];
}
}
}
$iblePos = $iblePos !== -1 ? $iblePos : -1;
$botPos = $botPos !== -1 ? $botPos : -1;
if ($iblePos === -1 && $botPos === -1) {
return false;
}
if ($iblePos !== -1 && isset($ua[$iblePos + 1]) && $ua[$iblePos + 1] === " ") {
$iblePos += 1;
}
for ($k = $iblePos; $k < $len; $k++) {
if (isset($ua[$k])) {
$char = $ua[$k];
if ($char === ";" || $char === "/" || $char == "(") {
if (count($currentWord) > 0) {
$currentWordStr = implode("", $currentWord);
if (count($ible1Word) < 1) {
$ible1Word[] = $currentWordStr;
}
if (count($ible2Words) < 2) {
$ible2Words[] = $currentWordStr;
}
if (count($ible3Words) < 3) {
$ible3Words[] = $currentWordStr;
}
if (count($ible4Words) < 4) {
$ible4Words[] = $currentWordStr;
}
if (count($ible5Words) < 5) {
$ible5Words[] = $currentWordStr;
}
}
break;
}
elseif ($char === " ") {
if (count($currentWord) > 0) {
$currentWordStr = implode("", $currentWord);
if (count($ible1Word) < 1) {
$ible1Word[] = $currentWordStr;
}
if (count($ible2Words) < 2) {
$ible2Words[] = $currentWordStr;
}
if (count($ible3Words) < 3) {
$ible3Words[] = $currentWordStr;
}
if (count($ible4Words) < 4) {
$ible4Words[] = $currentWordStr;
}
if (count($ible5Words) < 5) {
$ible5Words[] = $currentWordStr;
}
$currentWord = [];
}
}
else {
$currentWord[] = $char
}
}
}
if (count($ible1Word) > 0) {
$uaArrayToCompareAgainst[] = $ible1Word[0];
}
if (count($ible2Words) > 0) {
$uaArrayToCompareAgainst[] = count($ible2Words) > 1 ? implode(" ", $ible2Words) : $ible2Words[0];
}
if (count($ible3Words) > 0) {
$uaArrayToCompareAgainst[] = count($ible3Words) > 1 ? implode(" ", $ible3Words) : $ible3Words[0];
}
if (count($ible4Words) > 0) {
$uaArrayToCompareAgainst[] = count($ible4Words) > 1 ? implode(" ", $ible4Words) : $ible4Words[0];
}
if (count($ible5Words) > 0) {
$uaArrayToCompareAgainst[] = count($ible5Words) > 1 ? implode(" ", $ible5Words) : $ible5Words[0];
}
$currentWordLeft = [];
for ($l = $botPos; $l < $len; $l--) {
if (isset($ua[$l])) {
$char = $ua[$l];
if ($char === ";" || $char === "/") {
if (count($currentWordLeft) > 0) {
$currentWordStr = implode("", array_reverse($currentWordLeft));
if (count($bot1WordLeft) < 1) {
$bot1WordLeft[] = $currentWordStr;
}
if (count($bot2WordsLeft) < 2) {
$bot2WordsLeft[] = $currentWordStr;
}
if (count($bot3WordsLeft) < 3) {
$bot3WordsLeft[] = $currentWordStr;
}
if (count($bot4WordsLeft) < 4) {
$bot4WordsLeft[] = $currentWordStr;
}
if (count($bot5WordsLeft) < 5) {
$bot5WordsLeft[] = $currentWordStr;
}
}
break;
}
elseif ($char === " ") {
if (count($currentWordLeft) > 0) {
$currentWordStr = implode("", array_reverse($currentWordLeft));
if (count($bot1WordLeft) < 1) {
$bot1WordLeft[] = $currentWordStr;
}
if (count($bot2WordsLeft) < 2) {
$bot2WordsLeft[] = $currentWordStr;
}
if (count($bot3WordsLeft) < 3) {
$bot3WordsLeft[] = $currentWordStr;
}
if (count($bot4WordsLeft) < 4) {
$bot4WordsLeft[] = $currentWordStr;
}
if (count($bot5WordsLeft) < 5) {
$bot5WordsLeft[] = $currentWordStr;
}
$currentWordLeft = [];
}
}
else {
$currentWordLeft[] = $char;
}
}
}
if (count($bot1WordLeft) > 0) $uaArrayToCompareAgainst[] = $bot1WordLeft[0];
if (count($bot2WordsLeft) > 0) $uaArrayToCompareAgainst[] = count($bot2WordsLeft) > 1 ? implode(" ", $bot2WordsLeft) : $bot2WordsLeft[0];
if (count($bot3WordsLeft) > 0) $uaArrayToCompareAgainst[] = count($bot3WordsLeft) > 1 ? implode(" ", $bot3WordsLeft) : $bot3WordsLeft[0];
if (count($bot4WordsLeft) > 0) $uaArrayToCompareAgainst[] = count($bot4WordsLeft) > 1 ? implode(" ", $bot4WordsLeft) : $bot4WordsLeft[0];
if (count($bot5WordsLeft) > 0) $uaArrayToCompareAgainst[] = count($bot5WordsLeft) > 1 ? implode(" ", $bot5WordsLeft) : $bot5WordsLeft[0];
foreach ($uaArrayToCompareAgainst as $uaWord) {
if (isset($uas[$uaWord])) {
return true;
}
}
return false;
}
Textbearbetningsfunktion för att lägga in rutter
På tal om så kallade "String Parsers" så skrev jag igår ett rätt så kladdigt utkast av det för att verifiera och förbereda tillåtna URI:er att lägga in vid skapande av en rutt i det 99 % funktionsbaserade PHP-ramverket:
// Parse the rest of the route string after the method has been extracted
// and return the valid built route string with
function cli_parse_rest_of_valid_route_syntax($routeString)
{
$entireBuiltRoute = "";
$parsedParams = [];
$inParamBuilding = false;
$currentParamBuilding = "";
$allowedCharacters = array_flip(
array_merge(
range('a', 'z'),
range('0', '9'),
['_', '-',]
)
);
// Prepare segments by splitting the route string
// by "/" and also deleting empty segments
$path = trim($routeString, '/');
$uriSegments = empty($path) ? [] : array_values(array_filter(explode('/', $path)));
// Edge case: if the route string is empty, we just return "/"
if (count($uriSegments) === 0) {
return "/";
}
// Implode again and add a "/" to the beginning of the string
$path = "/" . implode("/", $uriSegments);
$len = strlen($path);
// We now iterate through each character in the string and build the route
for ($i = 0; $i < $len; $i++) {
$c = $path[$i];
// Edge-cases, just continue if first character is "-" or "_",
if ($i === 1) {
if ($c === "-" || $c === "_") {
continue;
}
}
// First check if we are inside of a parameter building
if ($inParamBuilding) {
// Then we check if the character is "/" meaning we reached the end of the parameter
if ($c === "/") {
$inParamBuilding = false;
// Now we check if the param we built alraedy exists in the parsed params array
if (in_array($currentParamBuilding, $parsedParams)) {
cli_err_syntax("Duplicate parameter: \"$currentParamBuilding\" in route: $path!");
} elseif ($currentParamBuilding === "" || $currentParamBuilding === ":" || $currentParamBuilding === " ") {
cli_err_syntax("Empty parameter: \"$currentParamBuilding\" in route: $path!");
}
// Otherwise we add it to the parsed params array and build the route
$parsedParams[] = $currentParamBuilding;
$entireBuiltRoute .= $currentParamBuilding . $c;
continue;
}
// Check if the character is a valid character for a parameter
// and then add it to the current parameter building or ignore it
if (isset($allowedCharacters[$c])) {
if ($c === "-" && isset($path[$i - 1]) && ($path[$i - 1] === "-" || $path[$i - 1] === "_" || $path[$i + 1] === "/")) {
continue;
} else if ($c === "_" && isset($path[$i - 1]) && ($path[$i - 1] === "-" || $path[$i - 1] === "_" || $path[$i + 1] === "/")) {
continue;
} else if ($c === "_" && isset($path[$i - 1]) && $path[$i - 1] === ":") {
continue;
} else if ($c === "-" && isset($path[$i - 1]) && $path[$i - 1] === ":") {
continue;
}
$currentParamBuilding .= $c;
continue;
}
}
// If we are not inside of a parameter building, we check if the character
// is a ":" meaning we are starting a new parameter building
elseif (!$inParamBuilding) {
if ($c === ":") {
if (isset($entireBuiltRoute[strlen($entireBuiltRoute) - 1])) {
if (($entireBuiltRoute[strlen($entireBuiltRoute) - 1] === "-" || $entireBuiltRoute[strlen($entireBuiltRoute) - 1] === "_")) {
continue;
}
}
$inParamBuilding = true;
$currentParamBuilding = "";
$entireBuiltRoute .= $c;
continue;
}
if ($c === "/") {
$entireBuiltRoute .= $c;
continue;
}
if (isset($allowedCharacters[$c])) {
if ($c === "-" && isset($path[$i - 1]) && ($path[$i - 1] === "-" || $path[$i - 1] === "_")) {
continue;
} else if ($c === "_" && isset($path[$i - 1]) && ($path[$i - 1] === "-" || $path[$i - 1] === "_")) {
continue;
}
$entireBuiltRoute .= $c;
continue;
}
}
}
// Check if we are still inside of a parameter building and if so, we add it to the parsed params array
if ($inParamBuilding) {
$inParamBuilding = false;
// Now we check if the param we built alraedy exists in the parsed params array
if (in_array($currentParamBuilding, $parsedParams)) {
cli_err_syntax("Duplicate parameter: $currentParamBuilding in route: $path!");
}
// Otherwise we add it to the parsed params array and build the route
$parsedParams[] = $currentParamBuilding;
$entireBuiltRoute .= $currentParamBuilding;
}
// Then check remove endings:"/:", "/", "/-", "/_"
if (str_ends_with($entireBuiltRoute, "/:")) {
$entireBuiltRoute = substr($entireBuiltRoute, 0, -2);
} elseif (str_ends_with($entireBuiltRoute, "/")) {
$entireBuiltRoute = substr($entireBuiltRoute, 0, -1);
} elseif (str_ends_with($entireBuiltRoute, "/-")) {
$entireBuiltRoute = substr($entireBuiltRoute, 0, -2);
} elseif (str_ends_with($entireBuiltRoute, "/_")) {
$entireBuiltRoute = substr($entireBuiltRoute, 0, -2);
} elseif (str_ends_with($entireBuiltRoute, ":") || str_ends_with($entireBuiltRoute, "_") || str_ends_with($entireBuiltRoute, "-")) {
$entireBuiltRoute = substr($entireBuiltRoute, 0, -1);
}
// Preg_replace check against multiple "/" or "-" or "_" in a row
$entireBuiltRoute = preg_replace(
'/([\/_-])\1+/',
'/',
$entireBuiltRoute
);
// Final check if string suddenöly is empty, we just return "/"
if ($entireBuiltRoute === "") {
return "/";
}
return $entireBuiltRoute;
}
Kommentarer finns kvar för att på något vis visa hur "jag resonerade" för jag förkastade användningen av Regex för även om det kanske är snabbare(?) - misstänker att det är på lågnivåspråk och inte någon renodlad PHP?! 🤣- så ville jag prova på en fungerande variant som kunde köra i O(n) där n motsvarar antalet tecknen i URI:n du gett.
Utmaningen är just att hantera alla olika specialfall vilket jag i ovanstående lösning gjort på riktigt dåligt vis i och med de plötsliga och långa if-satserna. Tanken är att endast följande är tillåtet:
/[a-z0-9\-_:\/]/
Sedan blir ":" ej tillåtet så fort du kommit in i ett param-segment som exempelvis:
/users/:id/:invalidParam::::::::::----/ok-string-now-again/-not-ok-string-ends__wrongly_-//
Det finns en intressant sak här att betrakta vilket är när du stöter på "-/" eller "_/". Detta fick mig att fundera på om vissa strängbearbetningsalgoritmer har behövt fatta prioriteringsbeslut. Å ena sidan kan vi tolka att "/" betyder segmentstart på nytt segment om det inte är slutet på hela strängen.
ELLER så kan vi tolka att "-" ska få företräde och vi ska försöka läsa in till nästa giltiga tecken såsom /[a-z0-9]/ vilket betyder att vi ser "/" när "-" eller "_" kommer före det som att det inte påbörjar ett nytt segment såtillvida inte det är slutet på strängen.
Det lättaste vore såklart att vid minsta fel så får användaren i FunkCLI felmeddelande om att fel slags förväntade tecken kom baserat på tidigare tecken, likt hur programmeringsspråk slår en på fingrarna om du skriver fel token i fel ordning.
Den användartillhandahållna URI:n i FunkCLI genomgår dock en snabb omvandling innan den börjar bearbetas för att ta bort det mest onödigaste:
$path = trim($routeString, '/');
$uriSegments = empty($path) ? [] : array_values(array_filter(explode('/', $path)));
Detta ovan tar först bort onödiga mellanslag och filtrerar även ut tomma element, och sedan (syns ej ovan) slås den ihop till en sträng igen för att bearbetas.
Övriga framsteg med FunkPHP är att nu FunkCLI nu mycket snart™ kan lägga till rutter så de inte bara säkerhetskopieras lokalt utan också skrivs in i alla nödvändiga ruttfiler så det ej måste göras manuellt.
Fem steg är värre än sex steg?!
En annan sak är att nu är det bara fem steg istället för tidigare sex steg. Dessutom kommer de tre sista stegen att innehålla liknande struktur som enligt nedan:
<?php // STEP 3: Match Single Route and its associated Middlewares
// This is the third step of the request, so we can run this step now!
if ($c['req']['current_step'] === 3) {
// Load URI Routes since we are at this step and need them!
// GOTO "funkphp/routes/route_single_routes.php" to Add Your Single Routes!
// GOTO "funkphp/routes/route_middleware_routes.php" to Add Your middlewares!
$c['ROUTES'] = [
'COMPILED' => include dirname(__DIR__) . '/_internals/compiled/troute_route.php',
'SINGLES' => include dirname(__DIR__) . '/routes/route_single_routes.php',
'MIDDLEWARES' => include dirname(__DIR__) . '/routes/route_middleware_routes.php',
];
// STEP 2: Match Route & Middlewares and then
// store them in global $c(onfig) variable,
// then free up memory by unsetting variable
$FPHP_MATCHED_ROUTE = r_match_developer_route(
$c['req']['method'],
$c['req']['uri'],
$c['ROUTES']['COMPILED'],
$c['ROUTES']['SINGLES']['ROUTES'],
$c['ROUTES']['MIDDLEWARES']['MIDDLEWARES'],
);
$c['req']['matched_method'] = $c['req']['method'];
$c['req']['matched_route'] = $FPHP_MATCHED_ROUTE['route'];
$c['req']['matched_handler_route'] = $FPHP_MATCHED_ROUTE['handler'];
$c['req']['matched_params'] = $FPHP_MATCHED_ROUTE['params'];
$c['req']['matched_params_route'] = $FPHP_MATCHED_ROUTE['params'];
$c['req']['matched_middlewares'] = $FPHP_MATCHED_ROUTE['middlewares'];
$c['req']['matched_middlewares_route'] = $FPHP_MATCHED_ROUTE['middlewares'];
$c['req']['no_matched_in'] = $FPHP_MATCHED_ROUTE['no_match_in'];
unset($FPHP_MATCHED_ROUTE);
// TODO: Add function that runs the handler key for matched route step!
// INFO: Unsure of the flow if MWs first before handler or viceversa?
// TODO 2: Also add to STEP 4 & 5 for similar Handlers!
// GOTO: "funkphp/middlewares/" and copy&paste the "_TEMPLATE.php" file to create your own middlewares!
// Check that middlewares array exists and is not empty in $c global variable
// Then run each middleware in the order they are defined as long as keep_running_mws is true.
// After each run, remove it from the array to avoid running it again.
if ($c['req']['matched_middlewares'] !== null) {
r_run_middleware_after_matched_routing($c);
}
// This is the end of Step 3, you can freely add any other checks you want here!
// You have all global (meta) data in $c variable, so you can use it as you please!
$c['req']['next_step'] = 4; // Set next step to 4 (Step 4)
}
// This sets next step. If you wanna do something more before that, do that before this line!
$c['req']['current_step'] = $c['req']['next_step'];
Du börjar med att kolla om du får köra detta steg, sedan testar du att matcha rutt mot den redan smått bearbetade URI:n (ej lika omfattande som när du lägger in dina faktiska att matcha mot) och sedan ska den köra tillhörande handler och sedan alla matchade middlewares. Sedan är det nästa steg om inte utvecklaren redan retunerat JSON, bearbetad HTML-mallkod eller en enskild fil innan.
Vem tror jag att jag egentligen är? Ställ dig i ramverksledet och håll tyst!🥴
Det där sistnämnda är nog en av de få stykor FunkPHP har att komma mot giganter såsom Symfony och Laravel inom PHP-ramverksvärlden? Väldigt lite är ihopkopplat. Du kan i princip hoppa till sista steget om du vill. Du kan kontrollera om din URI börjar med "/api/" och då hoppa till datasteget där du kanske har allt som har med API:er redan att göra med och i deras handlers kan du returnera JSON utan att fastna i sista steget där något synligt webbinnehåll ska returnas.
Styrkan i det mycket "lösligt modulära" är också dess potentiella svaghet: Det kan bli möjligen lika lätt som att göra fel som att göra rätt? Således har jag också funderat på i vilken utsträckning gui-mappen kan spela en viktig utvecklarroll där du kan göra mer med mindre fel såsom att lägga till giltiga rutter med inbyggda kontroller (detta sker dock när du försöker lägga in första rutt via FunkCLI).
Detta är första gången jag förstått vad datahydrering faktiskt innebär i praktiken!🤯
Ja just det, innan jag går för denna gång så vill jag avsluta med att jag funderat rejält och även "snackat" med Google Gemini 2.5 Flash Thinking-versionen om datasteget. För jag skrev då att jag ska köra 99 % funktionsbaserat PHP-ramverk vilket inkluderar SQL-frågehanteringar.
Så... hur sjutton görs då DrizzleORM, PrismaORM, KyselyORM och liknande SQL-abstraktionslagerkodbibliotek i funktionsbaserade format? Jo, du får först lägga in dina entiteter/tabeller som strukturerad data såväl som metadata om vilka relationer de har med andra tabeller. Sedan kommer gui-mappen in i bilden:
Du får helt enkelt klicka dig igenom den första delen när du bygger din funktionbaserade SQL-fråga där du då får upp listor vilka tabeller som är kompatibla att köras "with"-liknande med. Tack vare det grafiska användargränssnittet där du bara klickar och skriver minimalt så kan mycket annat automatiseras under huven så att säga.
Exempelvis kan själva datahydreringsfunktionen skrivas så att du kan autogenerera en PHP-fil som inkluderar alla parametrar inmatade i rätt ordning till funktionen så att du med O(n) där n motsvarar antalet returnerade SQL-rader vid körd SQL-fråga kan få mycket lättanvänd datastruktur att "HTML-säkert eka ut" i dina sidfiler!😜
Vidareutbildning
Inget nytt att rapportera mer än att jag tittat lite på Symfony-video och inser hur mycket mer omfattande dess CLI är än mitt!😵
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jobb & Uppdrag
Inget nytt att rapportera.
Hobbyprojekt
Äntligen en förbättrad URI Parser!
Du läste kanske för ett par dagar sedan om att min URI Parser till FunkPHP inte var riktigt tillräcklig för att kunna hantera särskilda specialfall. Nu har jag äntligen fått till en sådan version, åtminstone med de olika testdata jag har provat. De kluriga varianterna av specialfall är när du anger antingen för kort URI eller upprepade specialtecken innan slutet på URI:erna.
Här nedan ser du min förbättrade URI Parser som jag beslutat mig för att använda till FunkPHP när Utvecklaren lägger in en ny rutt att lägga in (strängen är redan minuskelbehandlad innan funktionen anropas):
// Parse the rest of the route string after the method has been extracted
// and return the valid built route string with
function cli_parse_rest_of_valid_route_syntax_better($routeString)
{
$BUILTRoute = "";
$lastAddedC = "";
$BUILTParam = "";
$PARAMS = [];
$IN_DYNAMIC = false;
$IN_STATIC = false;
$NEW_SEGMENT = false;
$NUMS_N_CHARS = array_flip(
array_merge(
range('a', 'z'),
range('0', '9'),
)
);
$SEPARATORS = [
"-" => [],
"_" => [],
];
$PARAM_CHAR = [":" => []];
$path = trim($routeString, '/');
$uriSegments = empty($path) ? [] : array_values(array_filter(explode('/', $path)));
if (count($uriSegments) === 0) {
return "/";
}
$path = "/" . implode("/", $uriSegments);
$len = strlen($path);
for ($i = 0; $i < $len; $i++) {
$c = $path[$i];
if ($len === 1) {
return "/";
}
if ($i === 0) {
$BUILTRoute .= "/";
$lastAddedC = "/";
$NEW_SEGMENT = true;
continue;
}
if ($i === $len - 1) {
if (isset($NUMS_N_CHARS[$c])) {
$BUILTRoute .= $c;
$lastAddedC = $c;
if ($IN_DYNAMIC) {
$BUILTParam .= $c;
if (in_array($BUILTParam, $PARAMS)) {
cli_err_syntax("Duplicate parameter found in Route: \"$BUILTParam\"!");
}
$PARAMS[] = $BUILTParam;
$BUILTParam = "";
}
continue;
}
if ($IN_DYNAMIC) {
if (in_array($BUILTParam, $PARAMS)) {
cli_err_syntax("Duplicate parameter found in Route: \"$BUILTParam\"!");
}
if ($BUILTParam !== "") {
if (isset($SEPARATORS[$BUILTParam[strlen($BUILTParam) - 1]])) {
$BUILTParam = substr($BUILTParam, 0, -1);
}
$PARAMS[] = $BUILTParam;
}
$BUILTParam = "";
}
continue;
}
if ($NEW_SEGMENT) {
if (isset($NUMS_N_CHARS[$c])) {
$BUILTRoute .= $c;
$lastAddedC = $c;
$NEW_SEGMENT = false;
$IN_STATIC = true;
continue;
}
if (isset($PARAM_CHAR[$c])) {
$BUILTRoute .= $c;
$lastAddedC = $c;
$NEW_SEGMENT = false;
$IN_DYNAMIC = true;
continue;
}
continue;
}
if ($IN_DYNAMIC) {
if ($c === "/") {
if (isset($SEPARATORS[$lastAddedC]) || isset($SEPARATORS[$c])) {
$BUILTRoute = substr($BUILTRoute, 0, -1);
$BUILTParam = substr($BUILTParam, 0, -1);
}
if ($lastAddedC === ":") {
$BUILTRoute = substr($BUILTRoute, 0, -1);
$IN_DYNAMIC = false;
$NEW_SEGMENT = true;
continue;
}
$BUILTRoute .= $c;
$lastAddedC = $c;
$IN_DYNAMIC = false;
$NEW_SEGMENT = true;
if (in_array($BUILTParam, $PARAMS)) {
cli_err_syntax("Duplicate parameter found: $BUILTParam!");
}
if ($BUILTParam !== "") {
$PARAMS[] = $BUILTParam;
}
$BUILTParam = "";
continue;
}
if (isset($NUMS_N_CHARS[$c])) {
$BUILTRoute .= $c;
$BUILTParam .= $c;
$lastAddedC = $c;
continue;
}
if ((isset($SEPARATORS[$lastAddedC]) || isset($PARAM_CHAR[$lastAddedC])) && isset($SEPARATORS[$c])) {
continue;
}
if (!isset($SEPARATORS[$lastAddedC]) && isset($SEPARATORS[$c])) {
$BUILTRoute .= $c;
$BUILTParam .= $c;
$lastAddedC = $c;
continue;
}
}
if ($IN_STATIC) {
if ($c === "/") {
if (isset($SEPARATORS[$lastAddedC]) || isset($SEPARATORS[$c])) {
$BUILTRoute = substr($BUILTRoute, 0, -1);
}
$BUILTRoute .= $c;
$lastAddedC = $c;
$IN_STATIC = false;
$NEW_SEGMENT = true;
continue;
}
if (isset($NUMS_N_CHARS[$c])) {
$BUILTRoute .= $c;
$lastAddedC = $c;
continue;
}
if (isset($SEPARATORS[$lastAddedC]) && isset($SEPARATORS[$c])) {
continue;
}
if (!isset($SEPARATORS[$lastAddedC]) && isset($SEPARATORS[$c])) {
$BUILTRoute .= $c;
$lastAddedC = $c;
continue;
}
}
}
if (count($PARAMS) > 1) {
$lastParam = array_pop($PARAMS);
if (isset($SEPARATORS[$lastParam[strlen($lastParam) - 1]])) {
$lastParam = substr($lastParam, 0, -1);
if (in_array($lastParam, $PARAMS)) {
cli_err_syntax("Duplicate parameter found: $lastParam!");
}
$PARAMS[] = $lastParam;
} else {
if (in_array($lastParam, $PARAMS)) {
cli_err_syntax("Duplicate parameter found: $lastParam!");
}
$PARAMS[] = $lastParam;
}
}
if (strlen($BUILTRoute) > 2) {
if (str_ends_with($BUILTRoute, "/:")) {
$BUILTRoute = substr($BUILTRoute, 0, -2);
} elseif (
str_ends_with($BUILTRoute, "/")
|| str_ends_with($BUILTRoute, ":")
|| str_ends_with($BUILTRoute, "-")
|| str_ends_with($BUILTRoute, "_")
) {
$BUILTRoute = substr($BUILTRoute, 0, -1);
}
}
return $BUILTRoute;
}
Funktionen är tänkt att följa O(n) där n är längden på URI:n. Den är uppbyggd på att hålla koll på saker som:
- Föregående tecken
- Nuvarande tecken
- Nuvarande byggande rutt
- Nuvarande :params
- Nuvarande byggande :param-sträng
Sedan håller den koll på följande tre viktiga tillstånd (eng. "states"):
- Nytt segment "/"
- Statiskt segment (":" kom ej direkt efter "/")
- Dynamiskt segment (":" kom direkt efter "/")
För varje tecken kontrollerar den senast om föregående tecken var ett separationstecken, det vill säga, -eller _ då dessa aldrig får förekomma direkt efter varandra utan ska alltid bara förekomma en gång mellan /a-z0-9/! När en :param har byggts vilket signaleras av ett följt "/" - så tillvida inte specialfallet slutet på URI:n - så kontrolleras den mot redan inlagda :params då de ej får förekomma dubbla gånger.
Anledningen till det sistnämnda är att det sabbar användningen inuti ramverket eftersom du då skulle få bara en :param när en rutt bearbetas. Så när detta sker i funktionen så slår den ifrån direkt så Utvecklaren får skriva om detta i FunkCLI:t helt enkelt.
Ja, egentligen har - och _ ingen reell påverkan i varje rutt förutom att det ser fult ut. Till exempelvis "GET/_users-/:id-". Om du nu funderat på saker som mellanslag och varför det inte finns med så är tanken att stödja slug-generering som ett separat stöd. Då kan du skriva in en vanlig sträng där endast varje enskilda mellanslag - inte flera i följd - sedan ersätts med - efteråt. Självfallet måste varje mellanslag endast förekomma mellan /a-z0-9/.
Gratisversionen av chatGPT utmanade mig med några versioner...
När jag blev färdig med denna förbättrade version av URI Parsern så skickade jag det till gratisversionen av chatGPT som först berömde mig men ansåg att det var för många rader (> 200 rader utan kommentarer) och gav mig tre olika alternativ. Jag kopierade och lade till dem som separata funktioner och provkörde dem mot samma testdata och resultatet blev... Ja, ta en titt själv nedan vet jag!
Kom ihåg mina krav för konsekvent utseende och därmed bearbetning genom hela funktionsramverket:
GILTIGT (alla bokstäver kan också bytas ut mot 0-9 och samma skulle gälla):
/users
/u_s-e-r_s
/u-s-e-r-s
/users/:id
/u-sers/:id
/u-ser_s/:id
/user-s/:i_d
/users/:id-1/:id_2
OGILTIGT (alla bokstäver kan också bytas ut mot 0-9 och samma skulle gälla):
users
/-users/-:id
/u--sers/:-id
/_users/:_id
/users_/:id
/user__s/:id
/users/:id-
/users/:id_
/users--/:id
/u_-sers/:id
/users/:id/
/users/:id/:id
Ja, det får inte ens sluta med "/"!!!
// TESTING: ChatGPT Suggested Parser: function cli_parse_rest_fsm(string $routeString): string
// after it criticized mine! XD
PS C:\xampp\htdocs\funkphp\src> php funkcli add all_routes g/a/::::::::::::--------------___/_:_:/_:/_:/_:/:_:_:_/:_:_:_/--------------_________:///::::_:_:_:_./_-/_:_/:_/:/-_::_-a-/ t
GPT ROUTE BEFORE PARSE: "g/a/::::::::::::--------------___/_:_:/_:/_:/_:/:_:_:_/:_:_:_/--------------_________:///::::_:_:_:_./_-/_:_/:_/:/-_::_-a-/"
GPT ROUTE AFTER PARSE: "/a/a-"
// TESTING: function cli_parse_rest_of_valid_route_syntax_better($routeString)
PS C:\xampp\htdocs\funkphp\src> php funkcli add all_routes g/a/::::::::::::--------------___/_:_:/_:/_:/_:/:_:_:_/:_:_:_/--------------_________:///::::_:_:_:_./_-/_:_/:_/:/-_::_-a-/ t
WKF ROUTE BEFORE PARSE: "g/a/::::::::::::--------------___/_:_:/_:/_:/_:/:_:_:_/:_:_:_/--------------_________:///::::_:_:_:_./_-/_:_/:_/:/-_::_-a-/"
WKF ROUTE AFTER PARSE: "/a/:a"
Det gick väl sisådär för det som ska ta över allas jobb enligt vissa människor på LinkedIn, och då pratar vi om mycket simpla parsing-regler att följa. Så chatGPT provade en annan version:
// TESTING: ChatGPT URI Parser Version 3
----------------------
ROUTE BEFORE PARSE: "g/a/::sdf::_:-f:::--::_::_:---asd----8d-------___/_:_:/_:/_7:/_c:/:_:_:_/:6_:_:_/-----b---------__:55--::--5//asd_5______:///::::__4_:_:_:_./_-/_:_/:_/:/-_23::_-a-/"
WKFs URI Parser:
WKF-ROUTE AFTER PARSE: "/a/:sdf_f-asd-8d/7/c/:6/b-55-5/asd_5/:4/23_a"
ChatGPT URI Parser 3:
GPT-ROUTE AFTER PARSE: "/a/:---asd----8d/7/c/6/:--5/asd_5/4/:_-a"
----------------------
ROUTE BEFORE PARSE: "g/a/::sdf::_:-f:::--::_::_:---asd----8d-------___/_:_:/_:/_7:/_c:/:_:_:_/:6_:_:_/-----b-/:aba__/:bb__-:_-:--__/_:/:_:_-/--------__:55--::--5//asd_5______:///::::__4_:_:_:_./_-/_:_/:_/:/-_23::_-a///////:/:/:/://////////__:/_:/_://_:-/"
WKFs URI Parser:
WKF-ROUTE AFTER PARSE: "/a/:sdf_f-asd-8d/7/c/:6/b/:aba/:bb/:55-5/asd_5/:4/23_a"
ChatGPT URI Parser 3:
GPT-ROUTE AFTER PARSE: "/a/:---asd----8d/7/c/6/b/aba/bb/:--5/asd_5/4/:_-a"
Det är inte precis att jag har skrivit en egen JSX Template Parser vilket jag hade älskat om jag hade kunnat skriva. Ska jag göra det så måste jag 100 % köra med "Accept or Reject"-metodik (exempelvis "unexpected token") istället för att försöka laga eftersom det skulle kunna finnas nästintill oändligt många olika specialfall i form av försök till skadliga kodinjiceringar.
Gratisversionen chatGPTs tre olika skräpvarianter av URI Parsers?🤔
Här nedanför finner du de tre olika URI Parsers från chatGPT och jag testade alla tre och de fick alla tre "spö" av de testdata från tidigare.
// ChatGPT Suggested Parser 1:
function cli_parse_rest_fsm(string $routeString): string
{
// 1) strip extra slashes, ensure leading /
$routeString = '/' . preg_replace('#/{2,}#', '/', trim($routeString, '/'));
$state = 'SLASH';
$out = '';
$lastChar = '';
$curParam = '';
$seenParams = [];
$allowed = array_flip(array_merge(range('a', 'z'), range('0', '9')));
$seps = ['-' => 1, '_' => 1];
$len = strlen($routeString);
for ($i = 0; $i < $len; $i++) {
$c = $routeString[$i];
$n = ($i + 1 < $len ? $routeString[$i + 1] : null);
switch ($state) {
case 'SLASH':
// we just saw a slash, accept it
$out .= '/';
$lastChar = '/';
$state = 'STATIC';
break;
case 'STATIC':
if ($c === '/') {
// repeated slash? skip
if ($lastChar === '/') break;
$state = 'SLASH';
$i--; // re-process this slash in SLASH state
break;
}
if (isset($seps[$c])) {
// no two separators in a row, and not at segment start
if (in_array($lastChar, ['/', '-', '_'], true)) break;
$out .= $c;
$lastChar = $c;
break;
}
if ($c === ':') {
// colon can appear only if next is a valid param-char
if (isset($allowed[$n])) {
$out .= ':';
$lastChar = ':';
$state = 'PARAM';
$curParam = '';
}
break;
}
if (isset($allowed[$c])) {
// normal static char
$out .= $c;
$lastChar = $c;
}
// else skip invalid
break;
case 'PARAM':
if (isset($allowed[$c])) {
// append to param name
$curParam .= $c;
// but don’t append to $out until we know it’s valid
} elseif ($c === '/' || $n === null) {
// end of param
// 1) trim trailing sep from name
$curParam = rtrim($curParam, '-_');
// 2) reject empty or duplicate
if ($curParam === '') break;
if (in_array($curParam, $seenParams, true)) {
cli_err_syntax("Duplicate parameter “{$curParam}”");
}
$seenParams[] = $curParam;
// 3) commit to $out
$out .= $curParam;
$lastChar = substr($out, -1);
// 4) transition
$state = ($c === '/') ? 'SLASH' : 'END';
if ($state === 'SLASH') {
$i--; // reprocess slash
}
}
// else skip invalid param chars
break;
case 'END':
// after finishing param at end, drop anything else
break 2;
}
}
// trim trailing slash (if it’s not the only char)
return rtrim($out, '/') ?: '/';
}
// ChatGPT Suggested Parser 2:
function cli_parse_parseRoute(string $raw): string
{
// 1) normalize
$s = '/' . preg_replace('#/{2,}#', '/', trim($raw, "/ \t\n"));
$len = strlen($s);
// 2) drop illegal start chars after the slash
if ($len > 1 && in_array($s[1], [':', '-', '_'], true)) {
$s = '/' . substr($s, 2);
$len = strlen($s);
}
// prepare FSM
$out = '';
$state = 'SEG_START';
$param = '';
$seenParam = [];
$allowed = array_flip(array_merge(range('a', 'z'), range('0', '9')));
$seps = ['-' => 1, '_' => 1];
for ($i = 0; $i < $len; $i++) {
$c = $s[$i];
$n = $i + 1 < $len ? $s[$i + 1] : null;
if ($state === 'SEG_START') {
// must be '/'
$out .= '/';
$state = 'IN_STATIC';
continue;
}
if ($state === 'IN_STATIC') {
if ($c === '/') {
// new segment
$state = 'SEG_START';
$i--;
continue; // re-process slash
}
if (isset($seps[$c])) {
// only allow '-' or '_' if next is alnum
if (isset($allowed[$n])) {
$out .= $c;
}
continue;
}
if ($c === ':' && isset($allowed[$n])) {
$state = 'IN_PARAM';
$param = '';
$out .= ':';
continue;
}
if (isset($allowed[$c])) {
$out .= $c;
}
continue;
}
if ($state === 'IN_PARAM') {
// end of param on slash or end-of-string
if ($c === '/' || $n === null) {
// drop trailing sep in param
$param = rtrim($param, '-_');
if ($param !== '' && !in_array($param, $seenParam, true)) {
$seenParam[] = $param;
$out .= $param;
}
// transition
if ($c === '/') {
$state = 'SEG_START';
$i--;
continue;
}
break; // done parsing
}
// accumulate valid param char
if (isset($allowed[$c])) {
$param .= $c;
}
// else skip it
continue;
}
}
// 4) final trim
$out = rtrim($out, "/-_");
return $out === '' ? '/' : $out;
}
// ChatGPT Suggested Parser 3:
function parseRouteBySegment(string $raw): string
{
// normalize slashes
$norm = '/' . preg_replace('#/{2,}#', '/', trim($raw, "/ \t\n"));
$parts = explode('/', trim($norm, '/'));
$seen = [];
$out = [];
foreach ($parts as $seg) {
// drop any leading/trailing bugs
$seg = preg_replace('#[^a-z0-9:_-]#', '', strtolower($seg));
$seg = trim($seg, ":-_"); // no leading/trailing colon/sep
// collapse multiple ":" into one, keep only the *last* param
if (substr_count($seg, ':') > 1) {
$pieces = array_filter(explode(':', $seg));
$seg = ':' . array_pop($pieces);
}
// if it’s a param, track duplicates
if (str_starts_with($seg, ':')) {
$name = substr($seg, 1);
if ($name !== '' && !in_array($name, $seen, true)) {
$seen[] = $name;
} else {
continue; // skip empty or duplicate
}
}
// static piece can’t start with sep
else {
if (in_array($seg[0] ?? '', ['-', '_'], true)) {
$seg = ltrim($seg, '-_');
}
}
if ($seg !== '') {
$out[] = $seg;
}
}
return '/' . implode('/', $out);
}
Möjligen kanske en betalande version av chatGPT - eller Gemini 2.5 Pro - hade kunna skapat en URI Parser som uppfyllt alla kraven. Sedan är tanken med URI Parsern, inte att rengöra konstiga URI:er utan bara att läsa in en URI i giltigt format utifrån bestämd tolkning såsom att :flera :params får ej ha samma namn och varje segment måste sluta på /a-z0-9/ såväl som börja med antingen : eller /a-z0-9/. Till sist så får - eller _ endast förekomma en gång i följd för varje /a-z0-9/.
Om du kan skriva en ännu effektivare O(n) - där n är längden på URI:n - URI Parser som ska uppfylla de där kraven utan att behöva göra de där specialfallskontrollerna i slutet i min algoritm så får du jättegärna dela med dig (regex tillåts ej). Använder du in_array() för de enskilda tecknen /a-z0-9/ anses din lösning också ogiltig!🤪
Någonstans i strukturen för algoritmen har jag misslyckats att hantera dessa specialfall under tiden varav varför de hanteras i slutet efter huvuditerationen av URI:n. (Hm, nu när jag dissat in_array() så inser jag att jag använder ju det för att kontrollera giltiga :params när det då kunde bara vara en HashMap Array att kontrollera mot...?🤔)
Vidareutbildning
Inget nytt att rapportera.
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jobb & Uppdrag
Det första referensuppdraget!🫡
Nu på måndag kommer jag att ha vedertaget videomarknadsmöte med den än så länge nöjda Uppdragsgivaren sedan jag tog examen för snart ett år sedan (maj 2024). Denna Uppdragsgivare har äntligen hittat ett referensuppdrag där de då tillsammans med sin slutkund kommer att få prova den webbutvecklade webbappen jag tog fram för snart ett år sedan!
En stark fördel denna Uppdragsgivare beskriver om detta referensuppdrag är att slutkundens slutanvändare i detta fall är mycket mobilvana så då blir det bra testkörning av webbappen som är mobilanpassad trots att det inte är en reell mobilapp i sig. Förhoppningsvis kommer inte Loopias servrar att haverera för att ett dussintal(?) användare anropar PHP-webbplatsen och dess MariaDB-databas?🤔
Inget nytt veckovis jobb men kanske en helt sprillans ny arbetsplats?!🤯
Det blev inget jobb alls denna vecka från Stockholmskunden och jag har inte fått något svar från tidigare uppdragstips som annars skulle ha kommit någon gång i april i år.
Antingen rann det ut i sanden på grund av rådande omvärldsomständigheter och/eller så valdes någon annan framför mig. Som tur var samtidigt denna vecka har jag varit på en fysisk teknikträff nere på stan där jag då fick en idé.
Det är så att vid dessa fysiska teknikträffar så får teknikintresserade presentera/visa upp nästan vadsomhelst teknikrelaterat oavsett hur simpelt eller komplext det må vara så länge det är SFW såklart!😬
Min idé är då att presentera det 99 % funktionsbaserade PHP-ramverket FunkPHP om en månad eller så för att på så vis stå ut från mängden bland nyutexaminerade med minimal arbetslivserfarenhet inom Webbutveckling sedan avslutade studier.
I samma veva vid samma fysiska teknikträff så träffade jag en person som inte bara lyssnar på samma utvecklingsinriktade podcast som jag utan också har samma chef sedan förra gången jag träffade honom under liknande omständigheter. Personen berättade att en person har sagt upp sig vid deras arbetsplats och att de inte har rekryterat någon ny ännu.
Jag bad honom då att kontakta sin chef för att tipsa om att jag finns tillgänglig till ett mycket attraktivt (och troligen "föraktat") arvode som våghalsig IT-frilanskonsult redan direkt efter avslutade studier från maj 2024!😅
Det återstå att se hur det blir av med den saken. Jag går in med låga förväntningar/förhoppningar men är redo att kanske äntligen få in foten bokstavligen talat på någon fysisk arbetsplats nära där jag bor nu när jag har en 100 % (till skillnad från 99 % funktionsbaserad🤭) fungerande cykel sedan någon månad tillbaka.
Hobbyprojekt
Har jag egentligen varit "dum-i-huvudet" i över en månad nu?🤔
På tal om det 99 % funktionsbaserade PHP-ramverket som ska få mig att stå ut från mängden av annars kanske "likvärdigt erfarna juniorer" (å andra sidan går det att kritisera det hela med faktumet att jag inte ägnat tid att lära mig ReactJS, Java, eller annat efterfrågat - så hur "smart" har jag egentligen varit på senaste tid?) så har jag nytt att rapportera.
Jag har ägnat några få tals veckor på FunkPHP-ramverket och det hela började först med att dela in varje HTTP-anropsflöde i tre steg efter vedertagen filtrering av HTTP-metoder, IP-adresser och Användaragenter:
1) Matcha ruttrutt och kör sedan mellanmjukvara
2) Matcha datarutt och kör sedan ytterligare mellanmjukvara
3) Matcha sidrutt och kör sedan ytterligare mellanmjukvara
I takt med att jag skapade dessa och först tyckte att det var häftigt och framförallt komplext så kom mycket snart verkligheten i kapp mig: Det var inte så roligt när det började bli så komplext med att ha relativt upprepade funktioner för alla tre samma steg.
Dessutom började frågor väckas inom mig vilket jag då till slut insåg att om jag upplever dessa väckande frågor, hur lär inte slutanvändarna reagera? Dvs., Utvecklare som otroligt nog skulle få för sig att prova på FunkPHP.
Att först gå in i en fil för att skriva in en rutt att matcha och sedan behöva skriva in samma rutt igen för att matcha "datarutt"(och vad menas ens med att matcha mot en datarutt om vi redan har matchat mot en rutt i ruttrutten?!😵💫)...
...och sedan ska vi upprepa samma sak i rutterna för sidorna? Och det sistnämnda, vad innebär det om vi skulle vilja returnera JSON istället för att kanske tro att vi är tvingade att alltid behöva returerna en förrenderad HTML-sida?🤔
Dessa frågor som börja krypa upp till mitt medvetna i takt med mer komplex kod (några tusentals rader nästan) för att åstadkomma rätt så lite ännu såsom att bara matcha rätt rutter och också ta bort rätt rutter fick mig att inse att det behövdes en drastisk förändring.
Om inte ens jag orkar - som har suttit längst med FunkPHP - pallar med att skriva in samma rutt tre gånger och även inte veta om jag glömt det någonstans eller veta hur de egentligen är tänkta att användas så lär knappast någon som ser det hela för allra första gången palla med det.
Det blir som om Du surfar in på en webbplats med för mycket uppmärksamhetssökande innehåll och du bara tänker reflexivt, "Nepp!" och klickar bakåt.
Således tog jag bort någon tusentals rad kod vilket var mest bara för att kunna lägga till och ta bort rutter utan motsägelsefulla incidenter från de olika tre stegen.
Därmed ledde detta till…
Från onödigt oönskad komplexitet till förhoppningsvis elegant enkelhet!
I bilden nedan ser du nu "troute_route.php" vilket är den Trie-baserade datastrukturen för de inlagda rutter i filen höger om den: "route_single_routes.php" där det sistnämnda filnamnet är kvarlevnaden av att jag först tänkte matcha ruttrutter, sedan datarutter och till sist sidrutter. Hursomhelst så ser du sedan längst till vänster intressanta mappar. Håll gärna utkik på mappen "handlers".
I bilden ovanför kan du se i Terminalen att jag först provat skriva "php funkcli add r g/users" vilket inte gick för du måste ange en hanteringsfil med antingen en hanteringsfunktion eller så blir det en sådan med samma namn som hanteringsfilens namn.
Nästa bild nedanför visar då vad som händer med filerna när du skriver in ett giltigt kommando för att lägga till en giltig rutt med en giltig hanteringsfil med eventuell hanteringsfunktion:
Nu ser du i bilden ovanför hur det har tillkommit en fil "users.php" i handlers-mappen och hur de två tidigare filerna har uppdaterats. Detta beskrivs kortfattat i Terminalen tack vare flitig användning av färgade ekomeddelanden.
Först kontrollerade den att det inte redan fanns en "users.php"-hanteringsfil där hanteringsfunktionen "users" redan användes. Sedan tolkade den rutten "g/users" som "GET/users" (du kan även skriva "GET/users" exakt eller "gEt/users" eller liknande så länge det går att tolka get/, post/, put/ eller delete/ från den).
När alla dessa kontroller gjorts så skapade den hanteringsfilen "users.php" vilket då innehåller hanteringsfunktionen "users". Även filerna med rutterna och den Trie-baserade strukturen över rutterna uppdaterades vilket framgår i Terminalens diverse SUCCESS-rader.
Bilden nedanför här och nu visar sedan hur en ytterligare rutt - GET/users/:id - försökte läggas till men med samma hanteringsfil och hanteringsfunktion "users.php" och "users".
Lägg märke till i bilden ovanför hur detta misslyckades trots att den först sade att "test.php" och "users" är OK såväl som rutten GET/users/:id. Felet beskrivs i SYNTAX ERROR-ekomeddelandet: Hanteringsfunktionen "users" används redan inuti hanteringsfilen "users.php" vilket skapar en konflikt. Hur skulle två olika rutter kunna hanteras av samma hanteringsfunktion?🤔
Visst, skulle det gå ("det mesta går med programmering") men det skulle skapa en möjlig onödigt komplex utvecklingsupplevelse enligt mig eftersom du skulle behöva göra hanteringsfunktionen mer komplex än annars nödvändigt och med tiden kanske du till och från ser två rutter med samma hanteringsfunktionsnamn och du undrar om du gjort ett misstag förrän du kollar upp det. För många onödiga frågor som kan väckas enligt mig!😅
På tal om dessa nästan tjatade hanteringsfiler och hanteringsfunktioner, hur ser de egentligen ut då? Vi har ju den där "handlers/users.php"-filen. Hur ser den ut? Vänligen ta en titt nedan.
Då ser du inuti users.php-filen och hur den har två funktioner. (Tjena, jag igen här! Jag ville bara flika in med det du redan funderat på - ja, du kan INTE välja ett funktionsnamn som redan används av det 99 % funktionsbaserade PHP-ramverket. Tack för mig!)
Först - i bilden ovanför - ser du hanteringsfunktionen users och sedan ser du en funktion vilket är vad som kan anropa den. Lägg också märke till avskiljningskommentarerna vilket hjälper till att i princip CRUDa innehållet i filen. När dessa saknas så kommer felmeddelanden att även fråga Utvecklaren om den försöker jävlas med ramverket!🤣
Du ser också i bilden ovanför att vi nu med kommandot "php funkcli add r g/users/:id users=>by_id" vill lägga till en ny rutt där vi vill lägga till hanteringsfunktionen "by_id" i hanteringsfilen users.php och finns inte users.php i detta fall så kommer den att skapas först.
Således, i bilden nedanför ser du då hur den lades till och hur "users.php" har uppdaterats med den nya hanteringsfunktionen by_id samtidigt som den första users finns kvar utan några som helst problem!
Om du var extra observant i bilden ovan så såg du även hur Trie-strukturen över rutterna såväl som ruttfilen i sig uppdaterades i samband med att vi inte bara lade till en ytterligare hanteringsfunktion utan också en ny rutt. Faktum är att vi lade till en ny rutt med en hanteringsfunktion utifrån en kanske redan existerande hanteringsfil.
Som sagt, finns inte hanteringsfilen så skapas den först och då hade standardanropet i $handler = "users" istället blivit $handler = "by_id" eftersom det blev den första hanteringsfunktionen som lades till i filen. Du skulle då kunna lägga till hanteringsfunktionen users i efterhand.
De två avslutande bilderna visar hur vi först tar bort hanteringsfunktionen "users" ur hanteringsfilen "users.php" med hjälp av terminalkommandot "php funkcli delete r g/users/".
(Ja, du gissade rätt: "r" står för "route" och "h" står för "handler" om du vill använda kortvarianterna av exakt samma terminalkommandon inuti ramverket!)
En sak du kanske reagerar på i första bilden nedanför här är varför troute_route.php-filen inte har uppdaterats men route_single_routes.php-filen har det? Om du skulle gissa. Varför skulle det kunna vara så? En bugg? Eller en "feature"?
Svaret är att det är en "feature" genom att även om du matchar inuti Trie-filen med exempelvis: "GET/users/" så skulle den inte matchas mot något i route_single_routes.php-filen under GET-nyckeln.
Så det specialfallet hanteras redan och möjligen är det istället en positiv bieffekt av hur Trie-strukturen i sig i kombination med algoritmen som använder den som lett till att det möjliga specialfallet "alltid™" hanteras korrekt.
Tar du nu en titt i den nedersta bilden bland de två bilderna ovanför här så ser du till sist hur vi nu äntligen tog bort by_id-hanteringsfunktionen ur users.php-hanteringsfilen.
Jag ritade in i rött för annars kan bilden verka missvisande. När filnamn är struken i VSCode så innebär det att den inte finns kvar i mappen där den låg, dvs., i handlers-mappen i detta fall.
Läs även terminalmeddelandet "Deleted Handler File "users" and Function "by_id"!" längst ned i Terminalfönstret i den nedersta bilden ovan. Dessutom återspeglas det även i hur GET-nyckeln i både Trie-filen såväl som ruttfilen är tom igen!🫡
Hur hög bör man bli på sina egna digitala fisar?
Okej, nu är det dags att tagga ned sin egen "digitala parad"😒 Det hela är både bättre än ingenting och samtidigt kommer det tillbaka till faktumet att: "Varför har du inte lärt dig ReactJS, Java, Kafka, Terraform, Kubernetes, global lastbalansering, Bloom-filtrering, med mera som den riktiga utvecklingsarbetsmarknaden faktiskt efterfrågar?🙄"
Jag säger det där "självelaka" för att inte råka bli hög på min egen digitala fis då jag är både "stolt" över det lilla jag har åstadkommit med den - relativt talat för egen del - stora mängd kod som finns i repot än så länge.
En annan sak jag funderat på som Du kanske skulle vilja hjälpa mig genom att bara lämna ett svar här i tråden är: Vad skulle du ens vilja göra inuti en så kallad "hanteringsfunktion" som matchat mot en given rutt?🤔
Å ena sidan så kan du göra precis allt där eftersom det är ju bara en funktion och så fort du skriver echo eller giltig HTML-kod så börjar du skicka ut HTML-data till klienten som anropat servern.
Detta betyder att detta 99 % funktionsbaserade PHP-ramverk är väldigt "opinionsfritt" i hur du kan välja att använda det. Samtidigt är friheten också en svaghet i sådana slags ramverk enligt mig: Då blir det ju nästan som om du inte förhåller dig inom givna ramar när du skapar ditt verk🤦♂️?
En fördel med så kallade "envisa ramverk" (eng. opinionated frameworks) som exempelvis Angular är att när du hoppar in i ett redan påbörjat projekt så vet du mer eller mindre hur saker och ting kan ha gjorts för att de har behövt hålla sig inom givna ramar när de kodat sina verk.
Om Du själv har åsikter om sådant gällande ramverk allmänt talat och/eller hur det knyter tillbaka till det "mycket avsmalna ramverk" jag håller på att bygga så får du gärna dela med dig här!📝^_^
Vidareutbildning
Inget nytt att rapportera.
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Hallå, någon som fortfarande läser?🤔 Jädrans snålhet att endast visa \d+k utan några decimalpunkter. Allt för att vara mobilanvändarnas stackars smala skärmar till lags!?🤣
Jobb & Uppdrag
Till veckan hoppas jag på vedertaget veckovis jobb från Stockholmskunden såväl som någon form av återkoppling från en person jag träffade vid den fysiska teknikträffen denna vecka.
En annan person som skulle ha hört av sig - som sagt var - någon gång i slutet av april 2025 har ej gjort det ännu så där förmodar jag att det har runnit ut i sanden och/eller att personen funnit en annan utan att ha meddelat mig.
Hobbyprojekt
Typexempel på svagheten/kruxet med funktionell programmering
Det går nu att skapa nycklarna handler, data och middlewares för en given metod/URI-rutt. En sak som inte hade provats ännu var att faktiskt köra de olika hanteringsfunktionerna för respektive testhanteringsfil. Bilden nedanför visar vad jag menar med det hela (det du ser då är lösningen medan jag sedan ska beskriva problemet som inträffade):
Du ser att kommandot - (utgå ifrån att alla kommandon hädanefter börjar med prefixet "php funkcli ") - först är add r g/users users och sedan add d g/users users. Okej, det gör du faktiskt inte för terminalen förstördes när jag minskade bredden för att skärmdumpa. Hursomhelst så ser det ut så här (ryck på axlarna över upprepningen av "Data" här och var - [anmärkning: stavfelet är redan fixat och sjösatt på repot!]):
[FunkCLI - INFO]: Parsed Handler: "funkphp/handlers/r_users.php" and Function: "r_users"
[FunkCLI - INFO]: ROUTE: "g/users" parsed as: "GET/users"
[FunkCLI - SUCCESS]: Added Handler "funkphp/handlers/r_users.php" with Function "r_users" in "funkphp/handlers/r_users.php"!
[FunkCLI - SUCCESS]: Added Route "GET/users" to "funkphp/routes/route_single_routes.php" with Handler "r_users" and Function "r_users"!
[FunkCLI - SUCCESS]: Rebuilt Route file "funkphp/routes/route_single_routes.php"!
[FunkCLI - SUCCESS]: Recompiled Trie Route: "funkphp/_internals/compiled/troute_route.php"!
PS C:\xampp\htdocs\funkphp\src> php funkcli add d g/users users
[FunkCLI - INFO]: Parsed Data Handler: "funkphp/data/d_users.php" and Function: "d_users"
[FunkCLI - INFO]: ROUTE: "g/users" parsed as: "GET/users"
[FunkCLI - SUCCESS]: Added Data Handler "funkphp/data/d_users.php" with Function "d_users" in "funkphp/data/d_users.php"!
[FunkCLI - SUCCESS]: Added Data Data Handler "d_users" and Function "d_users" to Route "GET/users" in "funkphp/routes/route_single_routes.php"!
[FunkCLI - SUCCESS]: Rebuilt Route file "funkphp/routes/route_single_routes.php"!
[FunkCLI - SUCCESS]: Recompiled Trie Route: "funkphp/_internals/compiled/troute_route.php"!
Trots att kommandot var först add r g/users users och sedan add r g/users users så skapades: r_users.php respektive d_users.php i handlers respektive data-mapparna. Varför sker det för?
Jo, du gissade helt rätt: med funktioner som körs i det globala omfånget (eng. global scope) så skulle det annars ha inneburit att först kan "test.php" köras men inte nästa "test.php" för det skulle bli omdeklarationsfel av PHP-tolken. Jag missade detta förrän igår när jag faktiskt fick för mig att provköra vardera hanteringsfil för en given rutt för handler- och data-nycklarna.
Det hela löses då med att lägga till ett relevant prefix så som "r_" för handler-nycklar, "d_" för data-nycklar, "m_" för middlewares-nycklar och möjligen "p_" för pages-nycklar. Sistnämnda funderar jag fortfarande på då dessa är tänkt att vara de faktiska HTML-baserade filerna som ska gå igenom en så kallad mallmotor (eng. "template engine") likt Twig från Symphony och/eller Blade från Laravel.
Dessa prefix har också ett litet gulligt långsökt påskägg i sig: r_, d_, p_. Ser du? Det gamla namnet för ramverket: RDP🤯 (nej, förknippa inte det med Remote Desktop Protocol från Microsoft!! )
Hursomhelst lösningen med prefixet kommer delvis från inspirationen med hur databashanteringslösningar nyttjar prefix av tabellnamn framför sina kolumnnamn för att undvika namnkollisioner mellan kolumner med samma namn fast från andra tabeller. Snabbt, smidigt och elegant helt enkelt!🙌
En annan sak som också "fungerar™" är att om du skulle ha två rutter där båda använder samma test.php-filer för rutten och all data så kommer det inte bli motsägelsefullt beteende för att du raderar ena eller andra rutten.
Det är inte inlagt ännu att säkerhetskopiera raderade hanteringsfiler ännu men det är enkelt att implementera. Så du kan med det enkla kommandot "delete r g/users" radera hela rutten och dess tillhörande hanteringsfunktioner och/eller hanteringsfiler:
PS C:\xampp\htdocs\funkphp\src> php funkcli delete r g/users
[FunkCLI - INFO]: ROUTE: "g/users" parsed as: "/users"
[FunkCLI - SUCCESS]: Deleted Single Route "GET/users" from the Route file!
[FunkCLI - SUCCESS]: Recompiled Trie Route: "funkphp/_internals/compiled/troute_route.php"!
[FunkCLI - SUCCESS]: Deleted Handler File "handlers/r_users.php" and Function "r_users"!
[FunkCLI - SUCCESS]: Deleted Data Handler File "data/d_users.php" and Function "d_users"!
Om det finns andra rutter som använder andra hanteringsfunktioner inuti någon utav de där hanteringsfilerna så kommer det meddelas istället i terminalen så att du är medveten om att filerna fortfarande finns kvar men inte allt innehåll i dem.
Med det rapporterat så anser jag att de preliminära bitarna för handlers och middlewares är avhandlade och det är dags att gå vidare till en av de större utmaningarna med ett 99 % funktionsbaserat PHP-ramverk, nämligen det här med databashanterandet...
Kodkampen: ORM-baserad databashantering vs. 99 % funktionsbaserad databashantering
Om du följer One Piece-animen just nu så känner du säkert igen det ovan. Det är "Joy-Wookie-Boy"🤣 som drämmer till Propel ORM med FunkPHP i väntan på de stora bossarna inom ORM-baserad databashantering, det vill säga Drizzle, Prisma och Laravels Eloquent bland annat.
Det är en intressant utmaning som är indelat i två sammanflätade frågor:
1. Hur ska liknande datahydrering kunna åstadkommas på en 100 % funktionell väg?
2. Hur ska utvecklarupplevelsen kunna matcha den ORM-baserade?
Notera att jag sa 100 % funktionellt för datahydreringen, inte datainhämtningen vilket är ett MySQLi-objekt än så länge. Möjligen blir det 100 % funktionsbaserat någon dag™.
Den första punkten är att få till datahydreringen, det vill säga att kunna få tillbaka en önskvärd datapresentation som gör det enkelt att i sidfilerna kunna skriva i stil med:
<h1>Test</h1>
{% foreach d.users as d.user %}
<p>{{d.user.name}}</p>
<p>{{d.user.age}}</p>
<p>{{d.user.and_so_on}}</p>
{% endforeach %}
(ja, anmärk att det är Twigs syntax med {%%} som används. Får se om det blir den eller Blade-syntaxen)
Bokstaven d är den globala $c['d']-variabeln där all data samlas efter validering och/eller hämtning från databas. Sedan kan klassiska extract() nyttjas för att få ut all data på en nivå under $c så vi får $d så att säga.
Självklart kommer vi INTE att använda htmlspecialchars() i stil med:
<?= htmlspecialchars($d['user']['name']);?>
Något sådant vore fullständigt ansvarslöst, eller hur?!😜
Datahydrering på funktionell väg kommer innebära att vi måste representera all data på ett sådant vis att en funktion(!) sedan kan avgöra vilka tabeller som är med (eng. "with") vilka andra tabeller. Eller snarare talat: vilken tabell som ska anses vara en lista av en annan förutsatt att de har ett samband redan.
Exempelvis tabellerna users och posts har tydlig relation: flera poster tillhör en användare. Men när du hämtar data så kan du antingen visa användare med flera poster per användare ELLER poster med den användare som den posten ägs av:
// Användare med en post
{{user.post.id}}
// En post med dess ägande användare
{{post.user.id}}
Du gör exakt samma SQL-fråga i båda fallen men hur du "hydrerar" all data är vad som avgör vilken av de olika ovanstående varianterna som sedan går att skriva i mallfilerna och inte.
Lösningen jag filar på i skallen och sedan i kod med hjälp från Gemini 2.5 Flash (och Pro i den utsträckning det går) är att vid funktionsanropet så anges vilken tabell som är med vilken annan tabell och då har vi "berättat" för funktionen om det ska förberedas för att kunna skriva: {{user.post.id}} eller {{post.user.id}}.
Den andra punkten är något du kanske insåg när du insåg nu att du ska alltså mata in tabellerna och alla kolumnerna du vill använda dig av för att de ska "hydreras" på ett lämpligt vis innan de skrivs in i dina mallfiler.
Så... vem... orkar... skriva kanske dussintals kolumner för några tabeller för att sedan komma ihåg vilket argument som nu var att säga vilken tabell som är med vilken annan tabell? Och hur ska detta lösas med ytterligare nivåer? Det vill säga, varje användare med alla poster med alla kommentarer för varje post för varje användare. Där har vi då en tabell med en tabell som i sin tur är med en ytterligare tabell.
Det blir en spännande utmaning rent funktionsmässigt att tugga igenom på ett effektivt vis. Fast mer spännande är då hur vi ger en utvecklingsupplevelse som känns lika banal som att bara skriva:
<?php // KODEXEMPEL NEDAN ÄR HÄMTAT FRÅN:
// https://www.doctrine-project.org/projects/doctrine-orm/en/sta... 2025-05-04
// list_bugs.php
require_once "bootstrap.php";
$dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC";
$query = $entityManager->createQuery($dql);
$query->setMaxResults(30);
$bugs = $query->getResult();
foreach ($bugs as $bug) {
echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n";
echo " Reported by: ".$bug->getReporter()->getName()."\n";
echo " Assigned to: ".$bug->getEngineer()->getName()."\n";
foreach ($bug->getProducts() as $product) {
echo " Platform: ".$product->getName()."\n";
}
echo "\n";
}
Möjligen är ovan inte så mycket bättre men det är objektorienterat och när du skriver $bug i ditt IDE så får du tack vare ditt IDE upp alla tillhörande kolumner i form av metoder att anropa - förutsatt så klart att du har konfigurerat rätt med entiteterna utifrån hur just Doctrine ORM tycks fungera.
Huruvida detta tar mer på PHP-tolken än den lösning jag tänker "kodsnickra" fram återstå att se. Min tanke är helt enkelt gui-vägen där du i princip kryssar i tabeller och deras kolumner du vill välja och sedan genom "smarta" rullgardinsmenyer kan du välja vilka tabeller som ska hydreras med vilka andra tabeller förutsatt att de har en nödvändig relation (dvs., PK & FK). gui-mappen ligger utanför mappen som ska sjösättas på servrar och kollar alltid så det är i localhost eller 127.0.0.1 annars tvärnekar skriptet från att köras.
Sedan när du gjort det så får du färdiggenererad PHP-kod vilket i princip är funktionsanropet med alla argument på rätt plats. På så vis så kommer FunkPHP närmast den underbara biten med objektorienterad programmering i IDE:er där du kan "titta inuti ett objekt för att bara kunna välja de objektmedlemmar som faktiskt finns inuti objektet".
Det är alltså planen för att lösa de där två punkterna så gott som det går!😅
Förhoppningsvis kommer det inte bli allt för klurigt. Allt handlar i början om hur SQL-tabellerna matas in och representeras på ett lämpligt vis inuti den globala $c['data']['tables']-variabeln (även $c['data']['relationships'] kommer att finnas där vilket är den nyckel som bestämmer de möjliga relationerna mellan tabellerna).
Du får önska mig lycka till med de sakerna tills vidare! 😁🫡
(Och vi har ju bara varit inne och smått petat på hela biten med mallmotorn som ska försöka matcha Twig och Blade i både prestanda och tillfredsställande utvecklingsupplevelse!😱😨😬)
Vidareutbildning
Inget nytt att rapportera mer än att igår såg jag "Topp 10 Galnaste x86 Assembler-instruktionerna":
På återseende!
Mvh,
WKF.
P.S. Det finns en lösningsvariant med hanteringsfilerna med hanteringsfunktioner inuti sig vilket är att använda $inner_funk1 = function (){//Abstraction Magic};-variabler inuti varje hanteringsfils primära returfunktion. Däremot upplever jag personligen att det skulle kunna upplevas lite "grötigt" än att tydligt ha function name1(){}, function name2(){} inuti samma fil?🤔Vad anser du?💭D.S.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jobb & Uppdrag
Ett kommande miniuppdrag?!
Jag kunde ej delta vid Uppdragsgivarens Sälj-/Utvecklingsmöte idag men jag pratade alldeles nyss med den än så länge nöjda Uppdragsgivaren över telefon vilket innebar mycket goda nyheter för båda inblandade parter.
Uppdragsgivaren vill även tilldela mig runt tre dagars arvode för att kunna hålla koll på det snart kommande referensuppdraget där den "mobilanpassade" (ej mobilbyggda(!)) webbappen kommer att sjösättas i skarpt läge genom att köras av några dussintals slutanvändare samtidigt.
Det blir både kul och riktigt skrämmande för nu blir det upp till bevis i hur bra den mobilanpassade webbappens arkitektur kan samspela med de troligen mycket begränsade Loopia-servrarna där den ligger sjösatt!🥶
Jag har verkligen 0 % insikt i hur lätt eller svårt servrar kan belastas beroende på anrop per sekund, om någon DDOS-attack inträffar samtidigt, lastbalanseringsbiten och så vidare. Dessutom saknar jag insikt i vad Loopia har för "smärtgräns" i sina servrar gällande HTTPS-anrop!🙃
Under det lilla test jag gjorde med Uppdragsgivaren för länge sedan så skedde en liten bugg i samband med att mobiltelefoner försattes i strömsparläge eller i något läge där JS-motorn i mobilens webbläsarapp inte längre ville prioritera körningen av JS-asynkron kod.
I och med det så mejlade jag över instruktioner om hur den JS-baserade nedräkningsfunktionen ska fungera med majoriteten av deltagande mobiltelefoner vars webbläsare troligen är Chrome eller Safari.
Det handlar om fyra sammanflätande saker:
1. Stäng av strömsparläge på mobilen.
2. Stäng av skärmsläckaren på mobilen.
3. Sänk ljusstyrkan om mobilen redan har låg batterinivå.
4. Ha igång webbläsarappen under hela förloppets gång.
Bäst vore såklart riktiga mobilappar som kunde använda egen nedräkning och med tillgång till att notifiera slutanvändaren när den räknat färdigt. På tal om notifieringar...
Notification API är vad det är...😮💨
Jag provade lägga till en Notifikationsfunktionalitet med hjälp av Notification API:t för att ge lite extra livskvalitet för slutanvändarna:
if ("Notification" in window && Notification.permission === "default") {
Notification.requestPermission().then(function (permission) {
if (permission === "granted") {
console.log("Notification permission granted.");
} else if (permission === "denied") {
console.warn("Notification permission denied.");
}
});
} else if (
"Notification" in window &&
Notification.permission === "denied"
) {
console.warn(
"Notification permission is denied. Cannot send notifications."
);
}
Dessvärre verkar det inte fungera på mobila Android-telefoners medföljda Chrome-appar trots att det är aktiverat att vissa Notifikationer och exakt samma förfrågan om att tillåta Notifikationer visas både i skrivbordswebbläsaren såväl som i den mobila webbläsarappen Chrome.
Följande kod nedan är vad som försöker skicka iväg en notifikation efter nedräkning skett och detta fungerar i FireFox skrivbordsversion på Windows 10:
if ("Notification" in window && Notification.permission === "granted") {
try {
const options = {
body: "GREJ klar!",
icon: "https://www.webbplats.se/favicon.ico",
};
const notification = new Notification(
"GREJ klar!",
options
);
console.log("Timer finished. Push notification attempted.");
} catch (error) {
console.error("Error showing notification:", error);
}
} else if ("Notification" in window) {
console.warn(
"Timer finished, but notification permission was not granted."
);
} else {
console.warn(
"Timer finished, but Notification API is not supported."
);
}
Kodraden console.log("Timer finished. Push notification attempted."); inträffar och det där konsolmeddelandet går att avläsa i skrivbordswebbläsarens Konsolterminal.
Således har jag ingen aning om varför det inte fungerar i Chrome-mobilappen på de två Androidtelefoner jag har kunna provat där jag såg till att Notifikationer fick och skulle visas i respektive Chrome-app.
Själva förfrågan om att tillåta Notifikationer dök upp i båda telefonerna men båda telefonerna stenvägrade att visa någon notifikation efter färdig nedräkning. Båda notifikationsdelarna lades upp i samma svep så det kan inte bero på olika JS-versioner för då skulle inte ens Notifikationsförfrågan ha dykt upp.
Jag fick ett tips om: https://cleverpush.com/en/test-notifications/ där jag provade mobilen då och då fungerade Notifikationerna. Däremot så fick jag två förfrågningar innan något visades: En först från webbläsaren och sedan en ytterligare från webbplatsen vilket såg ut att vara helt skräddarsydd dialogruta som jag acceptera inuti.
Så då undrar jag om det är något ytterligare som behövs för att kunna skicka Notifikationer i mobiltelefonernas webbläsar-appar?! Hm... Kanske någonting med prenumerationer? Får kika vidare på det.
Jag hittade nyss denna källkod från Mozilla: https://github.com/mdn/dom-examples/blob/main/to-do-notificat... vilket kanske kan ge mig ledtrådar på hur jag ska få till notifieringsbiten på mobiltelefoners webbläsar-appar...
Snabbkoda ihop ett övervakningssystem?!🤔
Uppdragsgivarens preliminära resultat med deras allra första referensuppdrag vilket involverar den mobilanpassade webbappen jag har tagit fram åt dem kommer att ha en avgörande effekt inte bara på deras framtida uppdrag utan även mina som följd.
Således är det av yttersta vikt att i skarpt läge kunna övervaka under körningens gång för att upptäcka eventuella buggar när flera slutanvändare använder samma mobilanpassade webbapp samtidigt under en begränsad tid.
Därför kommer jag att fråga Uppdragsgivaren om de önskar ett mycket förenklat övervakningssystem så det går att fånga upp om det verkar som om någon har "fastnat" och inte kommer vidare i den mobilanpassade webbappen för att på något vis då kunna "starta om för dem".
Här är det av yttersta vikt då att inte krocka med databasens läsningar och/eller skrivningar gällande slutanvändarnas användning av den mobilanpassade webbappen utan nyttja andra tabeller som endast indirekt har data om användarflödet. Detta finns redan som tur var så då har jag en idé jag ska presentera för Uppdragsgivaren.
Går det att enkelt med minimal databaspåfrestning att övervaka användarflödet hos slutanvändarna och hjälpa till vid behov - exempelvis att Uppdragsgivaren kan "nollställa" en slutanvändare utan att de behöver logga ut och in igen - så skulle det nog skapa oerhört mervärde både för slutanvändarna såväl som Uppdragsgivarens eget arbetsflöde.
Hobbyprojekt
Inget mer nytt om FunkPHP idag förutom det pinsamma misstaget när jag skrev min allra första felrapportering någonsin på GitHub vilket var så klart i det officiella repot för ingen mindre än PHP själv i egen hög och elfantstoraktig entitet!!! 🤯
TLDR; Jag glömde använda korrekt HTTP-huvud när jag ekade ut meddelandena vilket gjorde att webbläsaren försökte tolka HTML-taggarna istället för att bara skriva ut dem. Detta ledde till att "Lilla Jag™" trodde att jag hade upptäckt en bugg i var_export-funktionen i PHP!
Alltså: Jag skickade header("content-type: text/html"); när jag borde ha skickat header("content-type: text/plain");. Detta hade aldrig inträffat om jag <självcensur> <självcensur> <självcensur> <självcensur> <självcensur> <självcensur> <självcensur>😬!
Vidareutbildning
Inget nytt att rapportera.
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jobb & Uppdrag
Förra veckan fick jag minimalt med jobb från Stockholmskund men mer än noll men ändå kritiskt lågt. Förhoppningsvis denna vecka får jag besked från tidigare och än så länge nöjd Uppdragsgivare om kommande kortvarigt uppdrag på runt tio timmar med ett arvode på runt femhundralappen.
Det är också en Kodfika!-videoträff nu på fredag där jag högst sannolikt kommer att närvara vid. Tydligen kommer det att vara någon "gästföreläsare" som kommer att prata om något rätt så kort då dessa videoträffar snittar på lite drygt en timme.
Samtidigt är jag också redo för otroligt nog samtal från lokalt IT-bolag här nere på stan där jag har kontakt med en person som ska ha mejlat sin chef om eventuell rekrytering av Junior-utvecklare. De har nämligen en "lista över personer med kortvarig arbetserfarenhet inom webbutveckling" - att sådana listor ens protokollförs!🤣
Som vanligt återstår det att se hur mycket/lite jobb det blir denna vecka!🫡
Hobbyprojekt
Hur föredrar du att skriva så kallade valideringsregler (eng."Validation Rules")?
Så här:
$DX = [
'input_filed1' => 'required("Must be filled!")|string|min:10|max:111',
'input_filed2' => 'required|string|email|min:10|max:50',
'input_filed3' => 'required|integer|min:10|max:50',
];
Eller så här:
return array(
'input_filed1' =>
['required' => [
'err' => 'Must be filled!'
], 'string' => [
'err' => null
], ['min' => [
'err' => null,
'val' => '10'
]], ['max' => [
'err' => null,
'val' => 111
]]],
'input_filed2' => ['required' => [
'err' => null
], 'string' => [
'err' => null
], 'email' => [
'err' => null
], ['min' => [
'err' => null,
'val' => '10'
]], ['max' => [
'err' => null,
'val' => 50
]]],
'input_filed3' => ['required' => [
'err' => 'Must be filled!'
], 'integer' => [
'err' => null
], ['min' => [
'err' => null,
'val' => '10'
]], ['max' => [
'err' => null,
'val' => 50
]]],
);
Den första så klart om du inte är galenpanna. Den första dock innefattar en hel del strängmanipulation vilket jag lärde mig från fjolårets Advent of Code (2024) är mycket processorkrävande eller "mycket bytes-tuggande för datorn" med andra ord.
Här brukar det förklaras att det är en så kallad kompromiss (eng. "tradeoff") det hela handlar om. Men då har jag läst historier om när - Ordo förbjuder - du vill validera flera tusentals rader vid en HTTPS-förfrågan som exempelvis vid CSV- eller annan Excel-liknande data med hjälp av sådan förenklad strängbaserad valideringsregelsyntax.
Således vill jag då med mitt "Hobbyprojekt" erbjuda något stil med:
//Validation Handler File - Write your Validation Rules
// in the $DX variable and then run the command
// `php funkcli compile v v_test=>$function_name`
// to get the optimized version below it!
//DELIMITER_HANDLER_FUNCTION_START=v_test
function v_test(&$c) // <GET/test>
{
//DELIMITER_VALIDATION_USER_START=v_test
$DX = [
'input_filed1' => 'required("Must be filled!")|string|min:10|max:111',
'input_filed2' => 'required|string|email|min:10|max:50',
'input_filed3' => 'required|integer|min:10|max:50',
];
//DELIMITER_VALIDATION_USER_END=v_test
//DELIMITER_VALIDATION_COMPILED_START=v_test
return array(
'input_filed1' =>
['required' => [
'err' => 'Must be filled!'
], 'string' => [
'err' => null
], ['min' => [
'err' => null,
'val' => '10'
]], ['max' => [
'err' => null,
'val' => 111
]]],
'input_filed2' => ['required' => [
'err' => null
], 'string' => [
'err' => null
], 'email' => [
'err' => null
], ['min' => [
'err' => null,
'val' => '10'
]], ['max' => [
'err' => null,
'val' => 50
]]],
'input_filed3' => ['required' => [
'err' => 'Must be filled!'
], 'integer' => [
'err' => null
], ['min' => [
'err' => null,
'val' => '10'
]], ['max' => [
'err' => null,
'val' => 50
]]],
);
//DELIMITER_VALIDATION_COMPILED_END=v_test
};
//DELIMITER_HANDLER_FUNCTION_END=v_test
Ja, du har helt rätt: DET SER HELT ÄRLIGT TALAT RIKTIGT FÖRJÄVLIGT UT!
Alla dessa //DELIMITER-kommentarer överallt för att det 99 % funktionsbaserade ramverket ska kunna göra sin magi bakom kulisserna när du exempelvis kör php funkcli compile v v_test för att kompilera din vedertagna valideringsregelsyntax till den mer högoptimerade hashnyckelbaserade valideringsregelsyntaxen.
Det finns ingen Utvecklare med tillräckligt hög självrespekt som skulle få för sig att skriva den högoptimerade varianten framför den förenklade "strängbaserade" varianten. Detta innebär att det behövs dessvärre antingen ett gui och/eller den där $DX-variabeln som möjliggör den mer utvecklarvänliga valideringsregelsyntaxen. (Ja, det är inte för inte att variabeln heter just DX!)
Att dölja/abstrahera bort implementationer bakom klasser och objekt såväl som att även inkapsla variabler och funktioner inuti varje klass och objektinstans för att slippa namnkonflikter är garanterat en eller flera av skälen till varför varenda jädrans Open Source-projekt jag kikar i har klasser och objekt!😂
En annan rolig sak på tal om de faktiska implementationerna: Än så länge i mitt hobbyprojekt så är nästan majoriteten av koden att kontrollera rätt slags anropsargument i funktionerna innan någonting görs.
Ta bara något så simpelt som att läsa in en fil: först kontrollera att mappen där den ligger finns och är läsbar, sedan kontrollera att filen finns och är läsbar och sedan kontrollera om samma fil går att skriva till för att jag kanske vill skriva över den som i fallet med den högoptimerade valideringsregelsyntaxen som skrivs över med hjälp av de skräckinjagande //DELIMITER-kommentarerna.
Dessutom ska allt kollas med isset(), array_key_exists, med mera. Här kan jag således se den stora vikten typstrikta språk erbjuder då de för det mesta kan härleda vad för slags data du skickar runt och om det kommer att gå eller inte beroende på körflödet. Jag förstår bättre varför många JS-utvecklare rentav inte kan leva utan TS eftersom det gör att du slipper göra alla dessa paranoida manuella kontroller.
Det ska nämnas att alla dessa datakontroller innan någon körlogik faktisk körs är främst i CLI-verktyget för att förhindra motsägelsefulla situationer från att uppstå. Exempelvis ska du ju inte ha två rutter som egentligen leder till samma plats: GET/users/:id och GET/users/:by_id. Om du redan har lagt till en hanteringsfil med hanteringsfunktion så kan du ju inte få lägga till den igen för samma rutt.
Diskussionsfrågan där kan då istället bli om samma hanteringsfunktion i samma hanteringsfil ska få användas till flera rutter eller inte?🤔 Å ena sidan möjliggör det återanvändbar kod och å andra sidan kan det väcka frågor hos Utvecklaren när denna ser att två olika rutter pekar mot samma hanteringsfil och samma hanteringsfunktion och de kanske då tyst ställer sig frågan inombords,"Ska båda verkligen ha samma eller har jag gjort något fel här?"
Liten rolig klurig sak som orsakade oönskat beteende men som nu är fixat finner du nedan:
// FROM: "function cli_add_a_validation_handler()" in funkphp/_internals/functions/cli_funs.php"
$fileContent = file_get_contents($handlersDir . $handlerFile . ".php");
if ($fnName !== null && strpos($fileContent, "//DELIMITER_HANDLER_FUNCTION_START=$fnName\n") !== false) {
// Find what <method/route> that is already using it or just show default error!
$pattern = "/\/\/DELIMITER_HANDLER_FUNCTION_START={$fnName}.*\n.*?<(.*?)>.*/si";
if (preg_match($pattern, $fileContent, $matches) && isset($matches[1])) {
cli_err_without_exit("Validation Function \"$fnName\" in Validation Handler \"funkphp/validations/$handlerFile.php\" is already used by Route \"{$matches[1]}\"! (unless false comment)");
} else {
cli_err_without_exit("Validation Function \"$fnName\" in Validation Handler \"funkphp/validations/$handlerFile.php\" has already been created!");
}
cli_info("If you know what Route that should be using that Validation Handler instead, just manually change it in the Route file!");
}
Det är if-satsen $fnName !== null && strpos($fileContent, "//DELIMITER_HANDLER_FUNCTION_START=$fnName\n") !== false där den kontrollerar efter en särskild DELIMITER men om "\n" inte lades till så kunde den tolka "test2" som "test" och därmed påstå att du redan har lagt in "test2" fast du i verkligheten inte hade gjort det.
För övrigt så är det som är kvar innan skarp "lansering" (med en användare - jag!🤭) om sisådär drygt en månad följande:
Konvertering av förenklad valideringsregelsyntax till högoptimerad valideringsregelsyntax.
Fundera ut hur manuellt införda tabeller ska kunna "kopplas" (eng. be mapped to) konsekvent till rätt inmatningsfält skrivna i den förenklade valideringsregelsyntaxen för just nu så är dessa separerade medan i objektorienterat ramverk så finns redan den starka kopplingen genom arv av olika klasser.
Datahydrering när förenklad SQL-förfrågan "klickas" fram med hjälp av ett gui vilket endast fungerar i localhost.
En mycket förenklad mallmotor (eng. Template Engine) som i princip i sin första version endast kommer att nyttja preg_replaces() istället för riktig så kallad parsing.
Själva gui:t till allt ovan.
Avslutningsvis i denna del av denna gångs uppdatering så har jag till och från haft lite "hopplöshetskänslor" likt jag hade under olika projektuppgifter under distansutbildningens gång.
Känslor likt tankar, "Vad håller jag på med?", "Vem kommer någonsin att bry sig lika mycket om detta som jag?" och så vidare. Men jag har av viss erfarenhet vetat att det går över och det är bara att koda vidare.
Trots att det kanske bara blir jag som använder hobbyprojektet i slutändan så har jag ändå lärt mig en hel del och det blir helt klart en mycket mer intressant "snackis" vid (icke-)kommande uppdrags-/ och eller arbetsintervjuer än "Här är min femte TODO-app inuti en OpenAI-wrapper i MERN-stacken av någon helt outgrundlig anledning!😬"
Vidareutbildning
Inget nytt att rapportera mer än att jag för några dagar sedan tittade på följande YT-klipp om så kallad Pratt Parser:
Det verkar som om det inte går att komma ifrån att du börjar med att tokenisera din inmatning för att bygga upp ett så kallat Abstrakt Syntaxträd (eng. Abstract Syntax Tree; AST) som du sedan arbetar igenom. Du börjar alltså med en Lexer och sedan en Parser som tuggar igenom det Lexern har tagit fram vid första omgången.
Jag verkar ha någon liten Ordo-besatthet i att saker och ting måste bara kunna gå att genomföras med en loop annars blir det ju O(2*n) eller ännu värre! Eller så är det som så att det är värt högre O-värde genom att det blir lättare att hantera och om jag förstått det hela korrekt så kommer AST-strukturen att bidra med att kunna fånga upp och bearbeta "otydligheter".
Det klassiska fallet är att veta huruvida du är inuti en sträng eller inte och samtidigt inte låta dig "luras" av avskiljningstecken såsom enkel- och/eller dubbelfnuttar. Förvisso går det med en boolean att delvis hålla koll på det och titta på om avskiljningstecknet \ kom precis före nästa sedda enkel- och/eller dubbelfnutt. Två booleans behövs för att veta vilken slags sträng (" eller ') du är inuti.
För övrigt så förstår jag nu lite bättre delvis hur miniräknare troligtvis fungerar tack vare Pratt Parser. YT-klippet påstod även att ternary ?-operatorn också använder sig av liknande för att kunna skilja på vad som är uttrycket och de olika uttrycken efteråt såväl som stöd för flera på rad.
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
(Felstavningsvarning: För mycket text för att inbyggda rättstavningen i Firefox ska fungera - Volontära Språkpoliser har förvarnats!)
Jobb & Uppdrag
Ingen ny upplaga av arbete från Stockholmskunden idag och inget nytt ännu från än så länge nöjd Uppdragsgivare, eller det lokala IT-företaget nere på stan. En mycket och möjligen snart kritiskt svag start på denna arbetsmånad.
Kombinationen av lågkonjunktur och/eller ens Juniora titel gör det inte hela heller enklare då flera - mer erfarna (Mjukvaru)Utvecklare - på Frilansare Sverige-Slacken rapporterar om hur svårt det är att få tag på Uppdrag just nu trots att konsultbaserade(?) Ework Group pumpar ut nya platsannonser skrikandes dagligen i e-postinkorgen.
Så med det sagt, låt oss ta något mer positivt.
Hobbyprojekt
På grund av bristande jobb än så länge denna vecka så blev det ytterligare nytt fokus på det 99 % funktionsbaserade PHP-ramverket vilket Mycket Snart™ är färdigt för så kallad enanvändarlansering😆!
"//DELIMITERS, BE GONE!!!"
Jag skrev igår om hur förskräckligt det är att se alla dessa //DELIMITERS överallt i filerna. Idag tog jag mig då tiden att reda ut detta en gång för alla: Ska det verkligen behövas en Pratt Parser-liknande implementering för att hitta början och slutet på en hanteringsfunktion inuti en hanteringsfil?🤔
function get_match_function_regex($fnName)
{
if (!is_string_and_not_empty($fnName)) {
cli_err_syntax("[cli_match_function_part] Function name must be a non-empty string!");
}
if (!preg_match("/^[a-z_][a-z0-9_]+$/", $fnName)) {
cli_err_syntax("[cli_match_function_part] \"$fnName\" must use this string syntax: `[a-z_][a-z0-9_]+`!");
}
$regex = '/^function (' . $fnName . ')\(\&\$c\)\s*\/\/ <([A-Z]+)\/([a-z0-9_\-\/]*)>\s*$.*?^};$/ims';
return $regex;
}
function get_match_all_functions_regex($handlerType)
{
if ($handlerType !== "r" && $handlerType !== "d" && $handlerType !== "v") {
cli_err_syntax("[get_match_all_functions_regex] Handler type must be a non-empty string. Choose between: 'r','d', or 'v'");
}
$regex = '/^function (' . $handlerType . '_[a-z0-9_]+)\(\&\$c\)\s*\/\/ <([A-Z]+)\/([a-z0-9_\-\/]*)>\s*$.*?^};$/ims';
return $regex;
}
function get_match_all_functions_regex_without_capture_groups($handlerType)
{
if ($handlerType !== "r" && $handlerType !== "d" && $handlerType !== "v") {
cli_err_syntax("[get_match_all_functions_regex] Handler type must be a non-empty string. Choose between: 'r','d', or 'v'");
}
$regex = '/^function ' . $handlerType . '_[a-z0-9_]+\(\&\$c\)\s*\/\/ <[A-Z]+\/[a-z0-9_\-\/]*>\s*$.*?^};$/ims';
return $regex;
}
function get_match_return_function_regex($fnName, $method, $route)
{
if (!is_string_and_not_empty($fnName)) {
cli_err_syntax("[get_match_return_function_regex] Function name must be a non-empty string!");
}
if (!is_string_and_not_empty($method)) {
cli_err_syntax("[get_match_return_function_regex] Method must be a non-empty string!");
}
if (!is_string_and_not_empty($route)) {
cli_err_syntax("[get_match_return_function_regex] Route must be a non-empty string!");
}
return '/^(return function)\s*\(&\$c, \$handler\s*=\s*.+$.*?^};/ims';
};
function get_match_dx_function_regex()
{
return '/\$DX\s*=\s*\[.*?];$/ims';
};
function get_match_dx_return_regex()
{
return '/return\s*array\(.*?\);$\n/ims';
}
Om du tar en snabbtitt inuti Regex-galenskaperna så finns det en genomgående "hjälte" inom Regex:
$makeMyDayPunkString = '/$.*?^};$/ims';
Först och främst har vi modifikationstecknen "i", "m" och "s" vilket möjliggör att läsa in flera rader i följd oavsett teckenstorlek. När detta sedan kombineras med att vi kräver att något avslutas med i fallet ovan };$ så säger vi att den sista raden som avslutas måste då vara det sista innan radbrytning.
Du kanske ser "\n" i någon Regex vilket är för att skapa ny rad när något ersätts med hjälp av den Regex. En annan sak att anmärka är att de Regex som ska matcha mot en hanteringsfunktion använder ^ för att de är alltid början på en ny rad vilket även gäller den returnerade hanteringsfilens funktion som matchar hanteringsfunktion.
Funktionen som använder sig av flest Regex från ovan är följande "dagens hjältefunktion":
// VERY IMPORTANT WARNING: This function calls a function which uses eval() to parse the validation file!!!
function cli_compile_dx_validation_to_optimized_validation()
{
global $dirs, $exactFiles, $settings, $delimiters, $argv, $dirs;
if (!isset($argv[3]) || !is_string_and_not_empty($argv[3])) {
cli_err("cli_compile_dx_validation_to_optimized_validation() expects a string as input!");
}
$handlerDir = $dirs['validations'] ?? "";
[$handlerFile, $fnName] = get_valid_handlerVar_or_err_out($argv[3], "v");
if (!dir_exists_is_readable_writable($handlerDir)) {
cli_err("Validation Directory \"$handlerDir\" not found or non-readable/writable!");
}
if (!file_exists_is_readable_writable($handlerDir . $handlerFile . ".php")) {
cli_err("Validation Handler file \"$handlerFile.php\" not found in \"funkphp/validations/\" or not readable!");
}
$fnNameRegex = get_match_function_regex($fnName);
$dxVarRegex = get_match_dx_function_regex();
$dxReturnRegex = get_match_dx_return_regex();
$fileContent = file_get_contents($handlerDir . $handlerFile . ".php");
$matchedFn = preg_match($fnNameRegex, $fileContent, $matches);
if (!$matchedFn) {
cli_err("Validation Function \"$fnName\" not found in Validation Handler File \"funkphp/validations/$handlerFile.php\". Check for mispellings or typos?");
}
$matchedEntireFnName = $matches[0] ?? null;
$matchedEntireFnCopy = $matchedEntireFnName;
$matchedDX = preg_match($dxVarRegex, $matchedEntireFnName, $matches2);
if (!$matchedDX) {
cli_err_without_exit("The \"\$DX\" variable not found in Validation Function \"$fnName\" in Validation Handler File \"$handlerFile.php\".");
cli_info_without_exit("Make sure it is intended using CMD+S or CTRL+S to autoformat the Validation Handler File!");
cli_info("It must start as an array: `\$DX = ['<anything_inside_here>'];` or it will not be found!");
}
$matchedSimpleSyntax = $matches2[0] ?? null;
$evalCode = null;
try {
$evalCode = "\nreturn $matchedSimpleSyntax";
$evalCode = eval($evalCode);
} catch (Throwable $e) {
cli_err_without_exit("The \"\$DX\" variable was found but could not be parsed as a valid PHP Array!");
cli_info_without_exit("Make sure it is intended using CMD+S or CTRL+S to autoformat the Validation Handler File!");
cli_info("It must start as an array: `\$DX = ['<anything_inside_here>'];` or it will not be found!");
}
if ($evalCode === null) {
cli_err_without_exit("The \"\$DX\" variable was found but could not be parsed as a valid PHP Array!");
cli_info_without_exit("Make sure it is intended using CMD+S or CTRL+S to autoformat the Validation Handler File!");
cli_info("It must start as an array: `\$DX = ['<anything_inside_here>'];` or it will not be found!");
}
if (is_array($evalCode)) {
cli_info_without_exit("Found \"\$DX\" variable parsed as a valid PHP Array!");
}
$matchedReturn = preg_match($dxReturnRegex, $matchedEntireFnName, $matches3);
if (!$matchedReturn) {
cli_err_without_exit("The \"return array();\" statement not found in Validation Function \"$fnName\" in Validation Handler File \"$handlerFile.php\".");
cli_info_without_exit("Make sure it is intended using CMD+S or CTRL+S to autoformat the Validation Handler File!");
cli_info("The last part of the array() - `);` - must be indented to the same level as the \"return array (\" part!");
}
$matchedReturnStmt = $matches3[0] ?? null;
$optimizedRuleArray = cli_convert_simple_validation_rules_to_optimized_validation($evalCode);
$optimizedRuleArrayAsStringWithReturnStmt = "return " . var_export($optimizedRuleArray, true) . ";\n";
$replaced = str_replace($matchedReturnStmt, $optimizedRuleArrayAsStringWithReturnStmt, $matchedEntireFnName);
$newFileContent = str_replace($matchedEntireFnCopy, $replaced, $fileContent);
$result = file_put_contents($handlerDir . $handlerFile . ".php", $newFileContent);
if ($result === false) {
cli_err("FAILED compiling Validation Rules to Optimized Rules in Validation Function \"$fnName\" in \"$handlerFile.php\". Permissions issue?");
} else {
cli_success_without_exit("SUCCESSFULLY COMPILED Validation Rules to Optimized Rules in Validation Function \"$fnName\" in \"funkphp/validations/$handlerFile.php\".");
cli_info("IMPORTANT: Open it in an IDE and press CMD+S or CTRL+S to autoformat the Validation File again!");
}
}
Det som händer i funktionen ovan är att vi läser först in en hel PHP-fil som definitivt existerar och går att läsa och skriva till.
Sedan använder vi ett par Regex från ovan för att plocka ut matchande hanteringsfunktion, $DX-variabeln såväl som retur array();-satsen. Vi använder den vanligtvis icke-rekommenderade och den mest farligaste funktionen i hela PHP - eval() - för att snabbt få tillbaka en giltig array() eller null.
Denna skickas i sin tur till en funktion - ej färdig ännu - vilket omvandlar den till en optimerad valideringsregelsyntax. Inuti själva $DX-variabeln som måste vara en array för att matchas skrivs den förenklade valideringsregelsyntaxen. Om konverteringen lyckades så kan nu Regex-matchade retur array();-satsen ersättas med den optimerade valideringsregelsyntaxen.
Till sist så kan vi ta den ersatta och stoppa in igen i en kopia av den matchade hanteringsfunktion vilket i sin tur sedan stoppas in i den redan inlästa $fileContent och sedan skrivs filen över igen. På så vis har vi ersatt på rätt plats och utan några jädrans "//DELIMITERS"!🥰
(Det kan vara som så nu att jag skrev det hela helt fel ovan men det fungerar som det ska i praktiken!)
Det roligaste med den funktionen är att själva flödet för vad som skulle ske och i vilka steg kom helt från mig medan Regex-syntaxförslag och övriga kodsnuttar kom från Gemini 2.5 Preview-gratisversionen.
Möjligen hade den kommit med något annat och kanske mer effektivt men då istället något jag inte riktigt förstått ännu för att vilja använda med gott kodsamvete (*host* Vibe-programmerare *host*).
I princip handlar funktionen om att "simulera" med hur vi kopierar och klistrar in delar av en text inuti en annan för att göra en särskild ersättning. Varför vi egentligen inte bara direkt matchar $DX-variabeln och dess sammanhängande retur array();-sats är för att mönstret upprepas förutom hanteringsfunktionsnamn!
Okej, det går nog med en mer invecklad Regex som först matchar hela hanteringsfunktionen och sedan inuti den matchningen direkt matchar $DX-variabeln och dess retur array();-sats. Möjligen en mycket kort framtida refaktorisering med möjligen färre kodrader men svårare att få snabb överblick över.
På tal om refaktorisering...!🤯
Idag passade jag även på att göra vissa funktioner mer generella. Exempelvis så är hanteringsfilerna och deras hanteringsfunktioner exakt likadana för rutter, data och validering. Om något är väldigt lika, vad bör du göra då? Hm?😜
Mot den bakgrunden skapade jag idag ett par generella funktioner för att skapa och även kunna ta bort antingen enskilda hanteringsfunktioner inuti hanteringsfiler eller även hanteringsfilerna i samma veva om det var sista hanteringsfunktionen i hanteringsfilen.
Exempelvis funktionen function cli_add_a_data_or_a_validation_handler($handlerType) tar antingen bokstaven 'd' eller 'v' för att skapa en hanteringsfil och/eller hanteringsfunktion inuti den valda hanteringsfilen, medan funktionen function delete_handler_file_with_fn_or_just_fn_or_err_out($handlerType, $handlerVar) tar bort en sådan och denna stödjer även 'r'!
Tack vare denna refaktorisering så blev inte bara funktionerna kortare och färre - det hela bidrog till att korta ned med cirka ~500 rader (fast 150+ rader är säkert bara kommentarer!😂) i FunkCLI-filen vilket är den primära filen förrän gui:t är färdigt för en Utvecklare att sitta i och med.
Det här med Output Buffering i PHP...
Egentligen fick jag aldrig lära mig PHP på rätt vis: du skickar först HTTP-huvuden via headers() sedan skickar du all nyttolast (eng. "payload").
Detta även när du bara skriver vanliga HTML-taggar eller via echo, print, printf(), var_dump(), var_export(), vprintf(), debug_print_backtrace(), phpcredits(), phpinfo(), ReflectionExtension::info() eller felmeddelanden beroende på konfiguration med display_errors och/eller error_reporting.
Specialfallet är med var_export() som kan ta true i andra argumentet för att lagra i en variabel vilket du kan se att jag använder i kompileringsfunktionen för valideringsregelsyntaxkonverteringen.
Så jag blev alltid lite förvirrad över varför jag ibland såg text i webbläsaren och ibland inte förrän jag fick mycket kortfattad introduktion till hur PHP på ett "högnivåsätt" fungerar: du skickar HTTP-huvuden först för dessa kan betraktas som "Instruktioner för hur webbläsaren ifråga ska tolka din kommande nyttolast!". Hur ska du tolka något du saknar instruktioner på att tolka först?😵💫
När vi då tar kontroll över utmatningsbufferten i PHP så kan vi göra mycket coola saker som att lagra vissa saker i "den framtida nyttolasten" medan vi skickar iväg diverse HTTP-huvuden såsom kakor och annat gött medan detta inte hade fungerat om vi först råkat ekat ut något och sedan skickat iväg en kaka för då hade vi fått felmeddelanden om att HTTP-huvuden redan skickats.
Varför jag då nämner detta är för att jag har funderat på hur jag ska göra med Mallmotorn när nästlade PHP-filer ska förberedas som "nyttolast" och sedan skickas ut efter alla HTTP-huvuden. Nästlade PHP-filer är alltså att du har en fil som vill inkludera en annan fil som i sin tur kanske också vill inkludera en annan fil.
Och vad händer när include_once eller kanske mer rekommenderade require_once körs? Jo, PHP-filens innehåll läses in som om den redan fanns där så om du inte har koll på din utmatningsbufferts status (påslagen eller avslagen?) så riskerar du att skicka iväg "nyttolast" istället för att bara göra något med den först.
Fast varför ens använda den när det finns en risk att dess högsta tillåtna buffertstorlek inte går att ställa in hos ett webbhotell där webbplatsen sjösätts?
Istället kan vi nyttja kraften av file_get_contents() och använda oss av preg_replace_callback() för att iterativt läsa in ytterligare i varje huvudsakliga fil med hjälp av en återanropsfunktion som då kan ersätta den nuvarande inlästa filen.
Föreställ dig då {%part(headers/test)%} vilket då kommer tolkas som att läsa in test.php-filen i mappen headers vilket i sin tur ligger i en rotmapp som alltid part() utgår ifrån.
När test.php-filens innehåll har lästs in och ersatt {%part(headers/test)%} så tittar vi igen i samma inlästa text: Finns det någon ny {%part(mapp/fil)%} att läsa in?
Om det finns så upprepar vi annars så är vi färdiga med att läsa in alla nästlade tillhörande PHP-filer för den där filen att senare kompilera till en vanlig PHP-fil som sedan ekas ut till webbläsaren. På så vis slipper vi hålla på med potentiellt buffertstorleksbegränsad utmatningsbuffert!🫡
"Det är planen tills att verkligheten har slagit till en på käften!"
- ett omskrivet citat med upphovsperson Mike Tyson
Vidareutbildning
Inget nytt att rapportera mer än att jag såg följande YT-klipp idag om att "React kanske håller på att implodera":
Det anmärkningsvärda var inte klippet i sig utan en kommentar om att "PHP håller på att göra en comeback!" vilket jag så klart är lite partisk kring!😬
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jag har både mycket dåliga nyheter och mycket goda nyheter ! 🫡
Jobb & Uppdrag
Stockholmskunden meddelade för någon vecka sedan att det pågår kontorsflytt och att deras nuvarande verksamhet har pausats vilket betyder att jag har varit definitionsmässigt talat varit arbets- och/eller uppdragslös. Under den tiden har jag då fokuserat på hobbyprojektet.
Idag pratade jag med den än så länge fortfarande mycket nöjda Uppdragsgivaren om min situation. Personen sa att denne skulle dra i sina trådar av kontakter och se om jag kunde få någon kontaktuppgift där jag skulle kunna presentera mig. Jag har rådfrågat i lämpliga Discord-grupper om hur detta bör gå till.
Utmaningen är att jag inte vet vad jag kan hjälpa med förrän jag vet delvis om domänet, fått titta delvis på icke-sekretessbelagd kodbas, och även veta konkreta saker som utvecklingsstack, deadlines, etc. Annan utmaning är sedan också tidigare sagt att Uppdragsgivaren ej är tillräckligt tekniskt insatt för att kunna gå i gott ord om mig och det får absolut inte uppstå något missförstånd om att jag skulle vara duktigare än jag faktiskt är.
Möjligen dyker det upp något under veckans gång eller så blir det en "ekonomiskt tunn" sommar så att säga!
Hobbyprojekt
Om Du öppnar upp min öppna källkod för mitt hobbyprojekt - vilket du bör veta exakt vad det är om du är en regelbunden läsare - så råder jag dig att kika i filen "_internals/functions/d_data_funs.php" och börja på rad 70 där du ser funktionen som handlar om att först hämta en valideringsfil eller bara returnera null samt lagra felmeddelande i lämplig undernyckel i $c['err'].
Om du tittar i koden nedan här i inlägget :
<?php
// Data Handler File - Created in FunkCLI on 2025-05-24 08:52:25!
// IMPORTANT: CMD+S or CTRL+S to autoformat each time function is added!
function d_test(&$c) // <POST/test/:id>
{
// Created in FunkCLI on 2025-05-24 08:52:25! Keep "};" on its
// own new line without indentation no comment right after it!
$v_file = funk_use_validation_get_validation_array_or_err_out($c, "v_test");
$validatePOST = funk_use_validation($c, $v_file, "JSON");
$test = [
'v' => $c['v'],
'v_ok' => $c['v_ok'],
'v_config' => $c['v_config'],
'v_data' => $c['v_data'],
];
dj($test);
};
return function (&$c, $handler = "d_test") {
return $handler($c);
};
Så ser du hur den först då hämtar följande valideringsfil:
<?php
// Validation Handler File - Created in FunkCLI on 2025-05-24 08:52:30!
// Write your Validation Rules in the
// $DX variable and then run the command
// `php funkcli compile v v_test=>$function_name`
// to get the optimized version below it!
// IMPORTANT: CMD+S or CTRL+S to autoformat each time function is added!
function v_test(&$c) // <POST/test/:id>
{
// Created in FunkCLI on 2025-05-24 08:52:30! Keep "};" on its
// own new line without indentation no comment right after it!
// Run the command `php funkcli compile v v_test=>v_test`
// to get optimized version in return statement below it!
$DX = [
'name' => 'string|required|between:2,30',
'age' => 'integer|required|between:18,30',
];
return array(
'name' =>
array(
'<RULES>' =>
array(
'required' =>
array(
'value' => NULL,
'err_msg' => NULL,
),
'string' =>
array(
'value' => NULL,
'err_msg' => NULL,
),
'between' =>
array(
'value' =>
array(
0 => 2,
1 => 30,
),
'err_msg' => NULL,
),
),
),
'age' =>
array(
'<RULES>' =>
array(
'required' =>
array(
'value' => NULL,
'err_msg' => NULL,
),
'integer' =>
array(
'value' => NULL,
'err_msg' => NULL,
),
'between' =>
array(
'value' =>
array(
0 => 18,
1 => 30,
),
'err_msg' => NULL,
),
),
),
);
};
return function (&$c, $handler = "v_test") {
return $handler($c);
};
Den i sin tur består av en $DX-variabel vars namn antyder om Utvecklarupplevelsen. Du skriver sedan php funkcli compile v v_test=>v_test i terminalen så länge du har PHP version 8.2 lägst installerat. Denna skapar då return array() vilket är det som laddas in i $v_file (eller null vid misslyckande) i d_test.php-filen.
Sedan börjar det nästintill otroliga (tycker jag):
Först körs funk_use_validation (se kodrad 714 i källkoden) vilket tar parametrarna $c-globala variabeln, den optimerade valideringssyntaxregelarrayen, källan för data att validera (t.ex. $_GET, $_POST eller JSON än så länge, $_FILES kommer senare!) samt valfri boolean-värde. Det sistnämnda är huruvida antingen all data måste valideras för att det ska lagras i $c['v_data']-variabeln eller om delvis validera data får lagras. Jag frågade Gemini 2.5 Thinking och tipset var att tänka på inkompletta formulär där du då kan återge validerad data igen medan övriga fält rensas och varningar visas.
Funktionen anropar nu min mest komplicerade funktion hittills (av egen åsikt) vilket är funk_validation_recursively_improved (kodrad 412 i källkoden) vars namn antyder att den är både rekursiv och förhoppningsvis förbättrad. När vi pratar om rekursiva funktioner så har vi den klassiska frågan, "Vad är basfallet som ska avbryta rekursionen och därmed de stackade anropen? Svaret i detta fall är: funk_validation_validate_rules (kodrad 129 i källkoden).
Varje anrop till funk_validation_validate_rules innebär att ett korrekt enskilt typ av data valideras nu. Det kan vara strängar, associativa/numrerade arrayer, olika slags nummer, booleaner, och bland strängar finns det även särskilda typer såsom password och email bland annat. Dessa kommer då med egna valideringsregler inbyggda direkt i deras datatyper. När enskilt data har validerats korrekt så kommer den inte ha några felmeddelanden förknippade med några regler eller så får den det på regelnivå. Rekursionen sker främst i data med data under sig såsom associativa och/eller numrerade arrayer. Sistnämnda har även specialfallet när själva roten av alla data är en numrerad array.
På tal om datatyper så var det viktigt att skapa en konsekvent utvecklingsupplevelse för den enda troliga slutanvändaren (Jag! 😂) så för varje valt givet datafält så kan det ha en datatyp. En sak som tog väldigt lång tid var alla kontroller och varningar och i många fall stoppandes felmeddelanden vilket äger rum vid kompileringen av den förenklade valideringssyntaxen vilket betyder att Utvecklaren måste korrigera något.
Då jag inte har några problem att skriva mycket så finns det gått om gula varningar, röda felmeddelanden såväl som många blåa uppmuntrades informationstexter om hur det hela ska användas.
En annan viktig sak var att göra det enkelt: skriver du datatypen string och sedan min, max eller kanske between så ska den bara "fatta" att det nu är mångbytes-mässiga längden på en sträng medan för heltalsdatatypen integer så är det värdestorleken istället. Och ja, antalet element i arrayer gäller också.
Bilden nedanför visar exempel på när JSON-data skickas och först visas var_dumpad data som skickats och sedan returvärdet av valideringen ('v').
Lägg märke till i detta fall så är v_data null på grund av att funktionen anropades med true i booleanen (standardvärde när det lämnats tomt vid funktionsanrop) för huruvida all data måste validera annars "hydreras" ingen validerad data?
Du ser också "v_config" vilket är en intressant lösning att kunna nyttja tack vare den globala $c-variabeln. Den innehåller valfria specialegenskaper vid valideringen. Exempelvis kanske varje enskild regel som tillhör samma data som valideras få extraregeln "stop" vilket då avslutar all resterande validering för det datumet (data singular).
En annan specialregel är "field" för om du tittar i bilden så ser du "age" vilket ser illa ut i standardmeddelandet. Vad du anger efter "field" och dess kolon är vad du vill att fältet ska visa i standardfelmeddelandet.
Kodsnutten nedan visar också hur du får in dina egna felmeddelanden vilket måste ske inuti dubbla citattecken och parenteser. Själva hela $DX-variabeln bearbetas med reguljära uttryck vilket kräver att arrayen börjar med enkelfnuttar även om du skulle kunna köra med dubbelfnuttar.
$DX = [
'name' => 'string("Det måste vara en sträng!")|required|between:2,30',
'age' => 'integer("Heltal, tack!")|required|between:18,30("Age between 18 and 30 please!")|field:Age',
];
Observera att när du skriver eget felmeddelande så följer inte fältet med (t.ex. Age) för den regeln utan det är vad du får i alla standardfelmeddelanden när du inte angett något eget sådant för en given regel i en given inmatningsdatum (data singular)).
En rolig tvist på det hela var när jag hade fått till numrerade arrayer vilket du anger med hjälp av "*" som exempelvis:
$DX = [
'user.*' => 'list("Varje användare måste vara en numrerad array!")|required|between:2,30',
'user.*.age' => 'integer("Heltal, tack!")|required|between:18,30("Age between 18 and 30 please!")|field:Age',
];
Snabb frågesport fråga nu: Vad innebär between:2,30 för datatypen array?🤔Precis! Nu agerar den som antalet element du behöver - färst 2 och flest 30. Färre än 2 eller fler än 30 utvärderat av den inbyggda array count-funktionen i PHP så blir det felmeddelande.
Har du förresten kommit på vad det var för tvist jag stötte på med numrerade arrayer? Det finns ju ett specialfall med numrerade arrayer. Det vill säga: vad händer när hela roten är en enda kanske stor och lång numrerad array? Alltså:
$DX = [
'*' => 'list("Varje användare måste vara en numrerad array!")|required|between:2,30',
'*.age' => 'integer("Heltal, tack!")|required|between:18,30("Age between 18 and 30 please!")|field:Age',
];
Konstruktionen för funk_validation_recursively_improved var först ej hanterad för detta för den utgick ifrån att det alltid var ett förutbestämt datafält på vänstra sidan och högra sidan innehöll då reglerna (se den blossande kodraden 420 i källkoden) vilket satt sig på sin vassa kant så fort det är en numrerad array på vänstra sidan.
I princip blev lösningen att hantera specialfallet vilket då innebar att funktionen har två stora if-satser varav första är störst som du väl ser. När det väl lyckades så kände jag verkligen så här!
Hm, vad händer då om det är?:
$DX = [
'*.*' => 'list("FML!")|required|between:2,30',
'*.*.age' => 'integer|required|between:18,30|field:Age',
];
I nuvarande implementering så stöds detta ej. Det stoppande rött felmeddelande vid kompilering av den förenklade valideringssyntaxen så det kommer ej att gå. Och skulle Utvecklaren manuellt ändra i return array()-delen och sedan klaga så får de skylla sig själva.
Jag fick det delvisa tänket från ett YT-klipp om varför vissa saker stannar upp i TV- & datorspel för att datorer är värdelösa på att hantera oändlighet (åtminstone teoretiskt talat). Vad du gör i spel är att något som ska animeras hela tiden kanske har en tid som nollställs efter en viss loop och sedan börjar om igen.
Spel som saknar detta kan få skumma effekter då om du befinner dig på samma plats i flera timmar eller dagar som i fallet med Super Mario 64 (tror det är sju dagar du ska stirra på den animerade tavlan för vattenbanan innan du kan spöa Bowser en andra gång).
Med andra ord så kan du låta spelet stå på i sju dagar så kommer tavlan att sluta animeras för att dess inbyggda tidigare ej nollställs utan flyttalet överflödas. Frågan är då: Är detta rimlig normalanvändning av spelet så att Nintendos programmerare borde stå till svars? Personligen anser jag nej.
Liknande resonemang för jag då i den bistra sanningen och faktumet att trots alla kompileringsgenererade koddelar så kan Du ju ändå ändra i koden som ej fångas upp under körning för allt kan inte fångas upp utan att skapa onödig så kallad "overhead".
Sedan är det upp till var och en att tycka till hur mycket mjukvara ska rädda slutanvändarna från sig själva och i vilken utsträckning de ska ta vuxet ansvar för sin egen existens och därmed dagliga handlingar.
Hursomhelst, om vi skippar den skumma tangenten från ingenstans så är jag personligen rätt stolt över hur det fungerar i dagsläget. Funktionen som kompilerar förenklade valideringssyntaxregler till förhoppningsvis optimerade valideringsregler finns i cli_funs.php på kodraderna 992 till och med 2588!
Och majoriteten av koden är i princip bara att kontrollera så att "det är svårare att råka göra fel samtidigt som det är lättare att göra rätt!" vilket är en livsfilosofi jag bär med mig sedan något decennium tillbaka.
Möjligen går det att bryta ut denna valideringsbit till en egen simpel modul och därmed repo för skarp lansering trots att det redan finns en uppsjö av PHP-baserade valideringsbibliotek.
Ja, just det: Den har två nuvarande svagheter mycket värda att nämna:
1. Den klarar som sagt var ej flerdimensionella numrerade arrayer såsom [0][0], [0][0][0], och så vidare. Möjligen går det att få till med viss ytterligare kontroll i rekursionsbeteendet men istället föredrar jag att bara tillhandahålla de separata valideringsreglerna åt Utvecklaren som får så lov att skriva egna nästlade loopar!
2. Till skillnad från Laravel (vars valideringsregel jag tagit inspiration från och även kommit på andra som inte finns med där!) så finns inga supersmarta valideringsregler som kan agera utifrån vad andra datafält har validerats som. Möjligen går detta att få till genom att lagra diverse "valideringstillstånd" i $c['v_config']-variabeln.
Samtidigt gällande punkt två ovan så tänker jag att det kan finnas en risk för "förvirring" eftersom du då måste förstå vad specialregeln gör. Den som suttit med valideringsbiblioteket i dussintals timmar så är det ju så klart uppenbart likt någon som suttit med en bana i Super Mario Maker 2 medan den som kastas in i det hela inte alls kanske ser det "som är så uppenbart om du bara hade lagt lika många timmar på det som jag!"
Det finns däremot en datatyp som vill nyttja faktumet att det finns andra datafält som den vill samspela med och det är strängdatatypen password_confirm:regular_password. Alltså, du skriver password_confirm som datatyp och sedan måste du ange vilket annat datafält som den ska jämföra mot vilket i sin tur måste vara strängdatatypen password.
En viktig sak att fixa här också är att strängdatatypen password måste valideras före detta sker annars kommer det bli som om lösenordet aldrig angavs och därmed kan det hela valideras felaktigt. (återigen intressant ytterligare exempel på alla små "gotchas!" när en försöker skapa komplext sammanhängande system som hjälper Utvecklaren samtidigt som den kan göra sin magi med så lågt Ordo-värde som möjligt!)
Vidareutbildning
Inget nytt att rapportera mer än att jag har väl förhoppningsvis blivit lite på bättre att skriva rekursiva funktioner? Det gäller att hålla koll på vad som förs vidare i den nästlade rekursiva stacken och här är variabler som skickas som referenser underbara att använda då ett rekursivt anrop kan innehålla en djupare nyckelnivå medan den behåller samma nivå en rekursion uppåt.
Exempelvis fick jag katastrofala kodförslag från Gemini 2.0 Thinking som tyckte att sättet att hålla koll på nyckeldjupet i $c['v']-variabeln där alla valideringsfelmeddelanden lagras var att först array-merge för nuvarande djup och sedan köra foreach för att bygga ihop nuvarande nyckeldjup till $c['v']-variabeln.
Det skulle betyda att varje array_merge och foreach skulle bli längre för varje nyckeldjup! Och denna/dessa LLM:er ska ersätta Utvecklare Snart™? Fast det är ju inte Utvecklarna som ersätts av LLM:er utan det är personer som kan sparka Utvecklarna som ersätter dem med "AI" i hopp om att det är/blir ekonomiskt värt det i slutändan.
Nä, nu måste jag gå och varva ned mig lite!
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
Jobb & Uppdrag
Inget nytt att rapportera mer än att jag gick in på en Facebook-grupp och besvarade vad som såg ut att vara en uppdragsannons efter tips från en god Discord-vän. Inget svar ännu dock.
Hobbyprojekt
Några anmärkningsvärda förändringar
I det ambitiösa hobbyprojektet har jag nu tagit fram en uppdaterad version av de så kallade Valideringshanteringsfilerna och deras innehållande Valideringsfunktioner:
<?php
namespace FunkPHP\Validations\v_test;
// Validation Handler File - Created in FunkCLI on 2025-05-29 21:02:46!
// Write your Validation Rules in the
// $DX variable and then run the command
// `php funkcli compile v v_test=>$function_name`
// to get the optimized version below it!
// IMPORTANT: CMD+S or CTRL+S to autoformat each time function is added!
function v_test2(&$c) // <>
{
// Created in FunkCLI on 2025-05-29 21:02:46! Keep "};" on its
// own new line without indentation no comment right after it!
// Run the command `php funkcli compile v v_test=>v_test3`
// to get optimized version in return statement below it!
$DX = [
'one_digit' => "required|digit",
'user_password' => "required|password|between:5,10",
'user_password_confirm' => "required|password_confirm:user_password",
];
return array(
'<CONFIG>' =>
array(
'passwords_to_match' =>
array(
'user_password' => 'user_password_confirm',
),
),
'one_digit' =>
array(
'<RULES>' =>
array(
'required' =>
array(
'value' => NULL,
'err_msg' => NULL,
),
'digit' =>
array(
'value' => NULL,
'err_msg' => NULL,
),
),
),
'user_password' =>
array(
'<RULES>' =>
array(
'required' =>
array(
'value' => NULL,
'err_msg' => NULL,
),
'password' =>
array(
'value' => NULL,
'err_msg' => NULL,
),
'between' =>
array(
'value' =>
array(
0 => 5,
1 => 10,
),
'err_msg' => NULL,
),
),
),
'user_password_confirm' =>
array(
'<RULES>' =>
array(
'required' =>
array(
'value' => NULL,
'err_msg' => NULL,
),
'password_confirm' =>
array(
'value' => 'user_password',
'err_msg' => NULL,
),
),
),
);
};
return function (&$c, $handler = "v_test3") {
$base = is_string($handler) ? $handler : "";
$full = __NAMESPACE__ . '\\' . $base;
if (function_exists($full)) {
return $full($c);
} else {
$c['err']['FAILED_TO_RUN_VALIDATION_FUNCTION-' . 'v_test'] = 'Validation function `' . $full . '` not found in namespace `' . __NAMESPACE__ . '`!';
return null;
}
};
Nu framgår det först en så kallad namespace vilket fyller två syften i detta sammanhang:
Kapsla in funktionerna på filnivå så nu går det att ha en fil som innehåller samma Valideringsfunktion "v_test2" utan att den skulle krocka med en annan Valideringshanteringsfil som kanske laddas in vid en och samma HTTPS-förfrågan som behandlas och besvaras.
Kalla mig "bakom kodflötet" men jag har tidigare inte förstått existensen med så kallade namespaces inom programmering. I PHP så verkar den fylla det extra syftet att kunna förenkla användningen av funktioner. Exempelvis med hjälp av "use"-nyckelordet kan du då syfta på en funktion med särskilt namn från en särskild fil utan att behöva skriva in hela filnamnet och dess funktion. Det andra syftet jag tänker för det är att tydligare visa vad för slags fil du är inuti även om "r|d|v|s_"-prefixet hjälper till. "s_"-prefixet står för SQL-hanteringsfiler med SQL-hanteringsfunktioner så att säga!
Detta ställer den viktiga frågan: "Bör prefixen_ vara kvar eller ej i respektive filer?" eller kommer de att upplevas som överflödiga? 🤔Lägg även märke till denna lilla men signifikanta förändring:
function v_test2(&$c) // <>
Vad är grejen med "<>"?
Anmärk "<>"-tecknen vilket nu är tomma och annars innehåller HTTPS-metoden/exakt_rutt_med_valfria_parametrar. I sammanhanget för Validerings- och SQL-hanteringsfiler inklusive deras -hanteringsfunktioner så kommer de fylla syftet att valfritt på eget måfå av Utvecklaren innehålla vilka tabeller de syftar på.
Exempelvis:
function v_test2(&$c) // <authors,books*2>
Betyder att du har Validering inuti v_test2-hanteringsfunktionen för de inlagda tabellerna authors och books varav sistnämnda innebär en numrerad lista med två element för den tabellen i och med "*2"-syntaxen.
När denna Valideringshanteringsfunktion då skapas med dessa valda tabeller så kontrolleras de så klart först att de finns inuti tabellarrayen, annars nekas Utvecklaren och ombes att antingen ange rätt tabeller eller inte ange några tabeller vid skapandet av Valideringshanteringsfunktionen.
Varför det blev så här var för att jag insåg att det hela med Validering såväl som SQL-frågor bör ej endast vara förknippade med bara en enstaka HTTPS-metod och dess exakta rutt med möjlig dynamisk parameter utan bör kunna återanvändas!
Och så fick jag en "uppenbarelse" om vad <> då kunde användas till inuti de två annorlunda hanteringsfilerna om de inte direkt kunde kopplas till särskilda HTTPS-metoder med någon rutt: vilka tabeller de syftar eftersom filerna handlar om just databastabellerna inlagda via SQL-filer från den nyligen tillagda schemas-mappen.
ScaffoldingPHP eller vadå?😂
En sak jag börjat inse med CLI-filosofin i detta hobbyprojekt är att vad du i princip gör är att "scaffolda" fram saker såsom HTTPS-specifika rutter, förknippade hanteringsfiler och hanteringsfunktioner för dessa.
Samtidigt har du de mindre icke-specifika hanteringsfilerna och hanteringsfunktionerna gällande Validering och SQL-frågor där det snarare är valfritt huruvida du vill syfta på en eller flera särskilda databastabeller eller inte.
Så kallad "flexibel valfrihet" är mycket viktigt anser jag för Utvecklare så att de kan välja själva hur de vill använda någonting. Nackdelen med för mycket valfrihet samtidigt är att du kanske blir "paralyserande av allt analyserande" likt när du har för mycket att välja bland hos streamingtjänsterna.
En annan sak är något jag kallar för "Super Mario Maker 2/SMB2-metaforen". Föreställ att du skapar en bana i SMB2 vilket tagit dig tiotals timmar där du då inte bara har tänkt igenom hur den är tänkt att spelas utan också fört egna resonemang bakom dessa.
Men det är också grejen: Du har fört egna resonemang om hur din bana är tänkt att spelas utan att kanske möjligtvis kommunicerat det på ett lika uppenbart sätt medan allt är så uppenbart för dig för du har ju varit med från början med dina egna resonemang vid framtagningen såväl som tiden du har lagt på det hela.
Tänk dig sedan att du kastar in en spelare som har 0 sekunders erfarenhet av din bana. Kommer de att förstå varför den ser ut som den gör? Varför den tycks vara designad som den är? och så vidare. Liknande går att säga om alla dessa så kallade "utvecklingsramverk" - med en signifikant skillnad dock jämfört med metaforen ovan.
Till skillnad från SMB2-byggskapandet så kan du tillhandahålla "spelmanualer" eller "dokumentation" som det så fint heter inom utvecklingsvärlden. På så vis kan du vägleda nya spelare (eller Utvecklare i detta fall) att ta sig an din bana/ditt ramverk.
Men! Likt "spelmanualer" så kan det hela kännas dålig design från grunden om du ska behöva läsa en slags "manual" för att kunna spela ett spel eller kunna använda ett ramverk genom att först behöva läsa dess "dokumentation"?🤔
Tänk om banan i sig eller ramverket i sig kunde kommunicera vad du ska göra beroende på var du är någonstans? Exempelvis i bra spel så kan du visas i en säker miljö vad du förväntas göra och inte göra. En annan form av "dokumentation" är således kommentarer inuti de scaffoldade filerna inklusive exempelanvändning som följd av scaffoldandet[/i].
Genom finurlig placering och design av dem så går de även lättsamt att bara sudda ut eller ignorera och samtidigt få en slags förståelse för hur respektive fil är tänkt att användas genom att de alltid fylls med pseudodata.
En halvträff🎯 mot "ORM-konkurrenterna"?
Den skämtsamma frågan är så klart hur jag med detta hobbyprojekt ska besegra "ORM-jättarna" inom SQL-frågeskapandet. Det är ju så oerhört angeläget att bara skriva sitt så kallade "schema" och "relationerna" mellan dem och sedan bara använda alla de inbyggda om än nästintill "magiska" metoderna för att plocka ut data.
Och ja, du slipper ju även bry dig om hur denna data ska "paketeras" (eng. "data hydration") utan du anger bara vilka tabeller som ska vara "barn" (eng. "with") åt andra och så är det färdigt. All data hämtas och paketeras enligt dina önskemål - med bara ett antal kedjemetodanrop till ditt instansierade ORM-objekt!🤯
Om vi tittar tillbaka på Valideringsfunktionen fast vi byter ut den mot en så kallad SQL-frågefunktion så kan vi se ledtråden inuti "<>"-tecknen:
function s_test(&$c) // <authors,books>
I kodexemplet ovan så ser vi tabellerna authors och books vilket kan kontrolleras då om de först och främst existerar, och sedan om de har någon relation (sammankopplade primär- & sekundärnycklar) och därmed om de kan "hydreras" på något vis.
I och med det så kan de viktiga nyckelorden för en klassisk SQL-SELECT-fråga såsom FROM, (INNER/OUTER/LEFT/RIGHT) JOIN, ON WHERE, GROUP BY, HAVING, ORDER BY ASC/DESC, och avslutningsvis LIMIT OFFSET COUNT hanteras korrekt genom att det är känt vilka tabeller som kan slås samman beroende på val av JOIN så klart och det är inte krav på att nyttja ON utan bara om det önskas - återigen: valfrihet!
Gällande valfriheten igen så är infrastrukturen att förutom att du måste ange dina rutter för dina olika valda HTTPS-metoder och varje sådan måste ha minst en tilldelad hanteringsfunktion som finns inuti någon vald hanteringsfil så är det sedan helt och hållet upp till Utvecklaren hur de vill ta sig an det hela därpå.
Hypotetiskt talat så kan du i princip sköta allt inuti en och samma hanteringsfunktion inuti en given hanteringsfil:
Validera eventuella "ruttparametrar".
Validera eventuella erhållna data från användaren.
Eventuellt hämta data baserat på det från vald databas.
Antingen "hydrera" erhållen databasdata eller inte göra det.
Sätta meta-data för den sida du vill returnera.
Sätta layoutfiler den sida du vill returnera ska använda sig av.
Sätta diverse HTTPS-huvuden i samband med den returnerade sida du vill skicka tillbaka.
Skicka tillbaka antingen JSON och/eller din renderade "mallmotoriserade" sida.
Gör något ytterligare efter du skickat tillbaka svar: logga, cacha, och/eller annat!
Men vill du dela upp det mer så kan du skapa en Datahanteringsfil med en Datahanteringsfunktion vilket du endast använder för att separera på vad som är kopplat till eventuell data (för du kanske bara returnerar en icke-dynamisk sida utan något innehåll som först måste hämtas från en databas).
💩"I slutändan är det ingenting mer än en enda stor fet "kodbajskorv!"💩
Nu har jag frossat alldeles för mycket på mina egna "paradpruppar" så är det dags att komma ned på Jorden igen!😂
Visst är det lite smågulligt att "Lilla Junioren" har tagit fram ett så kallat "relativt valfritt ramverk" i det så kallade "låstasspråket PHP" vilket du använder när du inte vill kunna använda asynkron kodkörning på internationell invå med lastbalansering, delade data (eng. "sharding").
Jag pratar ned så där - nästan i en slags "reflexmässig Jantelagsanda" - för att visa att inte ännu har jag blivit så där "kodmässigt fåfäng" och tro att jag är någon slags "Thought Leader On Web Development" bara för att jag snickrat ihop något smått komplicerat "K1dd13 Zcr1pT" på lite drygt två månader snart.
Hobbyprojektet är både delvis imponerande och samtidigt inte eftersom jag inte har någon annan Slutanvändare än jag själv. Lite åt det hållet var en välmenande konstruktiv kommentar jag fick av någon på Discord angående det hela.
Dessutom återupprepar jag det nästan alla Utvecklare egentligen stör sig på: "Alla vill skapa ett ramverk för att lösa något andra ramverk inte hade löst men istället skapar de bara nya problem som då andra vill skapa ramverk för att lösa och så fortsätter det hela!".
Jag ska erkänna: Visst har jag till och från haft dessa klassiska irrationella dagdrömmar om att: "Tänk om folk på reddit börjar använda detta och massa populära CodeTubers börjar prata om det för att det är så sinnessjukt - funktionsbaserat ramverk i PHP - WTF, liksom?!" Även jag har bara en lättpåverkad aphjärna jag med!
Hursomhelst så har jag lärt mig en del om diverse datastrukturer och deras algoritmiska implementationer, viss infrastrukturdesigntänk inom PHP och en del strängmanipulering, eller ja, en hel del strängmanipulering!😅
(Jag misstänker samtidigt att potentiella Arbets- och/eller Uppdragsgivare skulle resonera något i stil med om de fått reda på detta slags hobbyprojekt: "Så personen har ägnat flera månader på ett eget ramverk - som i princip ingen kommer att få reda på ännu, mindre vilja prova - istället för att bemästra nuvarande vedertagna teknikstackar? Saknar personen känsla för prioriteringar här i arbetslivet?")
TLDR; Jag pratar både gott och skit om det så kallade hobbyprojektet för att visa att jag inte blivit fåfäng samtidigt jag är stolt över det lilla jag åstadkommit på den tid som gått!🫡
Vidareutbildning
Inget nytt att rapportera förutom att jag såg detta mycket intressanta simpla YT-klipp om hur "Tre Olika Sätt JS-Ramverk Renderar DOM i Webbläsarna":
Om jag förstått rätt och det stämmer så är snubben skaparen bakom SolidJS så inte helt bakom kodflötet bör personen då vara.
Personligen tycker jag det är lite lustigt att Virtual DOM med differentiationsanalys var det första stora heta innan det blev insikten att: "Ja just det, detta innebär ju att det växer proportionellt med antalet nästlade DOM-element. Vi kanske skulle bara 'lyssna' på vilka elements vars innehåll faktiskt ändras?"
Lätt att vara efterklok så klart. Om jag förstått det hela korrekt så kom ReactJS till främst för att lösa problem internt hos Facebook innan det sedan blev "FOSS"(?) och resten är historia?🤔
När det gäller Signal-paradigmet så undrar jag samtidigt hur det hela skalar om varje sak som ska få en "signal" blir då ett element att hålla reda på och så ska det kanske sammankopplas med andra vilket då betyder att de måste vara varandras så kallade "observatörer"?🙃
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Webbutvecklingsdagbok - efter studierna
En möjligtvis "liten" kortare uppdatering denna gång från gårdagen.
Jobb & Uppdrag
Igår runt ~ kl. 21:30-tiden blev jag uppringd av personen där jag via en gemensam Facebook-grupp först hade kontaktat personen. Personen berättade att denne egentligen inte ville säga så mycket om projektet men att personen tidigare hade anlitat billig distanspersonal från bland annat Indien och/eller Pakistan och då blev personen möjligtvis lite paff när jag sa att jag tar 500 SEK i arvode exklusive moms.
Personen erbjöd mig då 50 % av bolaget alternativt att jag bara skulle ha rollen som kodgranskare av den annars billigare distanspersonalen som då skulle ha till uppgift att skriva koden. Här såg jag då potentiell situation där distanspersonalen bara skulle sticka om de fått för mycket kritik av mig som kodgranskare och så skulle jag då stå där själv med denna potentiella Uppdragsgivare.
En annan sak personen erbjöd mig var att det skulle ju se rätt så fint ut i min Juniora Portfölj men då sa jag att även om jag håller med så kan jag dessvärre inte gå utan ersättning en längre tid plus att jag då blir upplåst i något under en möjligtvis längre tid utan någon uppfattning om hur lång tid det skulle kunna ta innan något blivit gjort på riktigt med god referens.
Det var som så att personen upplevde det lite jobbigt att behöva visa upp vad personen (personen berättade något i stil med att denne var orolig över att om för många Utvecklare skulle få se kodbasen så kanske idén skulle snos) redan hade gjort mer än att personen påstod att det var/är något som inte finns i Sverige och på något vis var likt LinkedIn och hade något med tjänster att göra med.
Jag har inte fått se något så jag kan bara gissa att det tycks vara en slags marknadsportal där tjänster säljes emellan med och/eller utan mellanhand (någonstans måste ju pengarna in)? Har ingen aning för övrigt. Möjligen blir det så att jag aldrig heller kommer att få se kodbasen för...
...redan där med oroligheterna om att idén kanske skulle snos så blev det dålig magkänsla för min del. Med känslan av opålitlighet och därmed vissa taggar redan utåt så skulle jag inte se det som ett positivt samarbete att ingå i. Det finns samtidigt en sak i det stora hela i allt detta som är motsägelsefullt just nu: Jag har ju tidigare sagt att jag gärna hoppar in 1-2 veckor för att visa vad jag går förr - jovars, men åt de bolag/företag jag känner bra magkänsla inför!😉
Det är som många gånger tidigare sagt tråkigt att det verkar hellre vara kanske smått oseriösa (utifrån ett ekonomiskt perspektiv - alla vill tjäna pengar men ingen vill egentligen betala för det så vem ska då betala vem i slutändan?🤪) som vill ta vara på ens mycket ambitiösa potential medan riktigt seriösa är/verkar för ekonomiskt rädda eller vad det nu kan vara?🤔😥
(En överdriven liten detalj kanske; men faktumet att personen ringde relativt sent kanske också indikerar på lite hur personen tycks se på arbete kontra fritid? Varierar säkerligen från person till person såklart!)
Förhoppningsvis hittar personen någon annan mer välvillig och jag önskar personen all möjlig framgång här i livet!
Hobbyprojekt
Det går nu att valfritt(!) lägga till vilka tabeller som ska få standardutförande i den förenklade valideringsregelsyntaxen när en ny valideringsfil och/eller valideringsfunktion skapas:
// Skapa Valideringsfunktionen `by_id` inuti Valideringsfilen `v_users`
// (skapa om den ej finns) med tabellen `users` som standardutförande
php funkcli create v users=>by_id users
I exemplet ovan används tabellen users för att skapa den grundläggande förenklade valideringsregelsyntaxen vilket sedan kan kompileras till den arrayoptimerade valideringssyntaxen. Flera kontroller görs som att tabellen måste finnas i config/tables.php, har valida kolumnnycklar och värden, och så vidare.
Den gör också några trevliga QoL-saker såsom att om ett kolumnnamn innehåller ordet "password" men inte "confirm" så antar den att det är ett lösenord och välja datatypen "password". Och om den sedan hittar "password"- och "confirm"-orden inuti en och samma annan tabellkolumn så kommer den att skapa datatypen "password_confirm:andra_password_kolumnen" som datatyp.
En annan trevlig sak är följande förenkling:
$v_test = funk_use_validation_on_single_validation_file_and_function($c, "v_test", "v_test2", "GET");
Tidigare behövde du istället skriva:
$v_file = funk_load_validation_file($c, "v_test");
$v_test = funk_use_validation($c, $v_file($c, "v_test2"), "GET");
En både visuell och designmässigt klumpigt utförande med alla funktioner och nyckelordet use måste vara högst uppe inuti filerna så det går ej att försöka återanvända flera olika Valideringsfunktioner inuti en och samma Valideringsfil på något snyggt vis än det exakt ovanstående.
Här har vi en tydlig infrastrukturmässig svaghet i användningen av samlade funktioner inuti en och samma filer. Eller ja, möjligen skulle vi kunna göra så att funk_use_validation returnerar en så kallad "dispatch handler" vilket alltid returnerar en array med först valideringsresultatet och sedan samma "dispatch handler" igen - lite likt hur du kan återanvända samma metoder i en och samma instansierade klass.
Frågan är om det i sig skulle börja orsaka frågetecken eller missnöje? (utöver redan missnöjet att här är ett nytt ramverk framför dina ögon och/eller skärmläsare!😂) Jag tänker hela tiden att: "De kommer alltid att ha lägre tålamod än mig gällande mitt eget ramverk. Så fort jag börjar fundera eller störa mig på något så är det nästan garanterat att även andra kommer att göra det också!"
(Jag tycks "smiskas" gång på gång av de fördelaktiga saker Objektorienterade programmeringslösningar kan bidra med kontra den funktionsbaserade programmeringslösningen jag valt att gå - allt till trots!😅)
Hursomhelst så saknas två regler som även behöver kontakt med databasobjektet vilket är `unique:tabellnamn,tabellkolumnnamn` och `exists:tabellnamn,tabellkolumnnamn` och dessa går även hand i hand med hur jag ska klura ut hela databasanslutningsobjektbiten från start till slut. Nästa steg är ju skapandet av högoptimerade SQL-frågor med valfri "datahydrering" om det är just en "SELECT"-fråga.
Här landar jag nästan i att returnera en array i stil med:
// Syntax:
return ["Kompilerad SQL-fråga",
"Datahydreringssats eller null",
"bundna parametrar om ?-placeholders använts eller null"
];
// Exempel:
return [
"SELECT users.id AS users_id,
users.name AS users_name FROM users WHERE users.id = ?;",
["users" => ["id","name"], "with" => null],
"i"
];
Lägg märke till användningen av nyckelordet "AS" överallt. Den kommer att vara räddaren i nöden när flera tabeller ska slå samman med kanske samma tabellkolumnnamn!🫡
Observera att du bara får tillbaka SQL-frågan här och du väljer själv om du vill skicka vidare SQL-frågan och därmed dess nödvändiga bundna parameter så att det är rätt datatyp som ska bindas efter den förberedda SQL-satsen.
Själva grejen med "datahydreringssatsen" är att den skickar du med om du vill få din data "paketerad" direkt efter slutförd och lyckad SQL-förfrågan eller om du vill göra något helt annat med det (först). Allt handlar som sagt igen om valfrihet!!!
Som standard då när en SQL-Hanteringsfil och dess SQL-Hanteringsfunktion skapas så är tanken att du då får alla inlagda tabeller från "config/tables.php" och därmed vilka möjliga JOINs ON som går mellan dessa tabeller så för dig blir det mer bara att sudda ut det du ej vill ha och möjligen korrigera små saker och/eller lägga till diverse så kallade underfrågor (eng. "subqueries").
Vidareutbildning
Inget nytt att rapportera mer än denna intressanta YT-video jag såg igår:
Den handlar om en numera fixad kodmiss i Chromium-källkoden när Utvecklingsverktygen körs där du kan få debuggingbaserad JS-kod att köras externt utan någon bekräftelse först (jämförelsevis i FireFox vilket så klart inte är Chromium-motorn dock).
Det hela möjliggjordes av bland annat tre samverkande saker:
1. Otillräcklig hantering av när och inte när JavaScript får köras inline: <a href="javascript:alert(1)">UPDATE CHROME PLZ!</a> // UPDATE CHROME NOW!!!.
2. Kommenterad CSP-tagg av någon helt outgrundlig anledning i en HTML-fil så det gick att köra JS-kod externt istället för endast från egen dator?!😵💫
3. Kommentaren i slutet i exemplet ovan blev det som gick att se som en "klickbar länk" för de icke-IT-insatta!
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
En möjligtvis "liten" kortare uppdatering denna gång från gårdagen.
Jobb & Uppdrag
Igår runt ~ kl. 21:30-tiden blev jag uppringd av personen där jag via en gemensam Facebook-grupp först hade kontaktat personen. Personen berättade att denne egentligen inte ville säga så mycket om projektet men att personen tidigare hade anlitat billig distanspersonal från bland annat Indien och/eller Pakistan och då blev personen möjligtvis lite paff när jag sa att jag tar 500 SEK i arvode exklusive moms.
Personen erbjöd mig då 50 % av bolaget alternativt att jag bara skulle ha rollen som kodgranskare av den annars billigare distanspersonalen som då skulle ha till uppgift att skriva koden. Här såg jag då potentiell situation där distanspersonalen bara skulle sticka om de fått för mycket kritik av mig som kodgranskare och så skulle jag då stå där själv med denna potentiella Uppdragsgivare.
En annan sak personen erbjöd mig var att det skulle ju se rätt så fint ut i min Juniora Portfölj men då sa jag att även om jag håller med så kan jag dessvärre inte gå utan ersättning en längre tid plus att jag då blir upplåst i något under en möjligtvis längre tid utan någon uppfattning om hur lång tid det skulle kunna ta innan något blivit gjort på riktigt med god referens.
Det var som så att personen upplevde det lite jobbigt att behöva visa upp vad personen (personen berättade något i stil med att denne var orolig över att om för många Utvecklare skulle få se kodbasen så kanske idén skulle snos) redan hade gjort mer än att personen påstod att det var/är något som inte finns i Sverige och på något vis var likt LinkedIn och hade något med tjänster att göra med.
Jag har inte fått se något så jag kan bara gissa att det tycks vara en slags marknadsportal där tjänster säljes emellan med och/eller utan mellanhand (någonstans måste ju pengarna in)? Har ingen aning för övrigt. Möjligen blir det så att jag aldrig heller kommer att få se kodbasen för...
...redan där med oroligheterna om att idén kanske skulle snos så blev det dålig magkänsla för min del. Med känslan av opålitlighet och därmed vissa taggar redan utåt så skulle jag inte se det som ett positivt samarbete att ingå i. Det finns samtidigt en sak i det stora hela i allt detta som är motsägelsefullt just nu: Jag har ju tidigare sagt att jag gärna hoppar in 1-2 veckor för att visa vad jag går förr - jovars, men åt de bolag/företag jag känner bra magkänsla inför!😉
Det är som många gånger tidigare sagt tråkigt att det verkar hellre vara kanske smått oseriösa (utifrån ett ekonomiskt perspektiv - alla vill tjäna pengar men ingen vill egentligen betala för det så vem ska då betala vem i slutändan?🤪) som vill ta vara på ens mycket ambitiösa potential medan riktigt seriösa är/verkar för ekonomiskt rädda eller vad det nu kan vara?🤔😥
(En överdriven liten detalj kanske; men faktumet att personen ringde relativt sent kanske också indikerar på lite hur personen tycks se på arbete kontra fritid? Varierar säkerligen från person till person såklart!)
Förhoppningsvis hittar personen någon annan mer välvillig och jag önskar personen all möjlig framgång här i livet!
Hobbyprojekt
Det går nu att valfritt(!) lägga till vilka tabeller som ska få standardutförande i den förenklade valideringsregelsyntaxen när en ny valideringsfil och/eller valideringsfunktion skapas:
// Skapa Valideringsfunktionen `by_id` inuti Valideringsfilen `v_users`
// (skapa om den ej finns) med tabellen `users` som standardutförande
php funkcli create v users=>by_id users
I exemplet ovan används tabellen users för att skapa den grundläggande förenklade valideringsregelsyntaxen vilket sedan kan kompileras till den arrayoptimerade valideringssyntaxen. Flera kontroller görs som att tabellen måste finnas i config/tables.php, har valida kolumnnycklar och värden, och så vidare.
Den gör också några trevliga QoL-saker såsom att om ett kolumnnamn innehåller ordet "password" men inte "confirm" så antar den att det är ett lösenord och välja datatypen "password". Och om den sedan hittar "password"- och "confirm"-orden inuti en och samma annan tabellkolumn så kommer den att skapa datatypen "password_confirm:andra_password_kolumnen" som datatyp.
En annan trevlig sak är följande förenkling:
$v_test = funk_use_validation_on_single_validation_file_and_function($c, "v_test", "v_test2", "GET");
Tidigare behövde du istället skriva:
$v_file = funk_load_validation_file($c, "v_test");
$v_test = funk_use_validation($c, $v_file($c, "v_test2"), "GET");
En både visuell och designmässigt klumpigt utförande med alla funktioner och nyckelordet use måste vara högst uppe inuti filerna så det går ej att försöka återanvända flera olika Valideringsfunktioner inuti en och samma Valideringsfil på något snyggt vis än det exakt ovanstående.
Här har vi en tydlig infrastrukturmässig svaghet i användningen av samlade funktioner inuti en och samma filer. Eller ja, möjligen skulle vi kunna göra så att funk_use_validation returnerar en så kallad "dispatch handler" vilket alltid returnerar en array med först valideringsresultatet och sedan samma "dispatch handler" igen - lite likt hur du kan återanvända samma metoder i en och samma instansierade klass.
Frågan är om det i sig skulle börja orsaka frågetecken eller missnöje? (utöver redan missnöjet att här är ett nytt ramverk framför dina ögon och/eller skärmläsare!😂) Jag tänker hela tiden att: "De kommer alltid att ha lägre tålamod än mig gällande mitt eget ramverk. Så fort jag börjar fundera eller störa mig på något så är det nästan garanterat att även andra kommer att göra det också!"
(Jag tycks "smiskas" gång på gång av de fördelaktiga saker Objektorienterade programmeringslösningar kan bidra med kontra den funktionsbaserade programmeringslösningen jag valt att gå - allt till trots!😅)
Hursomhelst så saknas två regler som även behöver kontakt med databasobjektet vilket är `unique:tabellnamn,tabellkolumnnamn` och `exists:tabellnamn,tabellkolumnnamn` och dessa går även hand i hand med hur jag ska klura ut hela databasanslutningsobjektbiten från start till slut. Nästa steg är ju skapandet av högoptimerade SQL-frågor med valfri "datahydrering" om det är just en "SELECT"-fråga.
Här landar jag nästan i att returnera en array i stil med:
// Syntax:
return ["Kompilerad SQL-fråga",
"Datahydreringssats eller null",
"bundna parametrar om ?-placeholders använts eller null"
];
// Exempel:
return [
"SELECT users.id AS users_id,
users.name AS users_name FROM users WHERE users.id = ?;",
["users" => ["id","name"], "with" => null],
"i"
];
Lägg märke till användningen av nyckelordet "AS" överallt. Den kommer att vara räddaren i nöden när flera tabeller ska slå samman med kanske samma tabellkolumnnamn!🫡
Observera att du bara får tillbaka SQL-frågan här och du väljer själv om du vill skicka vidare SQL-frågan och därmed dess nödvändiga bundna parameter så att det är rätt datatyp som ska bindas efter den förberedda SQL-satsen.
Själva grejen med "datahydreringssatsen" är att den skickar du med om du vill få din data "paketerad" direkt efter slutförd och lyckad SQL-förfrågan eller om du vill göra något helt annat med det (först). Allt handlar som sagt igen om valfrihet!!!
Som standard då när en SQL-Hanteringsfil och dess SQL-Hanteringsfunktion skapas så är tanken att du då får alla inlagda tabeller från "config/tables.php" och därmed vilka möjliga JOINs ON som går mellan dessa tabeller så för dig blir det mer bara att sudda ut det du ej vill ha och möjligen korrigera små saker och/eller lägga till diverse så kallade underfrågor (eng. "subqueries").
Vidareutbildning
Inget nytt att rapportera mer än denna intressanta YT-video jag såg igår:
https://www.youtube.com/watch?v=RLyhPGsEMz4
Den handlar om en numera fixad kodmiss i Chromium-källkoden när Utvecklingsverktygen körs där du kan få debuggingbaserad JS-kod att köras externt utan någon bekräftelse först (jämförelsevis i FireFox vilket så klart inte är Chromium-motorn dock).
Det hela möjliggjordes av bland annat tre samverkande saker:
1. Otillräcklig hantering av när och inte när JavaScript får köras inline: <a href="javascript:alert(1)">UPDATE CHROME PLZ!</a> // UPDATE CHROME NOW!!!.
2. Kommenterad CSP-tagg av någon helt outgrundlig anledning i en HTML-fil så det gick att köra JS-kod externt istället för endast från egen dator?!😵💫
3. Kommentaren i slutet i exemplet ovan blev det som gick att se som en "klickbar länk" för de icke-IT-insatta!
På återseende!
Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)
Ett av mina första it-jobb var för en sån nervös idé-tjuveri-konspiratör. Det är helt enkelt omöjligt i längden att jobba för en sån
Dels för att man inte långsiktigt kan planera sitt arbete för nya små förändringar ska alltid in över tid som sabbar mycket av det man byggt, men också rent emotionellt att man aldrig kommer bli litad på av ägaren
Och helt ärligt, han har ju redan delat mycket av idéerna med helt okända konsulter i Asien, varför skulle det vara säkrare än en junior konsult i Sverige?
Jag skulle springa därifrån, i alla fall om inte personen ändrar sig snart
En möjligtvis "liten" kortare uppdatering denna gång från gårdagen.
Jobb & Uppdrag
Igår runt ~ kl. 21:30-tiden blev jag uppringd av personen där jag via en gemensam Facebook-grupp först hade kontaktat personen. Personen berättade att denne egentligen inte ville säga så mycket om projektet men att personen tidigare hade anlitat billig distanspersonal från bland annat Indien och/eller Pakistan och då blev personen möjligtvis lite paff när jag sa att jag tar 500 SEK i arvode exklusive moms.
Personen erbjöd mig då 50 % av bolaget alternativt att jag bara skulle ha rollen som kodgranskare av den annars billigare distanspersonalen som då skulle ha till uppgift att skriva koden. Här såg jag då potentiell situation där distanspersonalen bara skulle sticka om de fått för mycket kritik av mig som kodgranskare och så skulle jag då stå där själv med denna potentiella Uppdragsgivare.
En annan sak personen erbjöd mig var att det skulle ju se rätt så fint ut i min Juniora Portfölj men då sa jag att även om jag håller med så kan jag dessvärre inte gå utan ersättning en längre tid plus att jag då blir upplåst i något under en möjligtvis längre tid utan någon uppfattning om hur lång tid det skulle kunna ta innan något blivit gjort på riktigt med god referens.
Det var som så att personen upplevde det lite jobbigt att behöva visa upp vad personen (personen berättade något i stil med att denne var orolig över att om för många Utvecklare skulle få se kodbasen så kanske idén skulle snos) redan hade gjort mer än att personen påstod att det var/är något som inte finns i Sverige och på något vis var likt LinkedIn och hade något med tjänster att göra med.
Jag har inte fått se något så jag kan bara gissa att det tycks vara en slags marknadsportal där tjänster säljes emellan med och/eller utan mellanhand (någonstans måste ju pengarna in)? Har ingen aning för övrigt. Möjligen blir det så att jag aldrig heller kommer att få se kodbasen för...
...redan där med oroligheterna om att idén kanske skulle snos så blev det dålig magkänsla för min del. Med känslan av opålitlighet och därmed vissa taggar redan utåt så skulle jag inte se det som ett positivt samarbete att ingå i. Det finns samtidigt en sak i det stora hela i allt detta som är motsägelsefullt just nu: Jag har ju tidigare sagt att jag gärna hoppar in 1-2 veckor för att visa vad jag går förr - jovars, men åt de bolag/företag jag känner bra magkänsla inför!😉
Det är som många gånger tidigare sagt tråkigt att det verkar hellre vara kanske smått oseriösa (utifrån ett ekonomiskt perspektiv - alla vill tjäna pengar men ingen vill egentligen betala för det så vem ska då betala vem i slutändan?🤪) som vill ta vara på ens mycket ambitiösa potential medan riktigt seriösa är/verkar för ekonomiskt rädda eller vad det nu kan vara?🤔😥
(En överdriven liten detalj kanske; men faktumet att personen ringde relativt sent kanske också indikerar på lite hur personen tycks se på arbete kontra fritid? Varierar säkerligen från person till person såklart!)
Förhoppningsvis hittar personen någon annan mer välvillig och jag önskar personen all möjlig framgång här i livet!
Generöst att erbjuda 50% av ingenting
Det här låter väldigt likt en person och ett projekt som jag pratade med för ett par år sedan. Han hade anlitat indier/pakistanier för att bygga en tjänst i WordPress och han erbjöd 50% av bolaget mot att jag skrev om skiten från scratch.
Vid tillfället fanns det 0 (noll) betalande kunder, och det finns flera likadana tjänster på marknaden, så det är inget unikt alls.
Jag undrade lite försynt varför jag skulle vilja skriva ett liknande system helt själv, och dela eventuella framtida intäkter med, exempelvis honom? Jag menar; jag skulle ju skriva systemet. Vad skulle han själv bidra med?
Själv skulle han fortsätta jobba heltid på sin nuvarande arbetsplats, men han skulle kunna hjälpa till att testa systemet på kvällar och helger. Men kanske inte varje kväll och helg...
I början av min karriär, för 30 år sedan, hoppade jag på diverse projekt (antingen helt gratis eller väldigt dåligt betalda) i hopp om att det skulle leda till något, vilket det förstås inte gjorde. Sådana misstag gör jag inte om
Med din kompetens och drivkraft, som är betydligt högre än den jag hade för 30 år sedan, så ska du inte skriva en enda rad kod gratis. Och det ska ingen annan heller, oavsett kompetens.
MSI PRO Z790-P WIFI | Intel i9 13900K | 128 GB DDR5
GTX 4070 12 GB
Samsung 990 Pro 4 TB | WD Black SN850X 2 TB Gen 4 | 2 x 1 TB Samsung 970 EVO Plus
3 x ASUS 27" | 1 x Philips 49"
Generöst att erbjuda 50% av ingenting
Det kan vara så, du har rätt i 90% av fallen, men du har fel i 10% av fallen, och en del av dom fałlen heter Canva, Mozilla, Facebook osv.
Man ska vara införstådd med vad man inte vet, och har man själv inga idéer så ska man kanske inte p*ssa på andras.
Och jobbar man som utvecklare så är sannolikheten att man jobbar på en tjänst eller produkt där någon annan har jobbat "gratis" nära nog 100%
I början av min karriär, för 30 år sedan, hoppade jag på diverse projekt (antingen helt gratis eller väldigt dåligt betalda) i hopp om att det skulle leda till något, vilket det förstås inte gjorde.
Så du hade inga egna idéer, och du kunde inte göra ett selektivt framgångsrikt urval på dom som presenterades för dig?
Med din kompetens och drivkraft, som är betydligt högre än den jag hade för 30 år sedan, så ska du inte skriva en enda rad kod gratis.
Nu gör ju trådskaparen ganska omfattande och långa inlägg här, så det där vet vi ju redan att det inte är "sant".
Och vad jag förstår så är trådskaparen dessutom utan uppdrag, så "gratis" (0kr i inkomst) är även där ett faktum.
Det är ju inte heller så att kod säljer sig själv.
(och för att förtydliga, jag skulle kunna ta precis motsatt position i den här diskussionen också, det finns 13 på dussinet av människor med idéer, och efter 15 år av 0% ränta och gratis pengar så har nästan alla 0% verklighetsförankring, och på sin höjd 5-10% har koll på vem den betalande kunden faktiskt är)
- Asus lanserar Noctua-modell av RTX 50804
- Bilder på ditt senaste inköp (2025) [inga produktbilder]1,1k
- Battlefield 6620
- En teknikresa till Taiwan - skulle du haka på?9
- Intels vd anser inte att Intels vd bör avgå15
- Digital Foundry blir fristående34
- Uppgradering + ny dator, hur väljer man rätt CPU/GPU?4
- Operativsystem för svensson114
- Hur tech-detoxar du?2
- Upgradera dator. Budget 7-8k3
- Säljes Razer Deathadder V3 Pro Vit | Oöppnad
- Köpes Amd am4 X3D köpes
- Säljes Doom: The Dark Ages
- Säljes Gainward GeForce RTX 4090 Phantom
- Köpes Enklare dator till Roblox
- Säljes Acer 27" Predator XB273UNV QHD IPS 170 Hz RGB
- Bytes Byte 3080
- Köpes MoBo, CPU, RAM till äldre server
- Bytes 27" mot 24"
- Säljes Prolle/Moderkort
- Asus lanserar Noctua-modell av RTX 50802
- Intels vd anser inte att Intels vd bör avgå15
- Heretic + Hexen överraskar med djävulskt kärleksfullt paket15
- USA:s president kräver att Intels vd avgår31
- Digital Foundry blir fristående34
- Filmquiz: Kända datorscener112
- ChatGPT-5 avtäcks ikväll - följ OpenAI:s livesändning52
- Sonos-högtalare fattade eld19
- Ingen plats för mus och tangentbord i Microsofts framtidsvision91
- Nu öppnar Battlefield 6-betan – värm upp inför Slaget11
Externa nyheter
Spelnyheter från FZ
- Avalanche pausar Contraband – följd av Xbox-neddragningarna? idag
- Kl. 18 – Mafia: The Old Country på FZ Play! idag
- Heretic och Hexen nyutgivna – nya episoder, cross-platform och Game Pass idag
- Fuskare påstås ha hittat till Battlefield 6-betan idag
- FZ High Score – Tales of the Shire fick ingen high score idag