Webbutvecklingsdagbok - efter studierna

Permalänk
Medlem
Skrivet av Xeonist:

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.

Om du menar att jag pissar på någons idé, där idén bokstavligen är att hänvisa till ett befintligt projekt så.., ja då gör jag det.

Jag tycker inte det är någon större bedrift att peka på exempelvis Linkedin, Facebook eller Youtube och försöka få någon att skapa en av dessa tjänster, fast bättre och lite annorlunda. Och än mindre att det skulle vara en sådan bedrift att den skulle vara värd hälften av bolaget, där någon annan bokstavligen bygger hela projektet.

Sådan idéer kan jag spruta ur mig på löpande band.

Jag fick precis en idé... Tänk dig Youtube, fast med mycket bättre sökfunktion och algoritmer för anpassat innehåll, möjlighet till offline-visning mm! Jag erbjuder 50% av bolaget till den som kodar ihop det gratis

Visa signatur

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"

Permalänk
Medlem
Skrivet av Superfrog:

Jag fick precis en idé... Tänk dig Youtube, fast med mycket bättre sökfunktion och algoritmer för anpassat innehåll..

Byt ut YouTube mot MySpace och gör det där inlägget 2004 så har du Facebook.

Byt ut YouTube mot PayPal och gör inlägget 2012 så har du Swish.

Det är inte så att den tekniska (utvecklingsbiten, om du så vill) delen av dom där tjänsterna är vad som är revolutionerande med dom.

Skrivet av Superfrog:

..och än mindre att det skulle vara en sådan bedrift att den skulle vara värd hälften av bolaget, där någon annan bokstavligen bygger hela projektet.

Sådan idéer kan jag spruta ur mig på löpande band.

Precis, det kan du, och ändå har du inte skapat någon av dom tjänsterna.

Det är alltså inte idéen som sådan, eller koden bakom, som skapar en framgångsrik tjänst.

Permalänk
Skrivet av medbor:

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

Tjo! Tack så mycket för ditt inlägg!

Ja, jag har valt att ej hoppa på det där potentiella projektet. Den största utmaningen med alla IT-idéer såsom jag uppfattar det är just att hitta användare/kunder eftersom det mesta redan finns (även om det inte nödvändigtvis är den mest fulländande versionen av sitt slag) och folk har ju en tendens att inte riktigt vilja ändra vanor (se friktion vid organisationsförändringar och organisationskultur).

Så jag ser det väldigt svårt att slå sig in på redan etablerade marknader om inte ens lösning tycks kunna lösa ett problem/frustration/smärta hos användare som nuvarande konkurrenter otroligt nog skulle ha missat att lösa redan. Jag har också erfarenhet att ha samarbetat med dessa slags personer som på något lustigt vis vill tjäna pengar och samtidigt tycker att andra ska jobba gratis - så varför ska då någon vilja betala dem också enligt liknande "resonemang"?!

Mvh,
WKF.

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk
Skrivet av Superfrog:

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.

Tjo! Tack så mycket för ditt inlägg!

Ja, dessa slags "idésprutande människor" brukar som högst bidra med deras konstanta "guld å gröna skogar"-snack, besatta detaljstyrning och den enorma avsaknaden av förtroende och tillit till en. Det sistnämnda är ju också motsägelsefullt: Om Du nu inte litar på en, varför anlitar du dem då till något som är tänkt att generera pengar från första början?🤔

En annan lustig grej är att dessa besatta detaljstyrande människor då indirekt antyder om att "De vet bättre" och om så är fallet, varför har de då anlitat en om de enligt deras besatta detaljstyrande beteende tycks nästan vilja göra det helt på egen hand?

Drivkraft å ambition håller jag stolt med om, däremot "kompetens" går att diskutera då jag fortfarande känner mig som en "mjukvaru-snickare" och INTE "mjukvaruingenjör" (jag har min "beef" med begreppet "ingenjör" som tidigare demonstrerat! ).

Jag känner att jag kan "kod-snickra" ihop saker just nu med diverse "kodverktyg" (PHP, JS, HTML, CSS/Tailwind) men skulle själva "kodverktygen" börja fela (t.ex., icke-hanterat specialfall, bugg, sårbarhet, etc.) så sitter jag hopplöst fast där utan någon suck i världen att kunna gå in som en riktig "mjukvaruingenjör" och laga kodverktyget (t.ex. gå in och laga/plåstra något icke-hanterat specialfall i BabelJS eller kanske ReactJS för just mitt unika ändamål).

Jag minns exempelvis jag lyssnade på någon podcast om där en riktig mjukvaruingenjör använde ett kodbibliotek som inte kunde hantera att specialfall så de kunde då gå in i källkoden och "plåstra" så det fungerade för deras unika ändamål och sedan slänga upp en PR i det öppna källrepot i form av en god gest som tack för att kodbiblioteket överhuvudtaget ens existerade.

Där snackar vi den kompetensnivån jag eftersträvar för att verkligen få titulera mjukvaruingenjör (troligen inriktat inom något särskilt (skript/programmerings)språk): kunna återuppfinna (eller "laga/förbättra") kodhjulet om så absolut allra nödvändigast skulle behövas i värsta fall. Jag syftar inte/aldrig på att bara för att du kan så ska du utan faktumet att du kan om så i allra värsta fallet skulle behövas.

Det var min lila random rant om "mjukvaruingenjör"-begreppet här från ingenstans ej riktat mot någon särskild utan bara i all allmänhet i och med begreppet "kompetens"!

Mvh,
WKF.

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk
Skrivet av Xeonist:

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%

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?

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)

Tjo, trådskaparen här! Tack så mycket för ditt inlägg!

Jag tänker mig att skulle Sweclockers forumet försöka etableras idag så skulle det troligen aldrig slå igenom för internetkonsumtionen ser annorlunda ut idag och det skulle bli som att alla skulle behöva bli den första att posta något men inget skulle ju finnas redan så varför skulle du då posta på ett dött forum? En slags paradoxeffekt utav det hela...

Så, att etablera sig idag med IT-baserad tjänst/produkt är helt annorlunda än innan "allt redan fanns inom olika marknadssegment" (det betyder inte nödvändigtvis att allt som redan finns idag är de fulländande versionerna!). Att hitta regelbundet betalvilliga slutanvändare är den största utmaningen upplever jag.

Tänk också på Survivor Bias; vi hör, läser & ser i princip bara alla framgångar. Extremt sällan som majoriteten av alla misslyckade kommer ut och berättar om hur länge de kämpat men aldrig riktigt nått någonstans. Liknande jämförelse: på gymmet ser du bara de som kanske kommer att ge upp eller aldrig gav upp; du ser aldrig de som faktiskt gav upp för de är inte där längre.

Mvh,
WKF.

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk

Webbutvecklingsdagbok - efter studierna

I väntan på NSW2!🥲

Jobb & Uppdrag
Inget nytt att rapportera.

Hobbyprojekt
Mer abstraktion åt kodfolket!!!
Äntligen lite bättre abstraktion gällande Valideringshanteringsfiler och deras Valideringshanteringsfunktioner vilket då även går att extrapolera till de kommande SQL-filernas och deras motsvarande SQL-funktioner:

function funk_use_validation(&$c, $validationHandler, $validationFunction, $source) { if (!is_string($validationHandler) || !is_string($validationFunction)) { $c['err']['FAILED_TO_RUN_VALIDATION_FUNCTION-funk_use_validation'] = "Validation Function needs a valid string for `\$validationHandler` and `\$validationFunction`!"; return false; } $optimizedValidationArray = null; if (isset($c['v_handlers'][$validationHandler])) { $optimizedValidationArray = $c['v_handlers'][$validationHandler]($c, $validationFunction) ?? null; } else { $validationFile = dirname(dirname(__DIR__)) . '/validations/' . $validationHandler . '.php'; if (file_exists_is_readable_writable($validationFile)) { $validationDataFromFile = include_once $validationFile; if (is_callable($validationDataFromFile)) { $c['v_handlers'][$validationHandler] = $validationDataFromFile; $optimizedValidationArray = $c['v_handlers'][$validationHandler]($c, $validationFunction) ?? null; } else { $c['err']['FAILED_TO_LOAD_VALIDATION_FILE-funk_use_validation'] = 'Validation Handler File ``' . $validationHandler . '.php` did not return a callable function.'; return false; } } else { $c['err']['FAILED_TO_LOAD_VALIDATION_FILE-funk_use_validation'] = 'Validation Handler File `' . $validationHandler . '.php` not found or not readable! (Reminder: a single string is parsed as `v_file=>v_function`!)'; return false; } } if ($source === "FILES") { $c['err']['FAILED_TO_RUN_VALIDATION_FUNCTION'] = "Use Validation Function `funk_use_validation_files(&\$c, \$optimizedValidationArray)` instead to validate `\$_FILES`!"; return false; } if (!is_array($optimizedValidationArray) || empty($optimizedValidationArray)) { $c['err']['FAILED_TO_RUN_VALIDATION_FUNCTION'] = "Validation Function needs a non-empty array for `\$optimizedValidationArray`!"; return false; } $allowedSources = ['GET' => [], 'POST' => [], 'JSON' => []]; if (!is_string($source) || !isset($allowedSources[$source])) { $c['err']['FAILED_TO_RUN_VALIDATION_FUNCTION'] = "Validation Function needs a valid string for `\$source` (\"GET\", \"POST\" or \"JSON\" - uppercase only)!"; return false; } $inputData = null; if ($source === 'GET') { $inputData = $_GET ?? null; $c['v_config']['source'] = "GET"; } elseif ($source === 'POST') { $inputData = $_POST ?? null; $c['v_config']['source'] = "POST"; } elseif ($source === 'JSON') { $inputData = json_decode(file_get_contents('php://input'), true); if (json_last_error() !== JSON_ERROR_NONE) { $c['err']['FAILED_TO_RUN_VALIDATION_FUNCTION'] = "Validation Function needs a valid decoded JSON string for `\$source`!"; return false; } $c['v_config']['source'] = "JSON"; } if (!is_array($inputData) || empty($inputData)) { $c['err']['FAILED_TO_RUN_VALIDATION_FUNCTION'] = "Validation Function needs a valid non-empty array for `\$inputData`!"; return false; } // TODO: REMOVE THIS LINE WHEN DONE TESTING // This is just for testing purposes to see the input data var_dump("TEST DATA(GET/POST/JSON):", $inputData); $c['v_ok'] = true; $c['v'] = []; $c['v_data'] = []; funk_validation_recursively_improved( $c, $inputData, $optimizedValidationArray, $c['v'], $c['v_data'], ); if ($c['v_ok']) { $c['v'] = null; return true; } if ( isset($c['v_config']['show_v_data_only_if_all_valid']) && $c['v_config']['show_v_data_only_if_all_valid'] === true ) { $c['v_data'] = null; } return false; }

function funk_use_validation(&$c, $validationHandler, $validationFunction, $source)

Tidigare behövdes det två funktioner för att först ladda in en fil och sedan köra deras funktion men nu kan allt göras med enbart denna simpla enkelrad:

$v_test = funk_use_validation($c, "v_test", "v_test2", "GET");

LLM:er har angett så kallade dispatchers vilket är tänkta att innehålla alla framladdade funktioner när nya filer inkluderats. Om du inte redan vet så leder inkluderande av samma filer inuti PHP till att du får tillbaka boolean istället för att faktiskt få ta del av filens innehåll då tanken är att du redan har den någon annanstans i minnet.

Inuti den globala konfigurationsvariabeln $c gick det då finurligt att ha ['v_handlers']-nyckeln vilket först är tom men kan fyllas med de inkluderade filerna. Varje inkluderad fil är då det instängda funktionsomfånget (eng. "function scope").

Med andra ord kan då varje fils funktioner åkallas via dessa lagrade filfunktioner i en och samma globala konfigurationsvariabel så att säga. Och går det för Validering så kommer det även att gå för SQL-frågeskapande.

Denna "snilleblixt" kom till mig när jag funderade på faktumet att middlewares är endast inkluderade filer i form av returnerade funktioner vilket innebär att i nuvarande lösning så går det bara att köra en middleware-fil en gång sedan är den "förbrukad".

Eller nej, den finns fortfarande kvar i "minnet" så länge namnet på funktionen går att ta reda på så går den att köras om igen troligen eftersom funktionen är då deklarerad?🤔

Från flera steg till bara ett par steg...!
Ett annat (medveten ordvits) - framsteg - är att nu innehåller den huvudsakliga inkluderade filen bara tre steg istället för tidigare fem steg. Dessutom är alla huvudsakliga initieringsfunktioner middlewares som körs innan någon ruttmatchningsfunktion körs.

Lägg märke till att middlewares-mappen nu har tre undermappar. En mapp är det som körs innan någon ruttmatchning äger rum, en annan mapp är den som körs efter att ruttmatchning har ägt rum men själva hanteringsfunktionen för den matchade rutten har inte ännu körts. Den tredje mappen är således det som körs efter att en HTTPS-begäran har hanterats.

Två saker nu:
1) Det är valfritt på ruttmatchningsnivån huruvida någon slags "afterwares" ska köras efter hanterad HTTPS-begäran eller ej. När JSON och/eller HTML returneras så går det att med tydligt funktionsnamn ange i stil med:

// Returnera JSON och AVSLUTA sedan skriptet return_json_and_exit($c, $json, $code); // Returnera KOMPILERAD SIDA (html) UTAN att avsluta skriptet return_page_without_exit($c, $page, $code);

Den första funktionen i koden ovan kommer att köra exit; efter returnerat JSON och valfri statuskod medan den andra funktionen ovanför kommer att fortsätta köra skriptet och därmed eventuella middlewares innan skriptet avslutas.

2) Och detta återkommer till det övre nu: Tack vare övertydliga funktionsnamn så är det svårare att göra fel eller snarare svårare att misstolka. Det ledde till en intressant tanke: eftersom mapparna för middlewares hypotetiskt talat kan ha samma filnamn så skulle det kunna gå att därmed ha samma funktionsnamn på dem då deras funktionsnamn ska motsvara deras filnamn.

I och med detta så finns det en påtaglig risk för Utvecklaren att uppleva otydlighet i konfigurationen av vilka middlewares som körs före ruttmatchning, efter ruttmatchning och före rutthanteringsfunktion, och efter hanterad HTTPS-förfrågan.

Mapparna kan komma att få helt andra namn då det inte framgår riktigt om after_route_match betyder innan rutthanteringsfunktionen körts eller inte. Det val jag har tagit för övrigt är att alla middlewares måste ha unika filnamn och därmed unika funktionsnamn även om de kanske vill kunna köras dubbla gånger under en HTTPS-förfråganscykel.

Exempelvis kanske du vill logga att du påbörjat en HTTPS-förfrågan och sedan kanske du vill logga att du har framgångsrikt avslutat en viss slags HTTPS-förfrågan. Samma fil kommer ej då att kunna köras så tillvida inte det konfigureras att alla körda middlewares samlas i en liknande ['mw_handlers']-nyckel på den globala konfigurationsnivån.

Problemet är då att otydlighetens djävulskap då kan åkallas från ingenstans och få Utvecklaren att kanske för sekunden tvivla på om det är rätt eller fel konfigurerat i de olika middlewares-arrayerna i den huvudsakliga konfigurationsfilen med rutter och förknippade middlewares.

Därför lutar jag starkt mot att tvinga unika filnamn för middlewares vilket då även möjliggör att kunna samla dem i en och samma ['mw_handlers']-nyckel på global konfigurationsnivå och låta Utvecklaren välja om denne vill köra en redan körd middleware igen fast med kanske ny last (eng. "payload").

Sammanslagna middlewares eller inte - det är den aldrig ständiga frågan?
Om du har varit överdrivet investerad i hur detta hobbyprojekt har utvecklats de snart två månader(?) så kanske du känner igen dessa middlewares-namn:

return [ '<CONFIG>' => [ 'middlewares_before_route_match' => [ 'm_https_redirect', 'm_run_ini_sets', 'm_set_session_cookie_params', 'm_db_connect', 'm_headers_set', 'm_headers_remove', 'm_start_session', 'm_prepare_uri', 'm_match_denied_exact_ips', 'm_match_denied_methods', 'm_match_denied_uas' ], 'middlewares_after_successful_request' => [], 'no_middlewares_match' => [ 'json' => [], 'page' => [], ], 'no_route_match' => [ 'json' => [], 'page' => [], ], 'no_data_match' => [ 'json' => [], 'page' => [], ], 'no_page_match' => [ 'json' => [], 'page' => [], ], ], 'ROUTES' => [ 'GET' => [ '/test/:id' => [ 'handler' => [ 'r_test' => 'r_test2', ], 'data' => [ 'd_test' => 'd_test2', ], ], '/test2' => [ 'handler' => 'r_test3', 'data' => 'd_test3', ], '/test3' => [ 'handler' => 'r_test5', ], ], 'POST' => [ '/test/:id' => [ 'handler' => [ 'r_test' => 'r_test2', ], 'data' => [ 'd_test' => 'd_test2', ], ], ], 'PUT' => [], 'DELETE' => [], 'PATCH' => [], ], ];

Konfigurationsfilen för Middlewares, Rutter & deras Hanteringsfunktioner

Precis!

Det är funktionerna som annars tidigare kördes under steg noll (innan det blev vettigare numerär ordningsföljd) där de mindre olika funktionerna nu fungerar exakt likadant fast är sina enskilda middlewares lagrade givetvis i before_route_match-mappen.

I princip är ['middlewares_before_route_match']-nyckeln det initiala som körs innan någon ruttmatchning försöks och detta går att ändra efter eget måfå. Självfallet finns då en mycket hög risk att någon ny Utvecklare gör fel här i tron om att det är bara att radera alla middlewares där.

Vissa är mycket viktigare än andra. Framförallt de som sätter särskilda ini_sets() och vissa säkerhetsbaserade HTTPS-huvuden. Dessutom kan det bli mycket tokigt med ruttmatchningsfunktionen som behöver en giltigt formaterat URI tack vare m_prepare_uri.

En fundering då har varit att slå samman vissa middlewares som är essentiella för att det hela ska kunna starta konsekvent för majoriteten av Utvecklarna samtidigt som de ska ges den enorma friheten att kunna välja vad som bör köras innan något ruttmatchningsförsök påbörjas.

Eller så kanske det finns en viss "charm" i att kunna se precis alla små - men ytterst viktiga - steg som körs innan huvudnumret i webbapplikationen äger rum?🤔 Det hela har också lite av en kanske (o)trevlig bismak av objektorientering där vi nu har "kapslat" hur den ska bete sig för en given fil.

I princip skulle det gå att modifiera i grunden så det skulle gå att välja andra liknande filer men med andra globalt konfigurerade middlewares i en helt annan körordning; och därmed ett helt annat webbapplikationsbeteende? *hm*

Det här med SQL-frågor och att inte återuppfinna hjulet alldeles för mycket på nytt...
Jag håller nu på med att skriva följande funktioner samtidigt så att de kan agera i samklang:

// Skapa SQL-hanteringsfil och/eller SQL-hanteringsfunktion likt Valideringsfiler & Valideringsfunktioner function cli_create_sql_file_and_or_handler() // Konvertera förenklad SQL-text till ren SQL-frågesats, och valfritt en hydrationssats & bundna parametrar vid möjlighet function cli_convert_simple_sql_query_to_optimized_sql($sqlArray, $handlerFile, $fnName)

Den första fungerar nästan likadant som funktionen för att skapa en Valideringsfil och/eller Valideringsfunktion. Skillnaden här är att för att få ut någon användbar användning av de genererade SQL-filerna och deras funktioner så måste snaran dras åt gällande friheten här - tyvärr!

Först och främst finns det ingen chans i världen att någon skulle vilja försöka lära sig skriva annorlunda SQL-språk bara för att i slutändan få ut en vanlig SQL-sats - då kan de lika gärna skriva den i phpMyAdmin eller liknande SQL-databashanteringssystem för med omedelbar återkoppling om syntax, om diverse databastabeller och/eller databastabellkolumner existerar, och så vidare.

För det andra så måste det gå fort, lättsamt och lättförståeligt till. Ju mer ny(!) syntax att behöva ta hänsyn till, desto mindre tålamod och de PHP-baserade ORM:erna kommer att skratta hela vägen från sina GitHub-repon trots att kanske flera av deras Slutanvändare fortfarande råkar skriva diverse "N+1"-satser.

Således fungerar det så här just nu (ja, jag ska flytta på de två sistnämnda argumenten, det upplevs inte logiskt) när du först skriver i CLI:t:

php funkcli create s test2=>test2 authors,comments select

Så skapas en SQL-hanteringsfil (om den inte redan finns) och dess SQL-hanteringsfunktion:

function s_test2(&$c) // <authors,comments> { $DX = [ '<CONFIG>' => [ '<QUERY_TYPE>' => 'SELECT', '<TABLES>' => 'authors,comments', '[SUBQUERIES]' => [ '[subquery_example_1]' => 'SELECT COUNT(*)', '[subquery_example_2]' => '(WHERE SELECT *)' ] ], '<MER_KOMMER>' => '<HÄR_SNART_OXÅ!>', ]; return array([]); };

Notera inuti kommentaren vilka tabeller det avser såväl som inuti '<TABLES>'-nyckeln, samt att det framgår vad för slags SQL-fråga det berör: en SELECT-fråga. Detta hjälper till med kompileringssteget och även vad som ska visas och inte.

Först hade jag tankarna om att visa alla möjliga SQL-nyckelord men då skulle Utvecklarna överflödas och även börja fundera på om allt måste användas? Så istället ska du få det begränsade som behövs för en given typ av SQL-fråga.

Exempelvis INSERT-frågan kommer endast innehålla de nödvändiga kolumnerna för den enskilda valda tabellen (jag försöker anamma ACID-principen där varje SQL-fråga ska göra en sak i taget - sedan hur detta ska gå ihop med transaktioner och tillbakarullning är ett framtida monster att bemöta!😱) inklusive de rätta bundna parametrarna så att du i princip bara behöver kompilera så får du en fungerande SQL-sats för att föra in data för en given tabell.

Liknande kommer att ske för UPDATE- & DELETE-satser där i princip bara SET (för UPDATE), WHERE (både UPDATE & DELETE) och FROM (endast DELETE bland dessa två) kommer att finnas för att förenkla vad som ska bara detaljkorrigeras av Utvecklaren innan kompilering.

SELECT-frågor blir däremot mycket intressant och där det största värdet med hela ska finnas: Här får du först alla valda tabellkolumner för dina valda tabeller och utifrån finurliga funktioner när tabeller först läggs in via .sql-filer under schemas-mappen så kan kompatibla JOINs lagras i förhand.

Detta innebär i sin tur att när SELECT-frågor med olika tabeller väljs så kommer det redan finnas alla "inställda" JOINs för dessa tabeller emellan. Det Utvecklaren ska välja då är vilka kolumner från respektive tabell som är av intresse, eventuella WHERE-satser och möjligen - exklusivt för SELECT-frågor - diverse kolumnbaserade aggregationsfunktioner såväl som ORDER, LIMIT & OFFSET.

Frågan är då hur det ska kommuniceras vad som är "obligatoriskt" och vad som är valfritt?🤔 Jag har funderat på att kommentera ut diverse nycklar som inte behövs om de inte önskas. För om de inte ens finns som tillgängliga nycklar så kan det ju bli tolkningen att de inte ens går att använda från första början eftersom de inte finns? (likt hur de inte finns för UPDATE, DELETE & UPDATE...)

Det förhoppningsvis största - och tänkta - värdet med SELECT-frågorna är att snabbt välja det som önskas, slå samman dem och sedan få en snabb datahydreringsfunktion att köra vid behov för att snabbt kunna presentera all data på önskat vis.

En rolig sak som slagit mig för någon månad sedan är att bara för att du slår samman två tabeller med JOIN - exempelvis "författare" och "böcker" - så vet datahydrering inte om du syftar på "Bok där varje författare är en undernyckel", dvs., "böcker med författare" eller om du syftar på "Författare där böckerna är en undernyckel", dvs., "författare med böcker".

Du hämtar alltså "Författare" & "Böcker" där varje författare har ett antal böcker och varje bok tillhör en författare (för enkelhetens skull i detta exempel) men hur du vill presentera all data med dessa två JOINade tabeller är helt och upp till dig att både besluta och sedan förverkliga med hjälp av lämplig datahydreringsteknik!

Gällande datahydreringsteknik så ser jag mig framför två alternativ:
1) Datahydrering med två loopar där första loopen är för varje rad av data på kolumnnivå och sedan andra loopen går då igenom varje kolumn och kollar om den redan finns på rätt nyckelnivå eller inte och sedan stoppar in den på rätt plats under den så kallade variabeltilldelningen.

2) Datahydrering med bara en loop då det är hårdkodat redan vart data ska stoppas - dvs., variabeltilldelandet är hårdkodat genom att den vet redan tabellkolumnerna. Mångmiljonfrågan är vad detta får för eventuell prestandavinst eller om det är magsjukeinducerande mikrooptimeringstrams om någon Databassenior skulle se denna datahydreringsteknik?😂

Vidareutbildning
Inget nytt att rapportera mer än att jag sett följande YT-klipp om rekursion:

Klippet håller mycket hög kvalité och hoppar mellan rekursion och iteration i hur funktioner kan skrivas för att på så vis förstå deras för- och nackdelar (och/eller kompromisser).

Även andra YT-klipp från denna så kallade "CodeTuber" håller mycket god kvalité enligt mig!🫡

Jag har ju varit smått sugen att få till den rekursiva valideringsfunktionen till en iterativ sådan, men absolut inte på ett hyfsat bra tag då rekursionsdjupet måste gå väldigt djupt för att orsaka problem vilket i sin tur också betyder enormt nyckeldjup i data som ska valideras.

Då ställs frågan om inte data ska ompaketeras på ett mer lätthanterligt vis för att förebygga sådana rekursionsdjupsrisker från allra första början istället?😬

På återseende!

Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk

Webbutvecklingsdagbok - efter studierna

"It is a matter of perspective!"
- Vortigaunt, Half-Life Alyx (2020).

(LÄSVARNING: Detta inlägg innehåller ordet nullbar från engelskans nullable ett antal gånger. Läs inte fel!🤪)

Jobb & Uppdrag
Jag har både mycket fruktansvärda nyheter och delvis goda nyheter!

Slutet på något stort och samtidigt början på något annat stort?!
Idag tog jag kontakt med en Uppdragsgivare jag nästan hade fakturerat i fem års tid. Kontakten skedde via telefonsamtal och under det kortvariga samtalet fick jag det konstaterat utifrån vad jag redan fruktat: "En av kärnverksamheterna läggs tills vidare på is medan det satsas på att automatisera delar av det!"

Med andra ord så har jag då numera inga Uppdrag från denna Uppdragsgivare (eller någon annan för den delen). Det "kugghjul" jag varit under dessa nästan fem års tid var just något väldigt manuellt vilket också krävde tålamod och en hel del mental ansträngning.

Nu är detta tänkt att kunna automatiseras med hjälp av LLM:er och OCR-teknologi som ska kunna göra det jag annars gjorde manuellt i diverse erhållna PDF-filer. Huruvida det kommer att gå och hur mycket fel - och därmed manuell mänsklig handpåläggning - detta leder till och om det är värt "det nya priset" återstå att se.

Detta är inget "Hur kunde jag inte se det komma?!"-fenomen dock utan jag har haft det i åtanke till och från under dessa nästan fem år. Webbutvecklingsutbildningen jag hoppade på för nästan snart tre år(!) sedan var den naturliga början på att börja med något mer kodintensivt än enbart HTML-skrivande av h1:or till h6:or kombinerat med AHK-skript under dessa nästan fem års tid.

Och så hamnade jag ju här för snart tre år sedan!

Den andra Uppdragsgivaren då? - Från exjobbet?
Jag har inte hört något nytt från den än så länge nöjda Uppdragsgivaren efter exjobbet jag gjorde och sedan det kortvariga uppdraget där jag tog fram en digital modul åt just den Uppdragsgivaren.

Utmaningen där verkar ligga i Uppdragsgivarens finansiella medel: det vill säga få betalt för ett eventuellt referensuppdrag för att då kunna nyttja en del av medlen där för att anlita mig för mina Webbutvecklingstjänster.

Det blir helt till att söka Uppdrag och/eller hel- och/eller deltidsanställningar inom Webbutveckling varav det sistnämnda lär praktiskt taget vara omöjligt (eller före både ock) i rådande ekonomiska tider där alla vill spara in och få "billigt högkvalitativt".

De delvis goda nyheterna är väl att Internet fortfarande finns och är inte en fluga ännu så där går det alltid att leta!🫡

Ett intressant perspektiv kring (tekniska) intervjuer!
När vi ändå är inne på anställningar så kommer vi ju inte undan det klassiska obligatoriska: intervju och ibland av teknisk natur. Ett intressant perspektiv jag läste kring detta var från någon Slack-grupp där en designer berättade om vad som skulle kunna summeras i stil med:"Det handlar mer om processen än enbart resultatet!"

Personen syftade på att i princip vemsomhelst kan ta fram en webbplats idag men kan alla förklara ingående processen och varför diverse beslut som togs och varför? Varför valde du de färgerna, den databasen, den arkitekturen, de designmönstren, och så vidare.

Extrapolering av detta skulle kunna vara att berätta om planeringsarbetet, implementeringsstadiet, hur saker och ting förändrades och hur beslut då fattades i och med (oförutsedda) förändrade omständigheter.

Tar jag mitt synnerligen överdrivna hobbyprojekt så började det först och lades på is ett längre tag. När det sedan plockades upp igen så blev det en del drastiska förändringar i takt med att jag själv upptäckte hur irriterande vissa saker och ting blev och då lär det ha blivit ännu mer så för de som knappt sett det ännu (läs gärna min tidigare Super Mario Maker 2-jämförelse).

Resultatet är så klart viktigt: Nådde det hela i hamn? Vad hände sedan? Vilka buggar/problem uppstod? Och vad är de nästa nödvändiga stegen om det hela skulle fortsättas framåt? Kortfattat talat handlar det mer om att demonstrera förståelse för klassiska designmönster, DSA:s, och annat vilket antyder om ens problemlösningsförmågor vilket förhoppningsvis förbättrats i takt med varje (hobby)projekt!

Hobbyprojekt
På tal om så kallade hobbyprojekt så finns en del nytt där att berätta!

Hur mycket är det kvar egentligen innan "beta"-versionen?🤔
Först till den mörklila(?) elefanten i rummet: Hur mycket är det egentligen kvar innan det går att lansera en klassisk "beta"-version så att säga? Jag siktar på att få en färdig senast denna månad för det går inte att hålla på i alla evigheter med detta i rådande ekonomiska situation ändå så.

Följande skulle jag påstå är kvar för ett "beta"-släpp:
- Göra färdigt scaffolding-biten för SQL-frågorna INSERT, UPDATE, DELETE & SELECT.

- Skapa enkel datahydreringsfunktion vilket samverkar med simpel SQL-frågekörningsfunktion.

- Skapa förenklad Mallmotor (eng. Template Engine) med startstöd för {{ variabler }} och {% mer_komplext %}.

- Enklare webbplats för lansering av "beta"-versionen alla seriösa PHP-utvecklare kommer att skratta ut. [i](och med all rätt då😂!)

Jag måste så klart fortsätta att först höja och sedan sänka mitt eget hobbyprojekt för det är både mer imponerande än ingenting och samtidigt möjligtvis en liten smula: "Kanske inte rätt tidsprioriterat med tanke på vad som står på spel kanske?😬".

WHERE-klausulen blir det första stora "kodmonstret"!😱
I klassiska MySQL SQL-frågor så är det WHERE-klausulen som är det stora monstret att få till kodvis upplever jag det just nu.

För INSERT, UPDATE & DELETE så är det just nu en tabell i taget du hanterar vilket betyder att det inte går att råka välja kolumner med samma namn från flera tabeller eftersom du bara hanterar en i taget.

För just INSERT så går det bara att sätta in för en given tabell och då kontrolleras så att rätt kolumner finns och att även alla icke-nullbara utan standardvärden är inkluderade. Standardvärden sätts ju om det inte är nullbart men inget värde angavs.

För UPDATE & DELETE är där det börjar bli komplext och där WHERE-klausulen först kommer in SQL-spelet. Nu kan du välja vilket villkor som måste uppfyllas för att uppdateringen eller raderingen ska äga rum.

Faktum är att om du inte anger en WHERE-klausul så får du en jättevarning för UPDATE & DELETE eftersom dessa innebär då att du vill ändra/radera flera rader samtidigt vilket skulle kunna leda till oönskade beteenden i längden.

Visst, om du verkligen bara vill radera all data ur en tabell utan att radera tabeller så blir DELETE utan en WHERE-klausul mycket användbar. Däremot uppmanas Utvecklaren att vara väldigt försiktig vilket är varför en varning utfärdas men omvandlingen/konverteringen går ändå igenom så att säga.

Det som gör WHERE-klausulen till ett riktigt "kodmonster" är att den kan uttrycka extremt komplexa villkor om så önskas. Förutom klassiska bindeord såsom AND, NOT, IN, OR och BETWEEN bland annat vilket går att ha inuti parenteser också så är det också underfrågor (eng. subqueries) att överväga här med när WHERE-klausulen tillgängliggörs!

(och vi har ju hoppat över diverse aggregeringsfunktioner och annat som SELECT-frågor kan vilja nyttja utöver WHERE-klausulen!)

En viktig aspekt i det hela med komplexitet är att ju mer komplext kompilatorn blir, desto mer komplex blir också användningen av den vilket kan försvåra den praktiska användbarheten av den. (okej, om den blir precis en kopia av SQL-tolkningsmotorn då, men då snackar vi verkligen om att idiotiskt återuppfinna hjulet på nytt!🫠)

Sedan har vi de alla olika operatorerna framför varje villkor att uppfylla via eller inte och en av dessa är en fiende: den bitvisa |-operatorn då denna används i "den förenklade syntaxen" för att dela på olika likt (exempel från Valideringshanteringsfunktion i Valideringshanteringsfil):

$DX = [ 'one_digit' => "required|digit", 'user_password' => "required|password|between:5,10", 'user_password_confirm' => "required|password_confirm:user_password", ];

Hursomhelst, målet är att göra det så simpelt som det går i funktionell väg och en svaghet här kontra objektorientering är att det sistnämnda kan kapsla in sitt dataskapande där du inte har någon direkt kontroll över datastrukturen mer än via de tillhandahållna objektmetoderna.

På så vis blir signifikant svårare att göra fel eftersom även att tilldela fel kan varje given objektmetod kontrollera och slå ifrån direkt. Svagheten i samma veva med inkapslande av objektets data inuti objektet och indirekt kontroll av dess värden via tillhandahållna objektmetoderna är just inkapslandet i sig: Du ser inte alltid vad det är du har gjort till skillnad från det visuella.

Tänk om vi på något vis kunde få det bästa av båda världarna där vi lättsamt kunde se vad för slags strukturs data vi CRUD:ade utan att kunna råka ändra på själva strukturen i samma veva...?🤔

("Vad i h... Vad är det som flåsar mig i nacken nu?!... Vänta... säg inte..... vad sjutton tusan bövlar gör du här gui-mappen?!?? Vad säger du...? Fasta inmatningsfält med etiketter som visar vad för typ av data du manipulerar och backend-validering likt scaffoldandet i CLI:et och sedan uppdatera visuellt på nytt...?")

Ja, det är ju inte för intet användargränssnitt finns till med tydliga avgränsningar på vad som går och inte att CRUD:a!

Vidareutbildning
Inget nytt att rapportera.

På återseende!

Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk

Webbutvecklingsdagbok - efter studierna

Jag småstädade lite i gamla skåp och fann min 2017-kalender där jag såg att jag då vägde runt 72-73 kg medan jag idag väger mellan 77-78 kg.

Absolut ingen katastrof men jag har tidigare vägt runt 65-70 kg när jag var runt 16-19 år gammal. Större delen av fettet sitter i buken (inte bra!) och lite i ansiktet ("köttbullsansikte")!

Som tur var har jag följande tisha i bilden ovan vilket jag ska börja använda på gymmet i kombination med dagligt proteinpulver i förhoppningen att "datatransformera" större delen av den där vikten till muskler💪 istället för fett🧈.

Jobb & Uppdrag
🪦Vila i frid tills vidare tidigare Uppdragsgivare(?)🪦
För några dagar sedan skrev jag om den tidigare Uppdragsgivaren jag har haft i nästan fem års tid och hur det samarbetet har lagt på is i och med att Uppdragsgivaren hoppas på att kunna ersätta det jag tillhandahållit manuellt med hjälp av AI gällande inläsning av PDF-filer och sedan kategorisering av dess innehåll.

Frågan är hur mycket av detta kommer att tillföra annan slags nödvändig manuell mänsklig handpåläggning och om kostnaden i tid (och därmed pengar) kommer att vara värt det. Den som lever och bryr sig kommer att förr eller senare att få svar på den frågan!

Det är ju smått lustigt att - fortfarande i skrivande stund - ha blivit ersatt av LLM:er när jag samtidigt känner till dess både styrkor såväl som uppenbara svagheter och nackdelar.

Till och med OCR-algoritmer är riktigt usla på att läsa in texter från PDF:er och det största problemet, likt med LLM:er är att du inte blir tillsagd om "Här kanske det blev fel" utan du måste i princip "lusläsa" igenom för att hitta eventuella "statistiska misstag".

Nåja, nog om det "jobbgnället" nu!

🤔Vem vill egentligen bli känd för detta ute på stan?🤔
En annan sak för några dagar sedan var att en person på UpWork bjöd in mig flera gånger till intervju (=det är alltid först intervju för ett jobb du kan ha sökt eller om Uppdragsgivare där bjudit in dig manuellt; du accepterar intervjun, sedan accepterar du kontraktet, du påbörjar och levererar det, Uppdragsgivaren godkänner efter kanske vissa revisioner och sedan får du betalt in på kontot som går att plocka ut efter ett par arbetsdagar).

Först bjöd personen in mig för ett jobb på $ 30 vilket jag avböjde, senare $ 40 vilket jag också avböjde. Sedan i förrgår(?) fick jag ett erbjudande på $ 100 vilket höjde mina ögonbryn av måttligt intresse. Det hela handlade om så kallad "Mystery Shopping" vilket i sig är lite udda kan jag tycka.

Personligen känner jag till fenomenet men har aldrig utfört sådana slags uppdrag och jag är fullt medveten om olika kopplade bedrägerier till dessa: exempelvis du får för mycket betalt för något och ombes att betala tillbaka men istället öppnas dispyt mot dig där du blir av även med mellanskillnaden i slutändan (=bedragaren går med "vinst").

Å ena sidan kände jag att behövde ta Uppdraget för de "enkla pengarnas skull". Samtidigt kände jag att det inte riktigt var värt det hela för jag behövde ta mig in till en annan del av staden med hjälp av cykel eller buss vilket kostar pengar i rådande "intäktsbefriade livssituation" (det var en jädrans eufemism?😂).

Sedan fick jag reda på från Uppdragsgivaren via UpWork att vad som krävdes var besök hos några elektroniska butiker nere på stan inklusive fototagande av diverse delar av en Apple-affär i form av en slags granskning (eng. "audit").

Nackdelen såsom jag såg det var att bilderna skulle behöva granskas och beviljas av Uppdragsgivarens slutkund (troligen någon lång kedja av folk med koppling till Apple) och jag skulle alltså teoretiskt talat ha kunna behöva åkt in igen för att ta nya bilder och därmed kostat mer tid, pengar och självrespekt än vad jag gått med på.

En annan uppgift i det hela var att jag skulle gå in på ett par elektroniska butiker och agera som köpare av en bärbar dator och/eller en surfplatta och sedan skulle jag ha gått in på Uppdragsgivarens webbplats (vilket ligger utanför UpWork - varningsflagga här(!)) för att fylla i något formulär.

I princip hade jag kunna blivit "den där personen som springer runt och låtsas vara kund för pengarnas skull" ute på stan vilket inte precis är den största här i Sverige. Uppdragsgivaren på UpWork sa sedan att det var brådskande med deadline redan på torsdag (dvs., igår) men jag avböjde det hela och sa att jag upplevde att jag inte kunde garantera för både dem och mig själv att jag skulle kunna göra rätt från början utan att behöva gå utanför tid- & självrespektbudgetten.

🎯✒️❤Konsten att skriva så det träffar rakt i hjärtat!❤️✒️🎯
Jag vet vad du kanske tror nu med alla dessa emojis i dessa underrubriker: "Har snubben blivit chatGPT?". De används som visuella hjälpmedel för att vägleda ögat för vad som är underrubriker till de huvudsakliga rubrikerna.

Som sagt var så skrev jag för några dagar sedan om det potentiella avslutade samarbetet mellan mig och min nästan femårigt långa Uppdragsgivare (det är hypotetiskt möjligt att Uppdragsgivaren återkommer redan någon gång efter sommaren om de upplever mer problem än kostnadssänkande effekter av deras så kallade "AI-satsning") så skrev jag om detta på flera platser.

I en Slack-grupp skrev jag om det och sättet jag formulerade då om min färska Juniora Fullstacks-Webbutvecklar-bakgrund var att istället för att säga att "Jag kan X, Y, Z inom webbutveckling" så skrev jag, "Jag har varit i kontakt med X, Y, Z inom webbutveckling".

Mina nämnda styrkor blev/är backend och gällande den sociala biten skrev jag en viktig sak (anser jag): "Jag har inget 'ego' i det jag gör". Sistnämnda innebär att jag har inget emot att ha fel, för jag vill inte ha rätt, jag vill veta/lära mig rätt.

En social bromskloss är ju "garanterat" personer vars "egon" krockar med lagets mål inom samarbetsbaserade utvecklingsjobb? Jag tänker att det blir onödigt långa diskussioner om saker och ting som inte överenskommes såväl som om något "ego" är beslutsfattande så kan det verkligen bli friktionsmässigt långsamt arbetande inom vissa arbetsgrupper?🤔

Hursomhelst, en person skrev senare till mig privat i en Slack-grupp att personen gillar min inställning och skulle höra av sig vid tillfälle om personen skulle vara vid behov av utvecklare vid nya och/eller pågående projekt.

Roligt att åtminstone kunna skriva något som kan kommunicera det jag vill få kommunicerat: "Jag kan något men påstår absolut inte att jag kan allt och jag vill hellre lära mig rätt än att ha rätt och jag har inget ego kring det jag gör så i arbetsgrupper har jag lättare att agera som 'socialt glidmedel' än en 'bromskloss'!".

Hobbyprojekt
(När du läser härifrån så sitter jag just nu i fredagens pågående Kodfika med start kl15:00 - lite små kul eller inte alls! XD)

🔁Den återanvändbara WHERE-funktionen?🔁
Följande funktion kan nu användas av alla de olika SQL-frågorna (INSERT, UPDATE, DELETE & SELECT):

function cli_parse_where_clause_sql($tbs, $where, $queryType, $sqlArray, &$builtBindedParamsString){}

Funktionen tar en sträng men omvandlar den sedan till en array för att bryta ned utifrån varje |-symbol vilket kan bli problematiskt om den bitvisa |operatorn används. Den behöver även känna till vad för slags SQL-frågan den hanterar (INSERT, UPDATE, DELETE eller SELECT), vilka tabeller det gäller såväl som alla nuvarande inlagda tabeller.

Efter klassisk parametervalidering så börjar den - för varje WHERE-del fördelat på |-symbolen - att kontrollera om det finns särskild startsyntax såsom nyckelorden AND, BETWEEN, IN med flera och dessa får WHERE-satsen inte börja med men den får innehålla dem dock.

Sedan så finns följande Regex:

$wPartRegex = '/^([()=A-Za-z_\-0-9:]+)\s+([+\-*\/%=&|^!<>]+|ALL|AND|BETWEEN|EXISTS|IN|LIKE|NOT|SOME)\s+(.*)$/';

Den körs efter att särskilda specialfall först har hanterats och den kommer att utökas för att också kunna skilja på [Subqueries] vilket just nu inte stöds utan då blir det ingen matchning. Sedan kan den hämta ut de matchade delarna:

$mCol = $wMatches[1] ?? null; $mOperator = $wMatches[2] ?? null; $mValue = $wMatches[3] ?? null;

En QoL-sak jag har lagt in är en varning när operatorn för en given WHERE-del inte är = vilket betyder att SQL-frågor såsom DELETE & UPDATE kan komma att påverka fler än en rad i en vald tabell:

if ($mOperator !== '=' && ($queryType === 'DELETE' || $queryType === 'UPDATE')) { cli_warning_without_exit("[cli_parse_where_clause_sql]: WHERE Clause Part: \"$wPart\" in Query Type: \"$queryType\" has an Operator that is NOT `=`!"); cli_info_without_exit("This could lead to affecting more Table Rows than desired unless you really want that!"); }

Sedan har vi två fall för varje WHERE-del som ska hanteras:
1. Är det bara en tabell som skickades med?
2. Eller är det flera tabeller som skickades med?

if ($singleTable){} else{}

Vad som görs innan en WHERE-del läggs in i WHERE-satssträngen som till sist skickas tillbaka om inget felmeddelande har stoppat hela konverteringen är att först kontrollera att alla tabellkolumner och tabeller angivna inuti den nuvarande WHERE-delen existerar för du kan ju inte göra en SQL-fråga mot icke-existerande tabellkolumner och/eller tabeller, eller hur?

Rätt datatypbindning hämtas vilket behövs till SQL-bind_params()-metoden när ?-tecken ska ersättas med korrekt typ av data vid en given SQL-fråga. Och om det är hårdkodat värde istället så kontrolleras det att det är av rätt datatypbindning annars stoppas det hela av felmeddelande.

Men är allt frid och fröjd så byggs $parsedWhere-strängen ut med tabellen och/eller kolumnerna såväl som dess giltiga operator och hårdkodade värde eller?-tecken. Sedan fortsätter det hela tills att hela WHERE-satssträngen har hanterats och returnerats till den fullständiga SQL-strängen.

Det är några specialfall att behöva hantera här och var såväl som tilläggen av mellanslag här och var vilket är lite oregelbundet här vilket gör att ord kan sitta ihop vilket då skapar en ogiltig SQL-fråga.

På tal om (o)giltiga SQL-frågor så är det följande del i kompileringsfunktionen, det vill säga function cli_compile_dx_sql_to_optimized_sql():

// This contains the optimized SQL Query which will then replace the "$matchedReturnStmt" // The function can error out on its own so we do not need to check for the return value! $optimizedSQLArray = cli_convert_simple_sql_query_to_optimized_sql($evalCode, $handlerFile, $fnName); // We validate the optimized SQL Query String by using the Prepared Statement that should not fail // If it fails, we will catch the exception and inform the Developer. It could fail due to actual // invalid SQL String Syntax or because of a mismatch between the Table Configuration in `tables.php` // and the actual Table in the MySQL DBMS (e.g. phpMyAdmin, Adminer, etc.) assuming it exists! $dbConnect = cli_db_connect(); $queryToTest = $optimizedSQLArray['sql'] ?? null; if ($queryToTest === null || !is_string_and_not_empty($queryToTest)) { cli_err_without_exit("The optimized SQL Query is Empty or NOT a Valid String in SQL Function \"$fnName\" in \"$handlerFile.php\"."); cli_info("Check if indeed the `sql` key was provided from the returned Optimized SQL Array Variable?"); } try { cli_info_without_exit("Testing the Optimized SQL Query String from SQL Function \"$fnName\" in \"$handlerFile.php\"."); $stmt = $dbConnect->prepare($queryToTest); } catch (mysqli_sql_exception $e) { cli_err_without_exit("The Optimized SQL Query String FAILED during Statement Preparing (from SQL Function \"$fnName\" in \"$handlerFile.php\")."); cli_info_without_exit("This means either\n1) Actual invalid SQL String Syntax that somehow slipped through the Compilation Stage when it shouldn't have, or that \n2) The Table and its configuration added in `config/tables.php` DOES NOT MATCH the Table with the same name in your local MySQL DBMS (e.g. phpMyAdmin, Adminer, etc.) assuming it exists!"); cli_info("Internal MySQLi Error: \"" . $e->getMessage() . "\""); } cli_success_without_exit("The SQL Query String in SQL Function \"$fnName\" in \"$handlerFile.php\" was Successfully Validated with 0 Errors When Sending it Prepared to the local MySQL DBMS!"); cli_info_without_exit("Attempting adding the entire Optimized SQL Array as the returned value in SQL Function \"$fnName\" in \"$handlerFile.php\"!");

som ansluter till lokala MySQL-databasen och sedan provar att skicka en så kallad förberedande SQL-sats (eng. "prepared statement"). En sak jag inte insåg här är att syntaxen kan vara korrekt fast SQL-frågan kommer att misslyckas på grund av hur det är konfigurerat i databasen eller vilka data som egentligen skickas med i samband med SQL-frågan (t.ex. en primär- och/eller referensnyckel som inte finns).

🗊Hur mycket är kod och hur mycket är kommentarer?!🗊
Avslutningsvis för denna gång - och det är i skrivande stund 25 minuter i Kodfikat just nu - så tog jag mig tillfället i akt att räkna antalet kommentarer kontra kod i den största filen just nu: cli_funs.php.

  • 6963 rader av kod, kommentarer och/eller radbrytningar.

  • 1368 rader som börjar med kommentarstecknen // (/* */ används ej i denna fil).

  • 5595 rader av faktisk kod, hur långt eller kort det må vara, och ibland radbrytningar.

Räknar vi då på: 1368 / 6963 = 0,1964 så får vi alltså att cli_funs.php-filen just nu innehåller nästan en femtedel i form av bara kommentarer(!) Vissa kanske älskar detta och andra får magsjuka, huvudvärk och känningar i kroppsdelar de aldrig tidigare har fått känningar i!

Vidareutbildning
För någon dag eller två sedan såg jag följande YT-klipp som handlar om en studie från Apples mjukvaruingenjör med fokus på AI/LLM:er:

I princip "sågas" hajpen om att LLM:ers så kallade "tänkande" eller "resonemang" skulle vara likställt det människor gör och att det hela egentligen emuleras i form av ord. Liknande har jag resonerat kring LLM:ers så kallade "tänkande/resonemang".

De förutser statistiskt talat nästa ord i en följd av ord utifrån vad dess träningsdata innehåller vilket skulle representera någon form av "tänkande" eller "resonemang". Vidare visade YT-klippet resultat från Apples studie av populära LLM:er och hur de misslyckades med sitt "tänkande" eller "resonemang" ju mer komplext något blev trots att lösningen egentligen var endast rekursiv i sin natur.

Exemplet var Hanoi-tornet och antalet cirklar du använde dig av. Efter ett visst antal så misslyckades alla populära LLM:er med sitt "tänkande" eller "resonemang" trots att de borde via "emergenta beteenden" borde ha kunna extrapolerat fram den optimala rekursiva funktionen oavsett ökad komplexitet i Hanoi-tornet.

(Nu är jag ~40 minuter inne i Kodfikat - så där får du lite halvfacit på den tid det ibland tar att skriva!)

På återseende!

Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk

Webbutvecklingsdagbok - efter studierna

Mycket kort uppdatering för en gångs skull: I och med den plötsliga och smått ofrivilliga "ledigheten" som följd av inga nuvarande arbeten och/eller uppdrag så har jag beslutat för mig själv att "tvinga mig själv att vara ledig" fram till och med nu på söndag. Anledningen bakom det hela är att jag lider av den så kallade "gråzonen" i egenskap av Egenföretagare.

Detta är när du känner att du skulle kunna vara ledig och koppla av samtidigt som du "tuggas" utav tankar längst bak i huvudet att du skulle kunna söka mer jobb och/eller fler uppdrag, och/eller samtidigt lära dig något nytt (=bygga ut din kompetensportfölj) för att öka dina sökmöjligheter/-chanser. Detta brukar även finna inom mig på helgerna med där jag bör vara ledig och "ladda batterierna" så att säga.

Berätta gärna hur Du själv som Egenföretagare har bekämpat denna mentala utmaning då det till skillnad från en heltidsanställning (påstår ej dock att det alltid är 100 % tryggt i rådande tider men mer än ingenting åtminstone) där du inte riktigt kan "vinna" lika mycket som att spendera obetalt arbete på din fritid (kanske att du gör så att du kan få mer gjort senare nästa måndag).

Jag kommer fortfarande att vara aktiv i fritidsinriktade ämnen här på forumet!🫡🎮

Egenföretagande är på så vis parallellt en lyx och en börda!😅

Jobb & Uppdrag
Inget nytt att rapportera i och med "självtvingad ledighet". Återkommer här tidigast den 23:e juni 2025.

Hobbyprojekt
Inget nytt att rapportera i och med "självtvingad ledighet". Återkommer här tidigast den 23:e juni 2025.

Vidareutbildning
Inget nytt att rapportera i och med "självtvingad ledighet". Återkommer här tidigast den 23:e juni 2025.

På återseende här tidigast den 23:e juni 2025.!

Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk
Medlem
Skrivet av WebbkodsFrilansaren:

Mycket kort uppdatering för en gångs skull: I och med den plötsliga och smått ofrivilliga "ledigheten" som följd av inga nuvarande arbeten och/eller uppdrag så har jag beslutat för mig själv att "tvinga mig själv att vara ledig" fram till och med nu på söndag. Anledningen bakom det hela är att jag lider av den så kallade "gråzonen" i egenskap av Egenföretagare.

Detta är när du känner att du skulle kunna vara ledig och koppla av samtidigt som du "tuggas" utav tankar längst bak i huvudet att du skulle kunna söka mer jobb och/eller fler uppdrag, och/eller samtidigt lära dig något nytt (=bygga ut din kompetensportfölj) för att öka dina sökmöjligheter/-chanser. Detta brukar även finna inom mig på helgerna med där jag bör vara ledig och "ladda batterierna" så att säga.

Berätta gärna hur Du själv som Egenföretagare har bekämpat denna mentala utmaning då det till skillnad från en heltidsanställning (påstår ej dock att det alltid är 100 % tryggt i rådande tider men mer än ingenting åtminstone) där du inte riktigt kan "vinna" lika mycket som att spendera obetalt arbete på din fritid (kanske att du gör så att du kan få mer gjort senare nästa måndag).

Jag kommer fortfarande att vara aktiv i fritidsinriktade ämnen här på forumet!🫡🎮

Egenföretagande är på så vis parallellt en lyx och en börda!😅

Jobb & Uppdrag
Inget nytt att rapportera i och med "självtvingad ledighet". Återkommer här tidigast den 23:e juni 2025.

Hobbyprojekt
Inget nytt att rapportera i och med "självtvingad ledighet". Återkommer här tidigast den 23:e juni 2025.

Vidareutbildning
Inget nytt att rapportera i och med "självtvingad ledighet". Återkommer här tidigast den 23:e juni 2025.

På återseende här tidigast den 23:e juni 2025.!

Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)

I slutet av 90-talet, när jag under några år hade eget företag, så hade jag extremt svårt att ta ledigt. Satt i princip och programmerade/jobbade alla dygnets vakna timmar. Fungerade inte i längden och det slutade faktiskt med att jag började köra lastbil (som anställd) - det gjorde jag i c:a 12.

Sedan 2016 är jag anställd och jobbar som utvecklare/systemadministratör på ett fåmansbolag. Jobbar hemifrån och styr helt själv vad jag gör om dagarna. Det enda jag saknar är kollegor med datorer/programmering som intresse.

Eftersom jag gillar mitt jobb väldigt mycket har jag ibland svårt att ägna mig åt annat på fritiden. Det händer rätt ofta att jag pysslar med något jobbrelaterat även på helgerna, men i regel bara fram till lunch.

Att ha andra intressen utöver sitt jobb underlättar garanterat, och det är väl där jag fallerar. Tränar några gånger i veckan, men annars har jag inga direkta intressen. Försöker spela (på datorn), men det är inte lika roligt längre.

Visa signatur

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"

Permalänk
Skrivet av Superfrog:

I slutet av 90-talet, när jag under några år hade eget företag, så hade jag extremt svårt att ta ledigt. Satt i princip och programmerade/jobbade alla dygnets vakna timmar. Fungerade inte i längden och det slutade faktiskt med att jag började köra lastbil (som anställd) - det gjorde jag i c:a 12.

Sedan 2016 är jag anställd och jobbar som utvecklare/systemadministratör på ett fåmansbolag. Jobbar hemifrån och styr helt själv vad jag gör om dagarna. Det enda jag saknar är kollegor med datorer/programmering som intresse.

Eftersom jag gillar mitt jobb väldigt mycket har jag ibland svårt att ägna mig åt annat på fritiden. Det händer rätt ofta att jag pysslar med något jobbrelaterat även på helgerna, men i regel bara fram till lunch.

Att ha andra intressen utöver sitt jobb underlättar garanterat, och det är väl där jag fallerar. Tränar några gånger i veckan, men annars har jag inga direkta intressen. Försöker spela (på datorn), men det är inte lika roligt längre.

Tjo, tack så mycket för dina delade (arbets)livserfarenheter!

Igår "lyckades" jag spela flera timmar på nya NSW2 vilket kändes skönt, både för stunden och efteråt. Vissa gamla spel laddar och flyter på bättre än från gamla NSW1. Datorspel har jag också svårare för med numera, då krävs det att spelets (unika) tema verkligen tilltalar mig så att jag kan "förlora mig själv" i det hela.

Ibland känns det också som att efter avkopplingen med spelande eller annat så är det nästan som att jag vill "bocka av" eller "kryssa i" antalet timmar jag nu har varit ledig vilket kanske också är en del av det övergripande problemet?

Mvh,
WKF.

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk

Webbutvecklingsdagbok - efter studierna

Du märker kanske att ikoner används numera framför rubrikerna. Det är för att - detta är INTE sant - DIGG ringde upp mig och hade anmärkningar på webbtillgängligheten i rubrikerna och funderade på varför jag inte använder fler sätt att kommunicera på utöver text?🤔

Bland annat rekommenderar Webbriktlinjer att du använder dig av färger, former och/eller ikoner för att snabbare förtydliga vad olika saker och ting handlar om. En annan sak du märker är den nya rubriken Hälsa vilket du kan läsa mer om under dess rubrik.

🍏 Hälsa
Det har dykt upp en ny rubrikskategori numera att rapportera om: Hälsa vilket omfattar alla former av hälsa: fysisk och psykisk även om det går att hitta ytterligare former såsom emotionell, social och själsmässig.

Jag fortsatte min träning äntligen efter nästan ett års uppehåll. Senast jag tränade var nionde juli 2024! Det blev lätt överkropp idag. Det betyder följande:

  • 22 minuters uppvärmning på löpband

  • 3x12 20 kg bröst bänkpress stång

  • 3x12 ryggresningar för nedre rygg

  • 3x20 benlyft hängandes i luften för mage

  • 3x12 12,5 kg baksida axlar maskin

  • 3x12 18 kg (9 kg per arm) axlar hantelpress

  • 3x12 18 kg (9 kg per arm) biceps sidocurls

  • 3x12 40 kg triceps pushdowns med stång

Dold text

Imorgon blir det således "lätt underkropp" vilket är ben, vader & mage! 🫡

Möjligen funderar du på vad detta har med Webbutveckling att göra? Några saker:

  1. Fysisk aktivitet förbättrar hjärnans förmåga tack vare förbättrad syretillförsel och senast jag kollade så behövs en frisk hjärna för utveckling.

  2. Fysisk aktivitet kan ses som ett sätt att låta ens undermedvetna få arbeta på diverse utvecklingsproblem.

  3. Fysisk aktivitet kan göra så att du kanske lever längre vilket betyder att du förhoppningsvis kan och vill arbeta längre.

Med andra ord: helt klart en värdig investering för alla företag, vare sig egenföretagare eller inte.

🖥️ Jobb & Uppdrag
👻 Novellen om Uppdraget som aldrig blev av!
Förra veckan fick jag kontakt med en person på internet i hopp om ett uppdrag. Vi hade ett snack via Teams och utbytte kontaktuppgifter. Jag mejlade personen igår och idag fick jag dessvärre beskedet att jag ej kunde få ta del av uppdraget i och med dess komplexitet och omfattning i förhållande till min tyvärr icke-existerande erfarenhet med liknande projekt.

Jag besvarade att jag förstod och var van med sådana slags besked numera och sa till personen att denne är varmt välkommen att kontakta mig i framtiden om mindre komplexa projekt/uppdrag. Aldrig fel att ha dörren öppen och bara fortsätta vidare tills någon eller något nappar så att säga!

Som sagt var så är detta slags besked något jag numera är van vid dock! Alla söker erfarenhet, få söker någon att erbjuda denna erfarenhet från första början!😬 Eller för att uttrycka det på annat vis:

"Alla vill ha erfarenhet, få vill invest... - ursäkta - stå för kostnaden att bygga upp denna erfarenhet från första början!"

Möjligen är det lite som att alla vill ha tårtan men ingen vill baka den?🤭

(Detta är INTE riktat mot något enskilt företag, enskild person, eller annan enskild entitet av något slag - bara en reflektion jag har gjort på sistone!)

Denna så kallade "arbetslivserfarenhets-rävsax" är 100 % självförvållad som den "galning" jag är som hoppade på "IT-frilanskonsulteriet" direkt efter nyutexaminerad Junior Fullstacks-Webbutvecklare vilket det nu har gått smått drygt ett år sedan nyutexamineringen och det har gått som det har gått.

När jag tog examen förra året så hade jag fortfarande kvar Stockholmskunden vilket jag inte har kvar längre tills vidare. Jag ska™ söka vanlig anställning också - men först när jag har något "utstickande" att visa upp. För annars kommer jag med 99,7976931348623157 % sannolikhet att betraktas som vilken annan Junior som helst.

Tanken med "utstickandet" är inte bara resultatet i sig utan även förmågan att beskriva det i detalj, berätta olika problem som uppstod, lösningar där, erkännandet av faktiska svagheter såväl som styrkor med den slutgiltiga lösningen och allt. Detta tackar jag att jag lärt mig genom att ha läst intressant utlägg om det hela i en Slack-grupp där det diskuterades design och frontend.

Någon kommenterade att bara för att du skapat en snygg webbplats så gör det ju inte dig till en duktig frontendare (om du vill fokusera på t.ex. UI+UX) utan du bör kunna förklara och motivera varför du gjorde som du gjorde (vilket då visar på förståelsen av diverse UI- & UX-principer).

🤷‍♂️ Uppdrag kanske i november?
Förra veckan fick jag även SMS från än så länge nöjd tidigare Uppdragsgivare vilket meddelat via SMS till bland annat mig såväl som andra att de nu har fått tag på ett så kallat referensuppdrag vilket går av stapeln någon gång i mitten på november i år!

Det är alltså drygt fyra månader från nu och det hela kanske rör sig om runt tio lax plus moms på sin höjd så inte det optimala för min egen del som egenföretagare i nuvarande situation. Men helt klart bättre än ingenting om vi säger så!

🤔 A-kassa för Företagare verkar vara en myt?
Nu kan det bli känsligt så läs följande paragrafer med insikten att moderatorerna flåsar dig bakom nacken för säkerhetens skull...

I och med rådande situation så fick jag påtryckningar från nära & kära att ta reda på det här med A-kassa för företagare. Efter lite "doing my own research" - på gott & ont - så upptäckte jag (om jag förstått det hela korrekt) att syftet med A-kassa är för att Du ska komma ut i arbetslivet igen som anställd.

En sak jag läste i en Slack-grupp vilket kanske summerar det hela är att A-kassa är inte tänkt att "ta den ekonomiska smällen för ohållbart drivna företag". Med andra ord: Du ska inte kunna nyttja situationen att du bedriver ett företag utan intäkter förutom från A-kassan.

Exempelvis så bör inte A-kassan ersätta någon som bedriver ett vulkanförsäkringsbolag i Sverige med Sverige som den enda marknaden!?😅

Så vad är då "A-kassa för Egen(företagare" då? Jo, buffert heter den så kallade ersättningen och tillhandahålls av... 🥁... 🥁... 🥁... Egenföretagaren själv i egen hög person! 🤯

🌐 Hobbyprojekt
Programmeringsfilosofi-trams
En rolig effekt av att ta en paus från kodprojekt är när du kommer tillbaka och kikar på koden igen och funderar på varför du gjorde som du gjorde. Kommentarer hjälper förvisso och den största delen av koden i mitt nuvarande hobbyprojekt är i princip intern datavalidering, dvs., kontrollera att det är rätt slags data som senare ska transformeras.

I princip verkar högnivåprogrammering handla om datatransformationsflöden, dvs., att du vill transformera data från en till en annan i något flöde. Och transformationen måste inte vara direkt utan kan härledas från något annat.

Exempelvis så kommer en sessionskaka in för att kontrolleras mot en giltig lagrad session i en databas och om den är giltig så kanske vi ska returnera data. Datareturen i sig är en slags transformering av den respons vi skickar tillbaka och den var beroende också av en giltig sessionskaka.

Vi börjar med ett responsobjekt vilket vi ska returnera men innan vi returnerar det så måste vi transformera det korrekt och här kommer då hela biten in med autentisering, mellanmjukvara, auktorisering, databasmanipulering, datahydrering, eventuell HTML-kompilering och sedan skicka tillbaka det datatransformerade responsobjektet.

"Datatransformationsflöden" i sin tur är egentligen bara en mycket grov förenkling av det mer fundamentala: "Skriva och läsa ettor och nollor i CPU-caches, arbetsminnet och/eller annan form av lagringshårdvara".

Så kallade "Villkor" (eng. conditions) i SQL-satser
Jag har upptäckt och insett att funktionen som hanterar WHERE-satsen egentligen ska vara mer generell än så. Den heter därför numera:

function cli_parse_condition_clause_sql($tbs, $where, $queryType, $sqlArray, &$builtBindedParamsString){};

Och den kan då hantera alla olika delar av en SQL-sats som vill CRUD:a beroende på diverse "Villkor". Nästa steg nu för den där funktionen ovan är att fixa så att rätt tabell kan hittas när det finns flera tabeller:

// We need to find the correct Table for the $mCol when ":" is missing // because we now have multiple tables and the $mCol might just be // a unique column name without the table name giving us no table! $correctTb = null; // ":" missing, so find correct Table manually if (!str_contains($mCol, ":")) { // We only loop through tables that are actually in the $tbs array foreach ($allTbs as $tb => $colKey) { if (!in_array($tb, $tbs, true)) { continue; } foreach ($colKey as $k => $col) { if ($mCol === $k) { // We found the correct Table for the $mCol $correctTb = $tb; break 2; // Break out of both foreach loops } } } } // We can just extract the correct Table from the $mCol when it contains a ":" else { $correctTb = explode(":", $mCol, 2)[0] ?? null; }

När funktionen först körs så börjar den med att lagra tabeller med kolumner och unika kolumner:

$uniqueCols = []; $tbsWithCols = [];

Och detta gäller endast de som faktiskt används i SQL-satsen, inte antalet inlagda i tables.php. Nu inser jag också problematiken här:

foreach ($allTbs as $tb => $colKey) { // Only do this for the tables actually being processed! if (!in_array($tb, $tbs, true)) { continue; } foreach ($colKey as $k => $col) { if (isset($col['primary_key']) || isset($col['foreign_key'])) { $tbsWithCols[] = "$tb:$k"; // Special case for Single Table Queries (only 1 Table provided) // Then we know PK and FK are Unique Columns in that table! if ($singleTable) { $uniqueCols[] = $k; } continue; } if (!in_array($k, $uniqueCols, true)) { $uniqueCols[] = $k; $tbsWithCols[] = "$tb:$k"; } } }

Den ska alltså lägga in unika kolumner och även tabeller:kolumner. Problematiken är att om tabell1:namn först hittas och därmed läggs in så vet den inte om namn-kolumnen är unik eller inte förrän den kollat alla andra tabeller och kanske tabell2:namn faktiskt finns och då bör namn-kolumnen inte läggas till som en unik kolumn i den arrayen.

Min preliminära lösning i skrivande stund innan jag gjort något är att lägga in kolumner i unika kolumner och finns den sedan inlagd, dvs., någon annan tabell har exakt samma kolumnnamn... ELLER glöm det där - så här provade jag istället nu:

$removeDuplicateCols = []; foreach ($allTbs as $tb => $colKey) { // Only do this for the tables actually being processed! if (!in_array($tb, $tbs, true)) { continue; } foreach ($colKey as $k => $col) { if (isset($col['primary_key']) || isset($col['foreign_key'])) { $tbsWithCols[] = "$tb:$k"; // Special case for Single Table Queries (only 1 Table provided) // Then we know PK and FK are Unique Columns in that table! if ($singleTable) { $uniqueCols[] = $k; } } // If it is in the array, we add it to the $removeDuplicateCols array elseif (in_array($k, $uniqueCols, true)) { if (!in_array($k, $removeDuplicateCols, true)) { $removeDuplicateCols[] = $k; } $tbsWithCols[] = "$tb:$k"; } // If it is not in the array we add it to the $uniqueCols array elseif (!in_array($k, $uniqueCols, true)) { $uniqueCols[] = $k; $tbsWithCols[] = "$tb:$k"; } continue; } } $uniqueCols = array_diff($uniqueCols, $removeDuplicateCols);

När jag lägger in första som en unik kolumn så vet jag inte om den är unik men hittar jag samma igen då i unika kolumnen så kan jag lägga till bland de dubbletter som sedan ska tas bort med hjälp av array_diff()-funktionen.

Självfallet vill jag inte lägga till dubbletter till arrayen som innehåller information om vad som är dubbletter - således kontrollen. Läser jag rätt här: https://www.php.net/manual/en/function.array-diff.php så bör den fungerar som jag tänker mig: Den returnerar en array med endast de värden som finns i den första array-parametern som ej fanns i de nästkommande array-parametrarna i funktionen?

Första exemplet kan förvirra då den bara returnerar blue medan yellow kan uppfattas som unik men det är vad som inte finns baserat på första arrayen i jämförelse med alla övriga arrayer och inte vad för potentiellt unika värden de övriga arrayerna som jämförs mot kan ha?

Nåja, minioptimeringarna fortsätter överallt i koden. Den växer/förbättras överallt, förhoppningsvis infekteras den inte lika fort med "buggar"! 😆

🎓 Vidareutbildning
Inget nytt att rapportera mer än att följande YT-klipp jag såg igår: https://www.youtube.com/watch?v=j8tHU8bVsFc var verkligen en så kallad "clickbait" då den drog ut åtta minuter på att förklara något som handlade om att kontrollera datatyp innan dess potentiella egenskaper kontrolleras i JavaScript.

YT-klippet fick förvånansvärt nog både ris och ros när den egentligen bara borde ha fått ris. Jag tänkte rätt fort att om datatypen inte kontrolleras i JS (och i princip allt är prototyp-objekt i JS) så kan du göra den där fulingen som ett objekt med objektegenskapen length vilket kan innehålla hur lång sträng som helst när tanken är att den egentligen bara ska innehålla heltal för längden för ett faktiskt strängvärde.

Således inget jag visar upp här att spela upp utan bara länkar till om du själv vill få ont i huvudet i åtta minuter. Det hela osar "skill issues" på serversidan än någonting annat?! 🫠

På återseende!

Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk

Webbutvecklingsdagbok - efter studierna

En kombination av insikt av bättre SecOps, och/eller lathet(?)/energieffektivisering(?) har fått mig att inse att underrubriken om hälsan är av personlig natur plus att denna tråd handlar om Webbutveckling och inte någon slags "textbaserad Instagram" om mitt liv. Du vill läsa om Webbutvecklingsrelaterat innehåll, eller hur? Varför ska Du då behöva se någon annans personliga träningsdagbok?!🤔

Du kanske följer vissa YT-kanaler där vissa saker älskar Du att titta på medan annat avskyr du. Jag brukar titta på klipp från en YT-kanal vars skapare reagerar på den animerade versionen av One Piece där jag redan har sett det mesta (såg senaste avsnittet idag).

Men samma YT-kanal publicerar massor av annat de reagerar på som jag bryr mig noll om. Därför följer jag inte kanalen utan ser bara vissa dagar i veckan när särskilda klipp dyker upp i rekommendationsflödet för att YT otroligt(?) nog vet vad jag vill se från vilka och när!

Liknande upplevelse kanske Du fick när du helt plötsligt såg rubriken "Hälsa"?😜 Därmed så kommer du aldrig mer att se den i just denna tråd här hos Sweclockers!

(Jag kan däremot - som ett avsked till den underrubriken - berätta att samma vecka från förra gångens rapportering här så genomförde jag benpasset, följt av flertalet promenader följande dagarna därpå!🫡)

🖥️ Jobb & Uppdrag
Jag har fått återkoppling från personen jag träffade vid DragonTech för numera länge sedan. Icke-förvånande nog så har bolaget där personen arbetar gått vidare med andra (och förhoppningsvis mer kvalificerade) kandidater gällande en Utvecklingsroll.

Igår fick jag ett DM om ett potentiellt jobb för en person som verkar ha "vibbkodat" fram en React Native-baserad app som samtidigt tycks krascha en hel del. Det lär ju vara roligt att felsöka en sådan mobilapp utan att ha varit från grunden där du till 99 % kan vara säker på massa "Jaha, se där ja..!."-reaktioner när du går igenom den statistiskt sannolika "vibbkodade" koden...

Om Du hoppar in i primärt "människokodade" projekt så kan du åtminstone få en känsla för vad som tycks kunna ha varit skrivet av vem i och med "personliga kodstilar" som kan dyka fram genom semantisk frekvensanalys. Jag påstår inte att detta per automatik gör alla människoprojekt bättre än alla "vibbkodade" projekt.

Hursomhelst så verkar detta potentiella jobb inte ha några pengar och då blir det ju likadant som om jag fortsatte med mitt så kallade "hobbyramverk" i syftet att ta fram en portfölj. Det går nog dock samtidigt att argumentera att det förstnämnda möjligen skulle väga tyngre som framtida referens.

🌐 Hobbyprojekt
SQL-Stränggenerering för SELECT äntligen "tillräckligt färdig"!

I nedanstående finner Du exempel på hur en SQL-fråga först genererades med:

php funkcli create s test2=>test5 s author,articles,comments

Och sedan kompilerades med följande kommando två gånger efter att ha rättat efter felmeddelande:

php funkcli compile s test2=>test5 s authors,articles,comments [FunkCLI - INFO]: Found "$DX" variable parsed as a valid PHP Array! [FunkCLI - SYNTAX ERROR]: Table `articles` is already marked as joined in `JOINS_ON` Key in SQL Array `s_test2.php=>s_test5` for SELECT Query! [FunkCLI - INFO]: You can only join a Table once in the `JOINS_ON` Key! (based on `tables.php` File) [FunkCLI - INFO]: Current Joined Tables are: `authors`->`articles`->`comments`!

Vid lyckad kompilering så syns följande:

[FunkCLI - INFO]: Found "$DX" variable parsed as a valid PHP Array! [FunkCLI - WARNING]: The `<HYDRATION>` Key in SQL Array `s_test2.php=>s_test5` for SELECT Query Type was NOT set or is Empty! (or invalid Data Type - no attempt were made to parse it due to invalid Data Type; must be an Array) [FunkCLI - INFO]: Hydration will not be applied to the results of this query! [FunkCLI - SUCCESS]: Built SQL String: `SELECT authors.id AS authors_id, authors.name AS authors_name, authors.email AS authors_email, authors.description AS authors_description, authors.longer_description AS authors_longer_description, authors.age AS authors_age, authors.weight AS authors_weight, authors.nickname AS authors_nickname, authors.updated_at AS authors_updated_at, articles.id AS articles_id, articles.author_id AS articles_author_id, articles.title AS articles_title, articles.content AS articles_content, articles.published AS articles_published, articles.created_at AS articles_created_at, articles.updated_at AS articles_updated_at, comments.id AS comments_id, comments.article_id AS comments_article_id, comments.content AS comments_content, comments.author_id AS comments_author_id, comments.created_at AS comments_created_at FROM authors INNER JOIN articles ON authors.id = articles.author_id INNER JOIN comments ON authors.id = comments.author_id;` [FunkCLI - INFO]: Testing the Optimized SQL Query String from SQL Function "s_test5" in "s_test2.php". [FunkCLI - SUCCESS]: The SQL Query String in SQL Function "s_test5" in "s_test2.php" was Successfully Validated with 0 Errors When Sending it Prepared to the local MySQL DBMS! [FunkCLI - INFO]: Attempting adding the entire Optimized SQL Array as the returned value in SQL Function "s_test5" in "s_test2.php"! [FunkCLI - SUCCESS]: SUCCESSFULLY COMPILED SQL Query to Optimized SQL in SQL Function "s_test5" in "funkphp/sql/s_test2.php". [FunkCLI - INFO]: IMPORTANT: Open it in an IDE and press CMD+S or CTRL+S to autoformat the SQL Handler File again!

Med följande kod (majoriteten av kommentarer borttagna):

function s_test5(&$c) // <authors,articles,comments> { $DX = [ '<CONFIG>' => [ '<QUERY_TYPE>' => 'SELECT', '<TABLES>' => ['authors', 'articles', 'comments'], '<HYDRATION_MODE>' => 'simple|advanced', '<HYDRATION_TYPE>' => 'array|object', '[SUBQUERIES]' => [ '[subquery_example_1]' => 'SELECT COUNT(*)', '[subquery_example_2]' => '(WHERE SELECT *)' ] ], 'FROM' => 'authors', 'JOINS_ON' => [ 'inner=articles,authors(id),articles(author_id)', 'inner=comments,authors(id),comments(author_id)', ], 'SELECT' => [ 'authors:id,name,email,description,longer_description,age,weight,nickname,updated_at', 'articles:id,author_id,title,content,published,created_at,updated_at', 'comments:id,article_id,content,author_id,created_at', ], 'WHERE' => '', 'GROUP BY' => '', 'HAVING' => '', 'ORDER BY' => '', 'LIMIT' => '', 'OFFSET' => '', '<HYDRATION>' => [], '<MATCHED_FIELDS>' => [ 'authors_id' => '', 'authors_name' => '', 'authors_email' => '', 'authors_description' => '', 'authors_longer_description' => '', 'authors_age' => '', 'authors_weight' => '', 'authors_nickname' => '', 'authors_updated_at' => '', 'articles_id' => '', 'articles_author_id' => '', 'articles_title' => '', 'articles_content' => '', 'articles_published' => '', 'articles_created_at' => '', 'articles_updated_at' => '', 'comments_id' => '', 'comments_article_id' => '', 'comments_content' => '', 'comments_author_id' => '', 'comments_created_at' => '' ], ]; return array( 'sql' => 'SELECT authors.id AS authors_id, authors.name AS authors_name, authors.email AS authors_email, authors.description AS authors_description, authors.longer_description AS authors_longer_description, authors.age AS authors_age, authors.weight AS authors_weight, authors.nickname AS authors_nickname, authors.updated_at AS authors_updated_at, articles.id AS articles_id, articles.author_id AS articles_author_id, articles.title AS articles_title, articles.content AS articles_content, articles.published AS articles_published, articles.created_at AS articles_created_at, articles.updated_at AS articles_updated_at, comments.id AS comments_id, comments.article_id AS comments_article_id, comments.content AS comments_content, comments.author_id AS comments_author_id, comments.created_at AS comments_created_at FROM authors INNER JOIN articles ON authors.id = articles.author_id INNER JOIN comments ON authors.id = comments.author_id;', 'hydrate' => array( 'mode' => 'simple', 'type' => 'array', 'key' => NULL, ), 'bparam' => '', 'fields' => array(), ); };

s_test2 => s_test5 - Exempel på CLI-genererad & kompilerad SELECT-fråga! (s_test2.php)

Som Du ser först i CLI:t så får du gott om information om problematik såväl som när något försöks och sedan om det lyckas eller inte, såväl som diverse varningar här och var. På grund av att filerna ändras direkt inuti IDE:t så uppmanas Du alltid efter sådana slags filmanipuleringar via CLI:t att öppna filen och spara den igen så att "Prettier" och/eller inbyggda Lintern(?) i IDE:t gör sitt att indentera kodraderna utifrån blocknivå.

När jag läste på mer om SQL så fick jag lära mig att exekveringsordningen av SQL-frågor faktiskt börjar med FROM och JOIN (när & där det är applicerbart) innan INSERT, DELETE, UPDATE eller i detta fall SELECT. Så $DX-variabeln följer den ordningen efter konfigurationsnyckeln "<CONFIG>" för att det ska logiskt gå att följa exekveringsordningen av en given SQL-fråga (vid SELECT främst).

Funktionen function cli_parse_joins_on_DFS($fromTb, $availableTableNames, $relationships){}; används för att ta fram vilka tabeller har vilka relationer med vilka andra tabeller. På tal om det: Om Du minns tables.php-filen där alla tabeller läggs till som en slags "Ground Truth" för alla CLI-kommandon när du vill arbeta med att så hade jag en nyckel där vid namn 'relationships' som jag först aldrig visste vad jag skulle ha till.

Sedan när jag blev tvungen att kunna erbjuda automatiska förslag på vilka tabeller som kan slås samman vid FROM+JOIN-stegen så fick jag förslag från Gemini 2.5 Flash Thinking att skapa något i stil med detta:

'relationships' => [ 'authors' => [ 'articles' => [ 'local_column' => 'id', 'foreign_column' => 'author_id', 'local_table' => 'authors', 'foreign_table' => 'articles', 'direction' => 'pk_to_fk', ], ], 'articles' => [ 'authors' => [ 'local_column' => 'author_id', 'foreign_column' => 'id', 'local_table' => 'articles', 'foreign_table' => 'authors', 'direction' => 'fk_to_pk', ], ], 'alones' => [], ],

Notera hur tabellen 'alones' (vars namn är 100 % med flit) är helt tom då den saknar faktiska relationer. En tabell har en eller flera relationer men en nackdel just nu med denna datastruktur är att den inte tillåter (men det går nog att möjliggöra med mer kod) att en tabell hänvisar till fler än en sekundärnyckel till en annan tabell.

Detta sistnämnda är dock dålig Databasstruktur anser jag: I princip om du skulle ha två olika sekundärnycklar från en tabell till sedan en och samma andra tabell så innebär det att du alltid måste stoppa in data åt två olika sekundärnycklar till samma tabell från en annan tabell? Vad händer då om du inte har data för tillfället till ena sekundärnyckeln men andra och du vill få övrig data instoppad så den inte går förlorad?!

Typexempel på dålig datamodellering(?) även om det enligt Gemini 2.5 Flash Thinking 100 % går att göra så här utan att MySQL skulle klaga på det. Faktumet att jag först inte ens trodde på det för jag bara "kände" att det kändes helt fel slags datastruktur tycker jag är ett gott tecken på mitt tänkande med normalformer och normalisering som det så (o)fint heter inom databasvärlden!

Så är JOINS_ON tänkt att fungera!
När du läser SELECT-nyckeln så ser det nog rätt så logiskt ut. Först har du tabellnamnet och sedan kolumnerna från den.

Alternativa skrivsätt per tabellnivå är följande (felmeddelande ges om tabellen och/eller någon enskild kolumn saknas från tabellen ifråga):

'SELECT' => [ 'authors:id,name', // Välj endast "id" och "name" 'articles!:id', // Välj alla kolumner men exkludera "id"-kolumnen 'comments',], // Välj alla kolumner från tabellen

Första tabellen så får du alltså endast kolumnerna id och name medan andra tabellen väljer alla kolumner samtidigt den exkluderar id-kolumnen. Tredje tabellen så väljs alla kolumner. Det går även att skriva in aggregeringsfunktioner direkt i SELECT-nyckeln vars alias kan hänvisas till i HAVING-nyckeln:

'SELECT' => [ 'authors:id,AVG(age)',], // Välj endast "id" och snittvärdet för "age"

Däremot om du skriver aggregatfunktioner i WHERE-nyckeln så blir det felmeddelande då det tydligen inte är tillåtet. Nycklarna GROUP BY och HAVING var riktigt kluriga även om HAVING i princip kunde återanvända en hel del från WHERE med tillägget av att tillåta aggregatfunktioner i uttrycken.

Så här ser egentligen JOINS_ON-nyckeln ut när du "schavottar" (eng. "scaffolding") för de tre tabellerna:

'FROM' => 'authors', 'JOINS_ON' => [ // Optional, make empty if not joining any tables! 'inner=articles,authors(id),articles(author_id)', 'inner=comments,authors(id),comments(author_id)', 'inner=articles,comments(article_id),articles(id)'],

Jämför med bilden nedan:

Notera ett problem nu i den "schavottade" JOINS_ON-nyckeln.

Du slår först samman authors med articles och sedan comments med authors men sedan articles med comments vilket du inte får göra. Däremot om du tar bort en av de inner=articles så blir allt fröjd igen. Detta är möjligt tack vare funktionen som använder Depth First Search-algoritmen (jag trodde först jag skulle behöva någon slags topologisk sortering istället) som nyttjar relationships-nyckeln i tables.php-filen för att veta vilka tabeller som kan slås samman och så fort ett "par" har slagits samman så får det inte upprepas.

Sedan blir det upp till Utvecklaren att välja vilka tabeller som bör slås ihop och i vilken ordning. Exempelvis följande genererade SQL-sträng:

SELECT authors.id AS authors_id, authors.name AS authors_name, authors.email AS authors_email, authors.description AS authors_description, authors.longer_description AS authors_longer_description, authors.age AS authors_age, authors.weight AS authors_weight, authors.nickname AS authors_nickname, authors.updated_at AS authors_updated_at, articles.id AS articles_id, articles.author_id AS articles_author_id, articles.title AS articles_title, articles.content AS articles_content, articles.published AS articles_published, articles.created_at AS articles_created_at, articles.updated_at AS articles_updated_at, comments.id AS comments_id, comments.article_id AS comments_article_id, comments.content AS comments_content, comments.author_id AS comments_author_id, comments.created_at AS comments_created_at FROM authors INNER JOIN comments ON authors.id = comments.author_id INNER JOIN articles ON comments.article_id = articles.id;

Fungerar otroligt nog när nedersta inner-strängelementet tas bort från JOINS_ON-nyckeln:

(Ja, du har 100 % rätt - faktumet att SQL-frågan är bara en lång rad är fruktansvärt för den som vill kunna lusläsa/granska den efter kompilering! Helt klart ett I-landsproblem att lösa senare!)

Några sista ord om JOINS_ON-nyckeln för denna gång är att den kanske upplevs mer komplex än vad den är tänkt att vara. En annan sak är att notera syntaxen för kolumnerna som används och hur det också reflekterar hur det är tänkt att lägga in tabellerna i ramverket:

authors(id),articles(author_id) authors(id),comments(author_id) comments(article_id),articles(id)

När en tabell läggs in så bör dess primärnyckel heta bara id medan alla sekundärnycklar bör heta i stil med entitetens singulära namn och sedan _id. Faktum är att när en tabell läggs in via en funktion som läser in en .sql-fil så lägger den till s för att göra namnet engelskt plural även om tabellnamnet inte är i det språket.

Detta saknas nu för just SQL-kompileringen!
Följande saknas än så länge i SQL-kompileringen:

  • Inre SQL-frågor (eng. Subqueries) stöds men då måste Utvecklaren manuellt lägga till det bundna datatypvärdet i 'bparam' och vilket validerat datafält i 'fields' som ska användas för varje ?-tecken i den ordning de förekommer i SQL-frågan i förhållande till 'fields'-arrayen.

  • Ingen datahydreringsstruktur finns ännu i 'hydrate'-nyckeln mer än vilken typ som ska skrivas i '<HYDRATION>' => [], och om data ska hydreras i form av objekt eller arrayer. Så det är nyckeln jag ska fixa härnäst. Får jag till det halvhyfsat likt att du bara kan skriva "authors=>articles=>comments" så tror jag att det ger att starkt mervärde. Det ser också bekant ut då likt med with-kedjandet i ORM-varianterna.

🎓 Vidareutbildning
Jag ska titta mer och jag har tittat på så kallde Parsers och dessa tycks börja med "lexers/tokenizing" där du vill kategorisera varje tecken likt du kategoriserar svenska ord i exempelvis olika ordklasser. Sedan när du har gjort det så kan du via din egen Parser bygga upp ett så kallt Abstrakt Strukturträd (eng. "AST") vilket du sedan bearbetar för att slutföra kompileringen.

Det finns några intressanta saker att överväga med tokenizers. Å ena sidan bör de vara kontextlösa genom att inte försöka göra mer än att bara kategorisera tecken. Å andra sidan bör de även inte tillåta fullständigt trasig text att ens få "tokeniseras" färdigt.

Klassikern är när den upptäcker att en sträng börjar via enkel- eller dubbelfnutt men den når slutet och ingen enkel- eller dubbelfnutt syns vilket betyder att en sträng aldrig egentligen "fullbordades" under tokeniseringssteget.

Men kontextlöst fungerar ju inte riktigt: Ska < kategoriseras som en operator, en öppningstagg eller kanske till och med ett okänt tecken? En viss nivå av domänspecificitet behövs till tokenisern utan att överdriva så att den blir prestandaeffektiv sett till tid och minnesanvändning.

När jag läste på en hel del om detta i samband med regex-galenskaperna jag själv har implementerat (och blivit lite bättre på regex - lookahead och lookaback är fortfarande kluriga vilket verkar vara nästan som att skriva in if/elseif/else-satser som en oneliner?!) så insåg varför SQL-frågan oftast börjar med nyckelordet SELECT/INSERT/UPDATE/DELETE (eller kanske systemkommando) fast exekveringsordningen för just en SELECT inte börjar med SELECT utan FROM och JOIN.

En bra start på en mycket simpel men tillräckligt användbar Parser vore just för hur rutter får skrivas innan de läggs i ruttfilen. Giltiga tecken är i princip: /^[a-zA-Z_\-0-9:/]+$/i. När "/:" skrivs så måste det följas av [a-zA-Z0-9]+ medan ingen ny ":" får skrivas. Så grammatiken är väldigt simpel, det vill säga, den själva faktiska "parsingen" som måste göras efter "tokeniseringen".

På återseende!

Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk

Webbutvecklingsdagbok - efter studierna | Del 1 av 3

Fram med valfri läskande dryck och goda tillbehör för nu blir det en rejäl uppdatering med det största fokuset på hobbyprojektet - det 99 % funktionsbaserade PHP-ramverket där resterande procenten är mysqli-objektet av pragmatiska skäl tills vidare - i och med rådande ledighetsmånader så det är mer eller mindre omöjligt att söka jobb och/eller uppdrag såväl som faktumet att jag har inget att komma med ännu (portfölj) tills lanseringen av hobbyprojektet.

🖥️ Jobb & Uppdrag
Inga nya jobb och/eller uppdrag erhållna. För ett par dagar sedan fick jag inbjudan på UpWork att gå med något slags internt talangteam inom "Swedish voiceover". Även om jag kommer att inom kort etablera mig med ljud- och videoklipp här och var på internet så är jag mil från att kunna vara någon svensk rösttalang. Så jag fick tacka nej till det erbjudandet.

🌐 Hobbyprojekt Del 1 av 3
📜 En mycket kort historisk tillbakablick
För numera drygt ett år sedan efter jag avslutat mitt examensarbete i den tvååriga distansutbildningen inom Webbutveckling med fokus på fullstack fast majoriteten av kurserna var mer backend snarare än frontend så hade jag fått försmak av "funktionell programmering" eller rättare sagt procedurbaserat paradigm snarare än objektorienterat (förkortat OO) paradigm.

Det är något mysigt med det procedurbaserade paradigmet där du mer tydligare kan se den data du arbetar med än att dölja alltid bakom (ärvda) klasser och objektinstanser där du inte alltid vet vad de innehåller oavsett hur mycket IDE-stöd du än får. Och när objekten i sig innehåller andra objekt och det hela är hårdtypat så blir krångligare för mig med så kallade "skill issues".

Kombinera sedan OO-paradigmet med inkorrekt bokstavsordning i det så kallade MVC-ramverket vilket egentligen borde vara CMV-ramverk eftersom du börjar med en rutt (eng. "Route") för att matcha mot någon unik resurs (eng. "URI") för att sedan antingen returnera resursen direkt (exempelvis en bild, ett ljudspår, ett videoklipp, eller kanske någon vanlig fil såsom JS, CSS, med mera) eller gå vidare till nästa steg i CMV.

Nästa steget är då naturligtvis data (eller "model") om du inte redan har returnerat något som ej behöver vidare behandling. Efter detta så kan du antingen returnera hämtad och/eller bearbetad data via JSON, XML eller annat önskvärt format eller så gå du vidare till det sista steget i CMV vilket då är "sidan" (eng. "page" eller "view").

Här kan du nu returnera en färdigkompilerad sida eller så kanske du måste kompilera någon som ej ännu har kompilerats. Nu har du genomgått alla steg i CMV-ramverket - eller vad jag kom att kalla för första versionen av numera FunkPHP - "RDP" eller "RouteDataPage". Det sistnämnda har så klart inte något som rullar på tungan precis.

Efter samtal med diverse LLM:er om bättre namn så ville jag först "FlowPHP" men det var redan upptaget och domänet var också upptaget. Så då när jag såg "Funk" så insåg jag också att "Funk" är en musikgenre såväl som förkortning av "Funktion" på svenska och när du säger det så kan det också låta som "func" eller engelskans "function" och jag är ju svensk så det hela blev perfekt på många nivåer och är ett slags "mentalt semantiskt påskägg" för icke-insatta att komma upptäcka på egen hand.

📜Version1️⃣: "RouteDataPage" (RDP)
Tar vi en titt då på första versionens mappstruktur så såg vi följande:

Mappar som skulle reflektera de viktiga stegen Route, Data & Page vars konfigurering skulle äga rum i dx_steps-mappen. Sistnämnda visade sig vara inte en så bra idé eftersom det blev lite bökigt att hålla koll på i vilken ordning saker och ting skedde/kördes i. På så vis bröt det mot det härliga med tydligt procedurbaserat paradigm.

(Psst! Ska vi vara tråkiga så går det att argumentera för att det mesta sker instruktion för instruktion med vissa undantag för flera instruktioner samtidigt beroende på processorarkitektur och så...)

Hursomhelst så blev det inte heller bättre när vi tittade på första versionen av indelningen av rutterna och deras prefixmotsvarande filer:

Vad som framgår i bilderna ovan är först och främst tre olika filer för tre olika saker: rutter, data och (eventuella) sidor. Tittar du på nedre bilden ovanför så ser du även hur mapparna data, pages och routes har sina egna filer för rutterna såväl som separata ruttfiler för själva middlewares för de slags rutterna! Snacka om mycket indelat för något som kunde ha varit så mycket bättre enhetligt!?🤔

Den nedre bilden från ovan visar dock en sak som är relativt nära nuvarande dagens version vilket är "routes_single_routes.php" (numera "routes.php") där varje rutt först är indelat på giltig HTTPS-metod (GET, POST, PUT & DELETE) och sedan på unika URI:er med stöd för dynamiska URI-segment tack vare kolon först efter varje snedstreck.

I och med denna första version med dessutom den jobbiga mappen dx_steps så blev det för mycket att göra för att bara få upp en enda simpel rutt till att börja med. Det var också faktumet att det behövde manuellt skrivas in i varje separata PHP-fil!!! (jösses!😱)

📜Version2️⃣: Förenklad RDP
Det hela förenklades nu genom att ta bort dx_steps-mappen och istället ha alla dessa steg i en och samma enstaka fil numera kallad "funkphp_start.php" och en och samma ruttfil kunde nu innehålla alla nödvändiga "RDP-delar" varje unika rutt inuti varje HTTPS-metod:

Bilden ovan är anpassad utifrån nuvarande filstruktur i vad som är numera den "slutgiltiga första versionen (version 3)". Med en rutt som kunde innehålla arraynycklar för vad som skulle köras och det spelade ingen roll hur du lade dessa arraynycklar för de kördes alltid i ordningen: middlewares -> handler -> data (om inte data redan returnerat) -> page.

Men allvarliga problem uppstod här:

  • Först och främst så betyder det att vid "GET/test" så körs alltså bara middlewares "auth" och "log". Men om Utvecklaren ville ha middlewares som körs innan redan på "/"-nivå då? Dessutom blev det också "påtvingat" att du måste nyttja arraynycklarna kallade "handler", "data", "page" fast du kanske inte ville det eller kanske ville kalla vissa för något annat? Var fanns friheten i det hela? Det hela höll på bli "ett ytterligare opinionsstarkt ramverk som påstås ha lösningen på allt!"🙃

  • Den huvudsakliga startfilen använde olika if-satser och höll sedan koll på och även den så kallade startfilen blev då i sin tur minst lika "påstridig" i hur och saker och ting borde köras vid varje inkommen HTTPS-förfrågan... Det var någonting här som inte riktigt fungerade som önskat och någonting drastiskt behövdes göras och det illa kvickt...!🤨

  • Trodde du att jag skulle skriva tre olika meningsbärande saker här likt diverse LLM:er gör? Nepp!

Hur skulle då dåvarande "förenklade RDP" förbättras/förenklas ytterligare för att maximera valfriheten för Utvecklaren samtidigt som vissa saker var opinionsstarka men ändå på ett sätt som gjorde det samtidigt relativt modulärt för hur Utvecklaren kunde välja att göra saker och ting på...?

Vad som behövdes var följande:

  • Möjligheten att välja själv vad som skulle köras i vilken ordning vid varje inkommen HTTPS-förfrågan.

  • Möjligheten att själv välja vilka arraynycklar som skulle användas för varje given rutt och även i vilken ordning de skulle köras.

  • Möjligheten att ha middlewares att samlas längs URI-segment så något som först låg på "/" kunde inkluderas och sedan köras innan något kördes på exempelvis "/test" eller till och med "/test/:id".

  • Möjligheten att kunna göra nästan hur som helst från start till slut samtidigt som en minimala "påstridighet" följdes med!

  • Till sist men inte minst: massor och då menar jag massor - av så kallade "medföljda batterier" för ingen gillar en ny batteridriven leksak utan medföljda batterier, eller hur?!😬

Nu blev det mycket att ta tag i!😮‍💨

(Fortsättning följer i nästa inlägg direkt efter detta!)

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk

Webbutvecklingsdagbok - efter studierna | Del 2 av 3

🌐 Hobbyprojekt Del 2 av 3
📜Version3️⃣: 🎷FunkPHP🐘
För att maximera möjligheterna för Utvecklaren samtidigt som det fanns en viss "påstridighet" i hur vissa saker och ting skulle göras så kom den enhetliga lösningen inspirerat från ordet "pipeline". Eller ta en titt på den nuvarande mappstrukturen vilket är vad som kommer att sjösättas i den första giltiga versionen av det 99 % funktionsbaserade hobby-PHP-ramverket:

Så här kan de olika yttre mapparna förklaras mycket kortfattat talat:

backups - Här - om så önskas - hamnar alla säkerhetskopior av filer när någon befintlig fil ändras så att innehåll försvinner eller om hela filen raderas exempelvis.

batteries - I denna kommer alla de så kallade "medföljda batterierna" att ligga vare sig det är olika middlewares för olika ändamål, eller "plugins" (inladdning av klassbibliotek för den som inte vill återuppfinna PHPMailer, PHPSpreadsheet, med mera på nytt i funktionell form!)

cli - Navigera in hit för att använda FunkCLI eller numera bara "funk" för vem orkar skriva "cli" hela tiden om du ändå redan är inne i mappen src/cli? Samma fil här anropas av REST API från gui-mappen tack vare Filesr-konfigurering i en .htaccess-fil i denna cli-mapp:

<Files "funk">SetHandler application/x-httpd-php</Files>

funkphp - Denna mapp läggs utanför public_html så att den hamnar på samma nivå eftersom denna inkluderas via index.php-startfilen i public_html dit alla HTTPS-förfrågningar skickas vidare om det inte är publika statiska filer inuti public_html som begärs som exempelvis public_html/css, public_html/js, public_html/fonts, public_html/images, public_html/osv.

gui - Tack vare framtida innehåll här så kommer det gå att göra fler saker visuellt och därmed svårare kunna göra fil direkt i filer för att snabbare "schavottera" så att säga!

public_html - Denna mapp ersätter den klassiska public_html-mappen (eller public enligt Laravel av någon outgrundlig anledning?!🙄) hos valfritt webbhotell som stödjer .htaccess-filer och PHP version 8.2 och uppåt.

schema - Här återfinns .sql-filer för att lägga till tabellerna i tables.php-filen så att relationer kan etableras så att inbyggd funktionalitet för högoptimerade SQL-förfrågningar och datahydrering kan äga rum såväl som valideringsgenerering.

template - En mystisk mapp vars namn egentligen är intetsägande på olika sätt och vis. Tanken är att valfria kodfiler läggs in här vilket sedan kan inkluderas vid "schavotterande" av olika slags filer. Exempelvis kanske du skapar en arraynyckel-fil till en viss rutt som du vill ska inkludera en viss "startkod" (tänk "code snippets") som automatiskt läggs in i början av den skapade arraynyckel-filens funktion.

test - Om Utvecklaren ändå anser att "100 % code coverage or it is all AI slop!" så finns mappen där tester kan läggas in och köras - under den lokala utvecklingen - med valfritt testbibliotek eller kanske till och med testramverk?! Jag har inte bevandrat mig inom testdriven utveckling så jag är för icke-insatt här!

Bli inte rädd nu när du ser denna PHP-array, men:

Vad du ser är "kärnan" i det hela för varje HTTPS-förfrågan. Jag hade en kortfattad diskussion om vad som skulle kallas för vad som körs efter alla funktioner (om de inte avbrutits innan via exit();!)

Jag var först inne på att kalla den för "exit" istället för "post-request" för jag tänkte på "exit" som i funktionen "exit" då jag lärde mig om "register_shutdown_function()" vilket låter dig köra en sista funktion efter att exit(); har anropats.

En sak du förmodligen reagerat på nu är, "Skämtar du...?! TRETTON separata pipeline-funktioner???" Här kommer vi då till det helt UNDERBARA med möjligheterna: Du kan - om du vill - bara lägga all logik du anser bör alltid köras oavsett i en och samma pl-fil ("pl" är förtkoningen av engelska ordet pipeline) och sedan så ser du bara en funktion som körs.

Hur du än väljer att göra så blir det en kompromiss på det ena eller andra viset! Och ja! Det finns dessutom en funktion som vilken funktion som helst kan anropa vilket då gör att när "post-request"vill köra sina pl-funktioner så skippas dem eftersom den först kollar om $c['req']['skip_post-request'] är sann medan standardvärde är falsk. MÖJ-LIG-HET & VAL-FRI-HET!!!🫡

function funk_skip_post_request(&$c) { $c['req']['skip_post-request'] = true;}

Om du granskat bilden ovan mer än vanligt så kanske du såg hur det finns pl-filer med "_v1" efter sig. Detta är för att tanken är att vid framtida versioner så kanske diverse medföljande pl-filer har uppdaterats men ändå kan återanvändas och detta gäller även för middlewares-filer då båda dessa "funktionsfiltyper" är tänkta att kunna återanvända emellan projekt som använder samma 99 % funktionsbaserade hobby-PHP-ramverk. Vad som troligen kommer skilja sig är de diverse funktionsfilerna inuti routes-mappen.

Just det... Vad hände med routes-mappen och dess routes.php-fil? Ta en titt själv:

Notera hur den har mapparna data och handler inuti sig medan mapparna sql och validation ligger utanför. De två inre mapparna har skapats via CLI-kommandona "php funk make:data test=>test" respektive "php funk make:handler v_test=>test".

Hade du däremot skrivit "php funk make:sql test=>test" eller "php funk make:validation v_test=>test" så hamnar filerna istället i de "påstridiga" mapparna sql respektive validation vilket alltid ligger i huvudmappen funkphp. Det icke-sagda resonemanget förrän nu är att dessa inte är tänkta att användas som arraynycklar för en given rutt!

Men JA! Du har helt rätt: på grund av hur VALFRITT det hela är så kan du hypotetiskt talat manuellt skapa mappen sql eller validation inti routes-mappen och sedan manuellt skapa funktionsfilerna och bara skriva in arraynycklarna manuellt för rutten:

'POST' => [ '/test' => [ 'middlewares' => ["auth", "log"], 'sql' => ['silly' => 'oh_why'] 'data' => ["test" => "test"], 'validation' => ['you' => 'do_this'] 'handler' => ["test" => "test"], 'page' => 'test', ], ],

Däremot kommer funk make:sql eller funk make:validation alltid per standardbeteende att skapa funktionsfilerna i mapparna utanför routes men du stoppas inte från att manuellt göra det hela. Kan du gissa förresten vad denna pl-funktion gör?:

12 => 'pl_run_matched_route_keys',

Exakt! Den kör igenom ruttnycklarna för en given rutt i den ordning de framgår:

<?php return function (&$c) { foreach ($c['req']['route_keys'] as $key => $_) { if (!is_string($key)) { $c['err']['PIPELINE']['REQUEST']['funk_run_matched_route_keys'][] = 'Route Key must be a String corresponding to the Folder where the Function File with corresponding Function Name would be inside of!'; return; } if (!isset($c['req']['route_keys'][$key])) { $c['err']['PIPELINE']['REQUEST']['funk_run_matched_route_keys'][] = 'Route Key `' . $key . '` NOT found for the Route `' . ($c['req']['route'] ?? '<No Route Matched>') . '`. Please check your Route Keys in `funkphp/routes/routes.php` for the Route `' . ($c['req']['method'] ?? '<No HTTP(S) Method Matched>') . ($c['req']['route'] ?? '<No Route Matched>') . '`!'; return; } $matchKey = $c['req']['route_keys'][$key]; $keyFolder = $key; $keyFile = ''; $keyFn = ''; if (is_string($matchKey)) { $keyFile = $matchKey; $keyFn = $matchKey; } elseif (is_array($matchKey)) { $keyFile = key($matchKey); $keyFn = $matchKey[$keyFile] ?? ''; } else { $c['err']['PIPELINE']['REQUEST']['funk_run_matched_route_keys'][] = 'Route Key `' . $key . '` must be a String or an Array with a Non-Empty String Value. No attempt to find a Route Key File was made!'; return; } if (isset($c['dispatchers'][$key][$keyFile])) { if (is_callable($c['dispatchers'][$key][$keyFile])) { $c['dispatchers'][$key][$keyFile]($c, $keyFn); } else { $c['err']['PIPELINE']['REQUEST']['funk_run_matched_route_keys'][] = 'Route Key `' . $key . '` File `' . $keyFile . '` is NOT a Callable Function. Please check your Route Key File in `funkphp/routes/routes.php` for the Route `' . ($c['req']['method'] ?? '<No HTTP(S) Method Matched>') . ($c['req']['route'] ?? '<No Route Matched>') . '`!'; return; } } else { $pathToInclude = ROOT_FOLDER . '/routes/' . $keyFolder . '/' . $keyFile . '.php'; if (!is_readable($pathToInclude)) { $c['err']['PIPELINE']['REQUEST']['funk_run_matched_route_keys'][] = 'Route Key `' . $key . '` File `' . $keyFile . '` does NOT EXIST in `funkphp/routes/' . $keyFolder . '/` Directory! Please check your Route Key File in `funkphp/routes/routes.php` for the Route `' . ($c['req']['method'] ?? '<No HTTP(S) Method Matched>') . ($c['req']['route'] ?? '<No Route Matched>') . '`!'; return; } $c['dispatchers'][$key][$keyFile] = include_once $pathToInclude; $c['dispatchers'][$key][$keyFile]($c, $keyFn); } } };

funkphp/request/pl_run_matched_route_keys

Faktum är att den funktionen fungerar mer eller mindre lika som funktionen som kör pipeline-funktionerna:

function funk_run_pipeline_request(&$c) { if ( isset($c['<ENTRY>']['pipeline']['request']) && is_array($c['<ENTRY>']['pipeline']['request']) && count($c['<ENTRY>']['pipeline']['request']) > 0 ) { $count = count($c['<ENTRY>']['pipeline']['request']); $c['req']['keep_running_pipeline'] = true; for ($i = 0; $i < $count; $i++) { if ($c['req']['keep_running_pipeline'] === false) { break; } $fnToRun = ""; $pipeValue = null; $current_pipe = $c['<ENTRY>']['pipeline']['request'][$i] ?? null; if ( $current_pipe === null || (!is_string($current_pipe) && !is_array($current_pipe)) ) { unset($c['<ENTRY>']['pipeline']['request'][$i]); $c['req']['number_of_deleted_pipeline']++; $c['err']['PIPELINE']['REQUEST']['funk_run_pipeline_request'][] = 'Pipeline Request Function at index ' . $i . ' is either NULL or NOT a Valid Data Type. It must be a String or An Associative Array Key with a Value! (Value can be null, but that is probably not useful in most cases)'; continue; } elseif (is_array($current_pipe)) { $fnToRun = key($current_pipe); $pipeValue = $current_pipe[$fnToRun] ?? null; $c['req']['current_passed_values']['pipeline']['request'][$fnToRun] = $current_pipe[$fnToRun] ?? null; $c['req']['current_passed_value']['pipeline'] = $current_pipe[$fnToRun] ?? null; } // "else" means it is a String so it has no value to store/pass on! else { $fnToRun = $current_pipe; } if (isset($c['dispatchers']['pipeline']['request'][$fnToRun])) { if (is_callable($c['dispatchers']['pipeline']['request'][$fnToRun])) { $runPipe = $c['dispatchers']['pipeline']['request'][$fnToRun]; $c['req']['current_pipeline_running'] = $current_pipe; $c['req']['number_of_ran_pipeline']++; $c['req']['next_pipeline_to_run'] = $c['<ENTRY>']['pipeline']['request'][$i + 1] ?? null; $runPipe($c, $pipeValue); } } else { $pipeDir = ROOT_FOLDER . '/pipeline/request/'; $pipeToRun = $pipeDir . $fnToRun . '.php'; if (file_exists($pipeToRun)) { $runPipe = include_once $pipeToRun; if (is_callable($runPipe)) { $c['req']['current_pipeline_running'] = $current_pipe; $c['req']['number_of_ran_pipeline']++; $c['req']['next_pipeline_to_run'] = $c['<ENTRY>']['pipeline']['request'][$i + 1] ?? null; $c['dispatchers']['pipeline']['request'][$fnToRun] = $runPipe; $runPipe($c, $pipeValue); } else { $c['err']['PIPELINE']['REQUEST']['funk_run_pipeline_request'][] = 'Pipeline Request Function (`' . $fnToRun . '`) at index ' . $i . ' is NOT CALLABLE for some reason. Each Function File should be in the style of: `<?php return function (&$c) { ... };`'; $c['req']['current_pipeline_running'] = null; } } else { $c['err']['PIPELINE']['REQUEST']['funk_run_pipeline_request'][] = 'Pipeline Request Function (`' . $fnToRun . '`) at index ' . $i . ' does NOT EXIST in `funkphp/pipeline/request/` Directory!'; $c['req']['current_pipeline_running'] = null; } } $c['req']['deleted_pipeline']['request'][] = $current_pipe; unset($c['<ENTRY>']['pipeline']['request'][$i]); unset($c['req']['current_passed_value']['pipeline']); $c['req']['number_of_deleted_pipeline']++; } $c['req']['current_pipeline_running'] = null; if ( isset($c['<ENTRY>']['pipeline']['request']) && is_array($c['<ENTRY>']['pipeline']['request']) && count($c['<ENTRY>']['pipeline']['request']) === 0 ) { $c['<ENTRY>']['pipeline']['request'] = null; } $c['req']['keep_running_pipeline'] = false; } else { $c['err']['MAYBE']['PIPELINE']['REQUEST']['funk_run_pipeline_request'][] = 'No Configured Pipeline Request Functions (`"<ENTRY>" => "pipeline"`) to run. Check the `[\'<ENTRY>\'][\'pipeline\']` Key in the Pipeline Configuration File `funkphp/pipeline/pipeline.php` File!'; } }

funkphp/_internals/functions/r_route_funs.php - fr.om. rad 36

(Fortsättning följer i nästa inlägg direkt efter detta!)

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming

Permalänk

Webbutvecklingsdagbok - efter studierna | Del 3 av 3

🌐 Hobbyprojekt Del 3 av 3
Du läste förhoppningsvis om hur CLI:t numera stöjder kommandon både via Terminalen såväl som i REST API? (via JSON) Detta är möjligt tack vare hur själva CLI-filen har omarbetats från grunden. Här följer därför några intressanta uttag från den.

Först så har du inledningen där massa konstanter först deklareras så att de konsekvent kan nyttjas. Intressant nog så verkar dessa bli globala för jag har inte behövt använda global-nyckelordet för att komma åt dem i funktioner vilket inkluderas senare på kodraderna 160-161.

Sedan ser du en konstant som heter "JSON_MODE" vilket är för att du behöver annorlunda information om du anropar via REST API med JSON än om du skriver direkt i terminalen. (eller egentligen inte eftersom gui-filen sköter detta men om någon vill använda den utanför den filen så finns stöd för det!) Anmärk även datastrukturen vid JSON-svar där type och message ingår...

<?php define('PROJECT_DIR', dirname(__DIR__)); define('BACKUPS_DIR', PROJECT_DIR . '/backups'); define('BATTERIES_DIR', PROJECT_DIR . '/batteries'); define('BATTERIES_MIDDLEWARES_DIR', BATTERIES_DIR . '/middlewares'); define('BATTERIES_PIPELINE_DIR', BATTERIES_DIR . '/pipeline'); define('BATTERIES_PIPELINE_REQUEST_DIR', BATTERIES_PIPELINE_DIR . '/request'); define('BATTERIES_PIPELINE_POST_REQUEST_DIR', BATTERIES_PIPELINE_DIR . '/post-request'); define('CLI_DIR', __DIR__); define('FUNKPHP_DIR', PROJECT_DIR . '/funkphp'); define('FUNKPHP_INTERNALS_DIR', FUNKPHP_DIR . '/_internals'); define('FUNKPHP_INTERNALS_COMPILED_DIR', FUNKPHP_INTERNALS_DIR . '/compiled'); define('FUNKPHP_CACHED_DIR', FUNKPHP_DIR . '/_cached'); define('FUNKPHP_CACHED_FILES_DIR', FUNKPHP_CACHED_DIR . '/files'); define('FUNKPHP_CACHED_JSON_DIR', FUNKPHP_CACHED_DIR . '/json'); define('FUNKPHP_CACHED_PAGES_DIR', FUNKPHP_CACHED_DIR . '/pages'); define('FUNKPHP_CONFIG_DIR', FUNKPHP_DIR . '/config'); define('FUNKPHP_CONFIG_BLOCKED_DIR', FUNKPHP_CONFIG_DIR . '/blocked'); define('FUNKPHP_CONFIG_VALID_DIR', FUNKPHP_CONFIG_DIR . '/valid'); define('FUNKPHP_MIDDLEWARES_DIR', FUNKPHP_DIR . '/middlewares'); define('FUNKPHP_PAGE_DIR', FUNKPHP_DIR . '/page'); define('FUNKPHP_PAGE_COMPLETE_DIR', FUNKPHP_PAGE_DIR . '/complete'); define('FUNKPHP_PAGE_COMPONENTS_DIR', FUNKPHP_PAGE_DIR . '/components'); define('FUNKPHP_PAGE_PARTIALS_DIR', FUNKPHP_PAGE_DIR . '/partials'); define('FUNKPHP_PIPELINE_DIR', FUNKPHP_DIR . '/pipeline'); define('FUNKPHP_PIPELINE_REQUEST_DIR', FUNKPHP_PIPELINE_DIR . '/request'); define('FUNKPHP_PIPELINE_POST_REQUEST_DIR', FUNKPHP_PIPELINE_DIR . '/post-request'); define('FUNKPHP_ROUTES_DIR', FUNKPHP_DIR . '/routes'); define('FUNKPHP_SQL_DIR', FUNKPHP_DIR . '/sql'); define('FUNKPHP_VALIDATION_DIR', FUNKPHP_DIR . '/validation'); define('GUI_DIR', PROJECT_DIR . '/gui'); define('PUBLIC_DIR', PROJECT_DIR . '/public_html'); define('SCHEMA_DIR', PROJECT_DIR . '/schema'); define('TEMPLATE_DIR', PROJECT_DIR . '/template'); define('TEST_DIR', PROJECT_DIR . '/test'); $JSON = false; $command = null; $arg1 = null; $arg2 = null; $arg3 = null; $arg4 = null; $arg5 = null; define('MSG_TYPE_ERROR', 'ERROR'); define('MSG_TYPE_SUCCESS', 'SUCCESS'); define('MSG_TYPE_INFO', 'INFO'); define('MSG_TYPE_WARNING', 'WARNING'); define('MSG_TYPE_IMPORTANT', 'IMPORTANT'); define('MSG_TYPE_SYNTAX_ERROR', 'SYNTAX_ERROR'); define('ANSI_RED', "\033[31m"); define('ANSI_GREEN', "\033[32m"); define('ANSI_YELLOW', "\033[33m"); define('ANSI_BLUE', "\033[34m"); define('ANSI_RESET', "\033[0m"); global $funk_response_messages; $funk_response_messages = []; if (php_sapi_name() === 'cli') { $command = $argv[1] ?? null; $arg1 = $argv[2] ?? null; $arg2 = $argv[3] ?? null; $arg3 = $argv[4] ?? null; $arg4 = $argv[5] ?? null; $arg5 = $argv[6] ?? null; $arg6 = $argv[7] ?? null; if (is_string($command)) { $command = strtolower($command); } if (is_string($arg1)) { $arg1 = strtolower($arg1); } if (is_string($arg2)) { $arg2 = strtolower($arg2); } if (is_string($arg3)) { $arg3 = strtolower($arg3); } if (is_string($arg4)) { $arg4 = strtolower($arg4); } if (is_string($arg5)) { $arg5 = strtolower($arg5); } if (is_string($arg6)) { $arg6 = strtolower($arg6); } } else { $JSON = true; header('Content-Type: application/json'); $payload = json_decode(file_get_contents('php://input'), true) ?? []; $command = $payload['command'] ?? null; $arg1 = $payload['arg1'] ?? null; $arg2 = $payload['arg2'] ?? null; $arg3 = $payload['arg3'] ?? null; $arg4 = $payload['arg4'] ?? null; $arg5 = $payload['arg5'] ?? null; $arg6 = $payload['arg6'] ?? null; if (is_string($command)) { $command = strtolower($command); } if (is_string($arg1)) { $arg1 = strtolower($arg1); } if (is_string($arg2)) { $arg2 = strtolower($arg2); } if (is_string($arg3)) { $arg3 = strtolower($arg3); } if (is_string($arg4)) { $arg4 = strtolower($arg4); } if (is_string($arg5)) { $arg5 = strtolower($arg5); } if (is_string($arg6)) { $arg6 = strtolower($arg6); } } define("JSON_MODE", $JSON); if (JSON_MODE) { if (!isset($_SERVER['REMOTE_ADDR']) || !in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', 'localhost'])) { http_response_code(403); echo json_encode(['error' => 'FunkCLI will ONLY accept using `127.0.0.1` or `localhost` as your Local IP!']); exit; } if (!isset($_SERVER['HTTP_ACCEPT']) || $_SERVER['HTTP_ACCEPT'] !== 'application/json') { http_response_code(406); echo json_encode(['error' => 'FunkCLI will ONLY accept `application/json` as value for the `Accept` Header!']); exit; } } if ( !file_exists(CLI_DIR . '/cli_funs.php') || !file_exists(CLI_DIR . '/cli_funs2.php') ) { if (JSON_MODE) { http_response_code(500); echo json_encode([ 'type' => 'ERROR', 'message' => 'FunkPHP CLI Function File(s) - either `cli_funs.php`, `cli_funs2.php` or both - NOT FOUND in `src/cli` Directory!' ]); exit; } else { echo "\033[31m[FunkCLI - ERROR]: FunkPHP CLI Function File(s) - either `cli_funs.php`, `cli_funs2.php` or both - NOT FOUND in `src/cli` Directory!\n\033[0m"; exit; } } include CLI_DIR . '/cli_funs.php'; include CLI_DIR . '/cli_funs2.php';

src/cli/funk fr.o.m. rad 1

Tack vare två funktioner så kan jag fortsätta använda de klassiska cli_err, cli_info, med flera, då dessa två funktioner samverkar för att returnera antingen ett ihopbyggt JSON-svar eller bara "ekar" ut direkt i terminaler:

function cli_output(string $type, string $message, bool $do_exit = false, int $exit_code = 0): void { global $funk_response_messages; $prefix = ''; $color = ''; switch ($type) { case MSG_TYPE_ERROR: $prefix = '[FunkCLI - ERROR]: '; $color = ANSI_RED; $exit_code = ($exit_code === 0) ? 1 : $exit_code; break; case MSG_TYPE_SYNTAX_ERROR: $prefix = '[FunkCLI - SYNTAX ERROR]: '; $color = ANSI_RED; $exit_code = ($exit_code === 0) ? 1 : $exit_code; break; case MSG_TYPE_SUCCESS: $prefix = '[FunkCLI - SUCCESS]: '; $color = ANSI_GREEN; break; case MSG_TYPE_INFO: $prefix = '[FunkCLI - INFO]: '; $color = ANSI_BLUE; break; case MSG_TYPE_WARNING: $prefix = '[FunkCLI - WARNING]: '; $color = ANSI_YELLOW; break; case MSG_TYPE_IMPORTANT: $prefix = '[FunkCLI - IMPORTANT]: '; $color = ANSI_YELLOW; break; default: $type = 'UNKOWN'; $prefix = '[FunkCLI - UNKOWN MESSAGE TYPE]: '; $color = ANSI_RESET; break; } if (defined('JSON_MODE') && JSON_MODE) { $funk_response_messages[] = [ 'type' => $type, 'message' => $message ]; if ($do_exit) { cli_send_json_response(); } } else { echo $color . $prefix . $message . ANSI_RESET . "\n"; if ($do_exit) { exit($exit_code); } } } function cli_send_json_response(): void { global $funk_response_messages; $overall_json_status = 'Success'; foreach ($funk_response_messages as $msg) { if ($msg['type'] === MSG_TYPE_ERROR || $msg['type'] === MSG_TYPE_SYNTAX_ERROR) { $overall_json_status = 'Error'; break; } } if ($overall_json_status === 'Success') { http_response_code(200); foreach ($funk_response_messages as $msg) { if ($msg['type'] === MSG_TYPE_WARNING) { $overall_json_status .= ' with Warning(s)'; break; } } } else { http_response_code(400); foreach ($funk_response_messages as $msg) { if ($msg['type'] === MSG_TYPE_INFO) { $overall_json_status .= ' with (Important) Info'; break; } } } $response = [ 'status' => $overall_json_status, 'messages' => $funk_response_messages, 'data' => [] ]; echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); exit(); }

src/cli/cli_funs.php kodrader 5-99

En mycket viktig funktion jag blev tipsad av Gemini 2.5 Flash av är följande om "atomisk filskrivning":

function cli_crud_folder_php_file_atomic_write($fileContent, $file_path) { $tempFilePath = $file_path . '.tmp'; if (file_put_contents($tempFilePath, $fileContent) === false) { cli_err_without_exit('FAILED to Write Content to Temporary File `' . $tempFilePath . '`!'); return false; } if (!rename($tempFilePath, $file_path)) { @unlink($tempFilePath); cli_err_without_exit('FAILED to Rename Temporary File `' . $tempFilePath . '` back to Correct File Path `' . $file_path . '`!'); return false; } return true; }

src/cli/funs.php kodrader 758-775

Den kommer jag att konsekvent implementera i alla funktionre som vill skriva ut till filer eftersom jag vill ju inte råka misslyckas skriva till en fil och få den förstörd utan snarare en temporär fil då som förstörs om den misslyckas döpa om filen som skulle egentligen "skrivas över".

Detta är återigen en av många små detaljer som jag aldrig riktigt lärde mig under Webbutvecklingsprogrammets gång utan fått lära mig i efterhand genom "samtal med statistiska internet" (LLM:er så att säga).

Avslutningsvis för denna gång i hobbyprojektet så vill jag introducera en funktion som varit guld värd och vilket även symboliserar de drygt tre månaderna av till och från "kodslit" med detta "100 % Human Slop hobby-PHP-ramverk" med fokus på procedurbaserat paradigm och även gett mig ny syn på konceptet "rena funktioner". (eng. pure functions)

function cli_folder_and_php_file_status($folder, $file) { if (is_string($folder) && str_starts_with(trim($folder), "/")) { $folder = substr(trim($folder), 1); } if (!isset($folder) || !is_string($folder) || empty($folder) || !preg_match('/^[a-z_][a-z_0-9\/-]*$/i', $folder)) { cli_err_without_exit('[cli_folder_and_php_file_status()]: $folder must be A Valid Non-Empty String! (whitespace is NOT allowed)'); cli_info('[cli_folder_and_php_file_status()]: Use the following Directory Syntax (Regex):`[a-z_][a-z_0-9\/-]*)`! (you do NOT need to add a leading slash `/` to the string)'); } if (!isset($file) || !is_string($file) || empty($file) || !preg_match('/^[a-z_][a-z_0-9\.]*$/i', $file)) { cli_err_without_exit('[cli_folder_and_php_file_status()]: $file must be A Valid Non-Empty String! (whitespace is NOT allowed)'); cli_info('[cli_folder_and_php_file_status()]: Use the following File Syntax (Regex):`[a-z_][a-z_0-9\.]*)`! (you do NOT need to add a leading slash `/` to the string and NOT `.php` File Extension)'); } $folder = trim($folder); $providedFolder = $folder; $file = trim($file); $filename = '<UNKNOWN>'; $singleFolder = '<UNKNOWN>'; if (str_ends_with($folder, '/')) { $folder = rtrim($folder, '/'); } if (!str_ends_with($file, '.php')) { $file .= '.php'; } if (str_starts_with($file, '/')) { $file = ltrim($file, '/'); } $folder = PROJECT_DIR . '/' . $folder; $singleFolder = basename($folder); $filename = $file; $file = $folder . '/' . $file; $fnRegex = '/^function\s+([a-zA-Z_][a-zA-Z0-9_]*)\(&\$[^)]*\)(.*?^};)?$/ims'; $dxRegex = '/\$DX\s*=\s*\[\s*\'.*?];$/ims'; $returnRegex = '/return\s*array\(.*?\);$\n/ims'; $returnFnRegex = '/^(?:(<\?php\s*))?(return function)\s*\(&\$c\s*.+$.*?^};/ims'; $fns = null; $fileRaw = null; $fileReturnRaw = null; if (is_file($file) && is_readable($file)) { $fileCnt = file_get_contents($file); if (!$fileCnt) { cli_warning_without_exit('[cli_folder_and_php_file_status()]: Could NOT Read the File `' . $file . '` when it SHOULD have been Readable. This means that Named Functions, their $DX and/or Return arrays(), OR Anonymous Function Files CANNOT be retrieved for use!'); } else { $fileRaw = $fileCnt; if (preg_match($returnFnRegex, $fileRaw, $fileReturnMatch)) { $fileReturnRaw = $fileReturnMatch[0] ?? null; } else { cli_warning_without_exit('[cli_folder_and_php_file_status()]: Could NOT find the Expected Anoynmous `return function` in the File `' . $file . '` when it SHOULD have been Found. This means it will NOT be possible to add any new Functions to this File (unless it is a Single Anonymous Function File) since it needs that matched part to add new functions from. This is due to the Regex: `/^(?:(<\?php\s*))?(return function)\s*\(&\$c\s*.+$.*?^};/ims` that cannot match `return function(&$c){};`!'); } if (preg_match_all($fnRegex, $fileCnt, $fnsMatches)) { foreach ($fnsMatches[1] as $idx => $fn) { $fns[$fn] = [ 'fn_raw' => $fnsMatches[0][$idx] ?? null, 'dx_raw' => null, 'return_raw' => null ]; if (preg_match($dxRegex, $fnsMatches[0][$idx], $dxMatch)) { $fns[$fn]['dx_raw'] = $dxMatch[0] ?? null; } if (preg_match($returnRegex, $fnsMatches[0][$idx], $returnMatch)) { $fns[$fn]['return_raw'] = $returnMatch[0] ?? null; } } } } } return [ 'folder_provided_path' => $providedFolder ?? null, 'folder_name' => $singleFolder ?? null, 'folder_path' => ((is_string($folder) && is_dir($folder) && is_readable($folder) && is_writable($folder)) ? $folder : null), 'folder_exists' => is_dir($folder), 'folder_readable' => is_readable($folder), 'folder_writable' => is_writable($folder), 'file_name' => $filename, 'file_path' => ((is_file($file) && is_readable($file) && is_writable($file)) ? $file : null), 'file_exists' => is_file($file), 'file_readable' => is_readable($file), 'file_writable' => is_writable($file), 'functions' => (isset($fns) ? $fns : []), 'file_raw' => ['entire' => $fileRaw ?? null, 'return function' => $fileReturnRaw ?? null], ]; }

src/cli/cli_funs.php kodrader 355-445

Funktionen handlar kort och gott om att nyttja regex för att sedan - och alltid - returnera huruvida en filsökväg existerar, om den innehåller anonym funktion och/eller diverse definierade funktioner och om den innehåller definierade funktioner har de då $DX med motsvarande return array() del då?

Så kallade $DX med motsvarande return array() del är endast tillämpbart för sql- och validation-filerna då dessa går att kompilera med hjälp av "php funk compile:sql|validation fil=>funktionsnamn". När nya filer och/eller funktioner ska skapas så blir det oerhört mycket enklare att ersätta befintliga textdelar för att sedan skriva ut ny fil med hjälp av hjälpfunktionen cli_crud_folder_php_file_atomic_write();.

🎓 Vidareutbildning
Vad är det då jag påstår att jag har lärt mig gällande "rena funktioner"? Jo, vad "ren" behöver det inte nödvändigtvis betyda att du bara returnerar en enda sak för att på så vis göra funktionen så enkel som möjlig för att den bara hanterar en enda enstaka sak. Det "rena" som jag tolkar det hela i skrivande stund är att den ska bete sig likadant förutsatt att inmatningsdata och även externa data den vill arbeta med (exempelvis filer) är likadana.

Anta att en fil skulle hinna försvinna precis när den ska försöka läsa in en fil, då kommer den att returnera att filen inte finns och det går att argumentera för att det inte blev en "bieffekt" i det hela. När det gäller "bieffekter" i "rena" funktioner så tolkar jag dem i skrivande stund att det handlar mer om statistiska saker (exempelvis returnera slumpartad data) och/eller att den CRUD:ar annan data utan att det i sig returneras så att du då inte riktigt "vet" eller får "återkopplin" om att det har inträffat.

En metadiskussion om "rena funktioner" är det hela semantiska i det: Vad menas med "ren"? Ta ett helt annat koncept inom utveckling "designmönster". När jag först hörde om dessa tidigt i min webbutvecklingsutbildning så fick jag ett väldigt "fyrkantigt" synsätt på dem som att om du använder ett designmönster så måste det användas överallt lite som att tvinga alla problemlösningar till att passa ditt verktyg istället för att hitta verktygen (plural(!)) som passar till att lösa problemen ifråga.

Många ord är "arbriträra" i sina betydelser - enligt mig - när det väl kommer till praktisk tillämpning av dem. Visst, "ren funktion" och ett givet "designmönster", men rita inte in dig bara för det in i ett hörn om att deras definitioner är lika inflexibla som Laravels förmåga att tillåta public_html/index.php istället nuvarande public/index.php === "🔥💉"!🤪

Se MÖJ-LIG-HETERNA och bejaka VAL-FRI-HETEN säger jag som mina sista ord för dessa tre förhoppningsvis mustiga kodblogginläggs gång!😎

På återseende!

Mvh,
WKF.
---------
✔️HT2022-VT2024 TWEUG Webbutvecklings-programmet 120hp (distans)

Visa signatur

(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming