Arkiv september 2019

Huvudet i kaklet

Visst heter det handen i kaklet, men man kanske inte alltid är så noga när man kör huvudet först mot en vägg. Min vägg är att få till ett smidigt sätt att lägga till en recension för en bok. Mitt dilemma är att jag har en databas som är beroende av att användare lägger till böcker. Så när en användare ska lägga till en recension så behöver användaren också lägga till en bok, och förlag, och förlagets hemvist, samt författare så klart. Det är så mycket som krävs av användaren. Detta kräver jag i den nuvarande versionen av Bokarenan, och kommer också göra det i den kommande versionen. I tredje versionen kan vi hoppas på att jag fått till en bra crawler som kan fixa det åt användarna. Men nu till den version jag för närvarande jobbar på.

Jag använder mig av Symfonys olika finesser för att göra mitt jobb enklare. Och visst har det gått väldigt smidigt att få till funktioner för att skapa, uppdatera och ta bort olika entiteter. Som jag skrev om i fredags så jobbar jag nu med att försöka få till ett enda formulär där användaren kan lägga till samtliga entiteter som är relevanta när en recension skapas. Jag har därför kombinerat olika färdiga formulär som Symfony skapat för respektive entitet. Slutresultatet från det är inte vackert eller särskilt användbart. Jag kommer försöka forma det så att det blir användbart, men jag börjar misstänka att allt mitt hoppande mellan olika guider skapar kunskapsluckor. Risken är att jag börjar förbise viktiga detaljer, eller kanske skapar en applikation som är svår att underhålla.

Som jag försökt illustrera ovan, jag har lite svårt att sluta cirkeln, och när jag kommer av mig kan det lätt bli att jag gör en röd räv, eller en egen implementering som ingen annan förstår. Inte ens jag själv om 6 månader. Jag ska försöka använda Symfony och få till formuläret, men jag kommer följa en utökad handledning i Symfony så att min lata hjärna inte hoppar över några viktiga detaljer.


Flerformulärsfredag

Denna vecka har gått på temat flerformlär: det att kombinera flera formulär till ett. Och det har fungerat, på sätt och vis. Jag vill att man ska kunna lägga till alla detaljer när man ska skriva en recension för en bok. Då kommer alla olika entiteter blandas in: författare, förlag, bok och recension. Det är bara det att en bok kan ju ha flera författare, och som formuläret nu renderas kan man bara lägga till en författare. Användaren ska kunna lägga till fler fält om det rör sig om flera författare. I tidigare version av Bokarenan så renderades cirka sju fält, men bara ett fält visades. Användaren kunde klicka på en knapp för att lägga till ett fält (upp till max sju fält). Detta kanske jag kommer använda, men det är inte så stiligt. Fördelen är att detta kan användas utan att åberopa JavaScript. Det hela kan skötas med CSS. Kan bli att jag återvänder till den lösningen, men först ska jag försöka få det hela att fungera med JavaScript, för att på ett dynamiskt sätt kunna lägga till så många författare man själv önskar.

Det jag tycker funkat bra denna veckan är att flytta över logik från controller till en hjälp-klass som jag kallar EntityBasket. Denna klass är uppbyggd för att kunna plocka upp alla entiteter så som de preciserats i en formulärklass jag kallar EntityBasketType. EntyiBasket ges formulärdata när den initialiseras och kommer med följande metoder:

Samtliga metoder hämtar alltså en entitet och varje metod bygger på att de kallar metoden fetchEntity som ser ut såhär:

Det innebär att jag slipper upprepa kod, och det funkar helt okej. Det som återstår efter att jag fått formulär med att lägga till flera författare att funka är att interagera med databasen.

Så vill jag avsluta denna vecka och välkomna helgen med att konstatera att det inte blir många knop i huvudet när jag får så lite sömn. Tvillingarna kommer till ro ganska sent på kvällen och tillbringar gärna halva natten på mitt bröst. Till tröst har jag haft några riktigt läckra upptäckter i Elite Dangerous. Jag har äntligen blivit först att upptäcka en planet, men det finns ju miljarder av solsystem så någon gång skulle det ju ske. Men sen så upptäckte jag en riktigt konstig klippformation på planeten. Det såg ut som en gigantisk blomma och runt omkring den fanns ”organiska mineraler”. Jag verkar ha upptäckt något utomjordiskt. Om jag landsatte min rover och körde nära blomman så slutade viss elektronik i rovern att funka. Det var lite skrämmande och jag stack så snart jag hade tagit några prover. Jag kommer bege mig tillbaka dit igen i helgen och se om jag får fler prover med mig.

Trevlig helg, allihop!


getData funkade!

Från föregående inlägg så undrade jag om det rätta sättet att komma åt rätt värden från formuläret var att använda Request-objektet eller Form-objektet. I andra projekt hade jag använt ”superglobala” variabler för att komma åt formulär-värden. Då hade jag använt $__POST[”book_name”]. I Symfony finns dessa värden i Request-objektet. Eller så var i alla fall min uppfattning. Men jag upptäckte att jag inte fick det resultat jag väntade för jag fick aldrig värdet från book_name. Därför blev det att jag vände mig till googling:

Det blev helt enkelt ganska många fönster när jag försökte hitta svar på mina funderingar. Jag kollade i Symfonys guider, dess github-sida, PHP:s officiella sida, och så klart Stack Overflow. Jag fick inte riktigt svar på varför det inte funkade att använda Request-objektet. Men däremot fann jag lösningen! Jag kunde använda Form-objektet. Genom $form->getData() så kunde jag extrahera ett bok-objekt. Och därmed kunde jag få detta enkla resultat och jag kände mig så lättad:

Book is set. It's title is Testing.


Fortsätter med det svåra

Som jag lovade i fredags skulle jag presentera resultatet av att kombinera flera olika formulär. Här är det visuella resultatet:

Inte vackert någonstans, och inte intuitivt att använda. Men allt sådant kommer senare. Först ska alla grundläggande funktioner finnas på plats. Det som står närmast är att fånga upp alla aktuella komponenter och göra relevanta saker med dem. Just nu har jag problem att hitta åt rätt värden i det Request-objekt som skapas. Kanske finns lösningen i att använda Form-objektet istället? Jag får återkomma.

Lycka till denna vecka alla ni måndagsälskare!


…göra det svåra

Jag har på ett ganska enkelt sätt kunna använda Symfonys console maker för att skapa CRUD-funktionalitet genom webbläsaren. Respektive entitet har getts ett eget User Interface för att kunna interagera med det. Nu kan användare genom webbläsaren se vilka entiteter som finns av en viss typ, skapa nya eller uppdatera dem. Men för tillfället finns respektive entitet på sin egen sida, alltså inte på en enda gemensam sida. Det jag vill uppnå är vad jag redan har åstadkommit i min gamla version av Bokarenan. Se bilden nedan:

Som framgår av bilden så har respektive entitet samlats under en rubrik/knapp. Det som händer när man trycker på ”Lägg till” är att applikationen kommer söka efter respektive entitet i databasen och lägga till den om den inte redan finns.

Nu ska jag däremot samla Symfony-entiteter i ett formulär. Och som jag redan har uttryckt så tycker jag att de guider som Symfony har är toppen. Självklart finns det en guide som kan hjälpa mig här: How to embed forms och How to embed a collection of forms. På måndag ser jag fram emot att kunna presentera resultatet.

Och nu blir det helg. Jag fortsätter att utforska rymden i Elite Dangerous. För närvarande kör jag många frakt-uppdrag, och förhoppningsvis kan jag snart skaffa mig ett nytt skepp som jag kan anpassa för gruvverksamhet. Jag både gillar spelet och tycker det är tråkigt. Det är toppen att spela och lyssna på en podcast.

Trevlig helg!


Gör det enkla

När jag skulle börja göra ett navigationsfält för Bokarenan med hjälp av Twig så hamnar jag lite vilse. Jag vill nämligen ha ett navigationsfält med huvudsidorna för applikationen synliga med indikation för den sida som för tillfället visas. Här har jag tänkt mig att texten kommer göras fetare och bakgrunden något mörkare. I övrigt kommer navigationsfältet vara likadant överallt på sidan. Navigationsfältet kommer därför återanvändas överallt genom att det inkluderas i huvudmallen, av vilka övriga mallar kommer bygga vidare på.

Tidigare har jag använt enkel PHP för att göra en kontroll efter vad en variabel har för värde. I navigationsfältet hade jag om-kontroller för varje sak i fältet för att kolla om variabeln överensstämde med kontroll-värdet.
<div class="<?php if ($page == "start") {echo ' active';}?>">

Det förutsätter dock att variabeln har ett värde som är unikt för varje sida, varje sida måste därmed sätta variabeln före det att koden för navigationsfältet inkluderades i filen:
$page = "start";
include($path_navbar);

Det funkar okej, men är något av spagetti för mig, eftersom jag inte skickar variabeln någonstans utan förutsätter att den inkluderade koden har rätt variabelnamn för att hitta rätt.

När jag nu använder mig av Twig-mallar för att generera min presentation av applikationen så behöver jag ändra logiken något. Jag vill ju att respektive sida ska kunna anmäla sig som den aktuella sidan och huvudmallen ska därmed reflektera detta i navigationsfältet. Jag googlade lite och blev lite skrämd av svaren. Någon styrde det genom att skicka en variabel till den aktuella sidan genom en kontroller. Om den personen hade flera olika kontroller som sedan förlängde en ursprungskontroll med den funktionen eller om samma logik fick användas om på flera olika ställen framgick inte. Visst är det ju så att kontroller är det som styr vad som presenteras när, men jag kände mig inte helt bekväm med att för tillfället använda kontroller för något som jag trodde var ganska enkelt.

Min lösning för närvarande blev istället att huvudmallen har block för respektive navigationslänk. En sida kan därmed ange vilket block i huvudmallen som ska indikeras som aktiv.
Huvudmallen:
<li class="{% block navHome %}{% endblock %}"><a href="/">Hem</a></li>

Undermallen:
{% block navHome %}active{% endblock %}

Det får jobbet gjort. Men jag är inte helt nöjd med det. Det som är bra med detta är att jag fått bort om-kontroller och att jag inte behöver oroa mig över en vilsen variabel. Det som jag inte är nöjd med är att jag kommer behöva hoppa mellan två olika mallar för att hålla koll på alla olika block.


Att logga in

Jag fortsätter följa guiden från Symfony för hur man sätter upp ett inloggningssystem. Det finns script redo att hjälpa en att komma igång. Med ett enkelt kommando: php bin/console make:auth, så genereras/uppdateras en konfigurationsfil, en kontroller, en autentiserare och HTML-fil för login-sidan. När jag kollar på HTML-filen så finns där ett inloggningsformulär med POST-metod. Detta är jag van vid. Dock så finns det inte angivet vad som ska vara mottagare för formuläret. Det brukar anges av action="". Men detta saknas i HTML-filen där det i formulärtaggen helt enkelt står: <form method="post"> . Istället läser jag: ”When you submit the form, the LoginFormAuthenticator will intercept the request”. Om jag förstår detta rätt så innebär det att formuläret inte behöver en mottagare. Istället kommer denna autentiserare ligga och lyssna efter formulärposter från login-sidan. För att få svar på detta så kan jag pröva mig fram. Jag prövar att registrera en ny användare, et voilà: det funkar*.

* Jo, det funkar som jag föreställde mig, men jag behövde göra lite justeringar då det automatisk genererade registreringsformuläret saknade ett fält för epost.

Hur justerar man då de automatiskt genererade formulären? Det går ju att göra som jag gjorde först: ändra i Twig-filen (det vill säga det som blir HTML-kod). Det var ganska enkelt att lägga till ett fält och få det presenterat för sig. Kruxet är bara det att det fältet ska snappas upp av en kontroller. Det jag behövde göra var att gå in i App\Form\RegistrationFormType och lägga till ett fält i en FormBuilder med funktionen:
->add('email', EmailType::class)
Och därmed var min lycka gjord.

När jag nu fått till inloggningsfunktionen så fortsätter jag med att lägga till entiteter med Symfonys CLI (Command Line Interface), eller vad de kallar för maker bundle. CLI:t gör det hela pinsamt enkelt att lägga till entiteter. Jag får hjälp av det att bestämma fält och typ samt relationer, och då genereras/uppdateras entitets- och förvarar-klasser med rätt information.


Dokumentation som är bra

Jag anpassar för närvarande Bokarenan till att nyttja Symfonys olika komponenter. Det är åtminstone planen men det blir mycket läsande. Jag är ute på nya marker när jag nu utforskar ett framework utan en bok som grund. Men det gör ingenting! Jag tycker dokumentationen är utmärkt. Symfony har guider för att komma igång med grunderna, för att starta med Unit-tester och för att sätta upp ett inloggningssystem. Fler finns det, men detta är så många som jag har gjort just nu. För närvarande går jag igenom Security-guiden. Eller, jag höll på med det, tills den föreslog att jag skulle gå igenom guiden för att sätta upp en inloggningssida. Det blir lite som att hoppa ned i hål efter hål, och hoppas man kommer tillbaka någon gång.

Den här veckan har det gått långsamt att programmera. Det har kanske blivit att jag funnit en timme per dag att programmera eller läsa om programmering. Anledningen: bebisarna växer och behöver mer stimulans, och jag har känt ett ökat behov av att träna mer. Det blir därför fler promenader och ett antal timmar på gymmet under veckan. Men jag känner mig nöjd med programmeringen. Jag är i huvudsak pappa, och programmering är min hobby. Så länge som jag hittar någon tid att sitta ned och programmera så är jag nöjd.

Det jag kommer göra till nästa uppdatering här på bloggen är att sätta upp en enkel front-end och lägga till entiteter och kontroller för att hantera böcker.

Sen så är det ju självklart fredag igen, och med helgen så kommer mina enkla nöjestips. Jag är rätt så bevandrad nu, i och med alla barnvagnspromenader. Och sen en månad tillbaka så ser jag fram emot mina promenader, för då kan jag lyssna på podcasts. Och en av mina favoriter just nu är Linear Digressions. De består av en duo – en webbutvecklare och en datavetare som diskuterar olika tillämpningar av machine learning. Det jag älskar med denna podd är att jag som bara känner till ämnet hyfsat ytligt kan följa med. Och det är alltid trevligt att lyssna på denna duo som har sån bra dynamik sinsemellan.

Så slår jag också ännu en gång ett slag för Elite: Dangerous. Den gångna veckan har jag somnat ett par gånger framför det när jag försökt spela när bebisar varit lugna. Spelet kanske låter något tråkigt nu, men det är mest för att jag varit något utmattad vid slutet av dagen.

Trevlig helg, allihop!


Jag testar och experimenterar

Det råkade bli testning inkluderat, som jag beskrev i mitt förra inlägg. Jag har inte mycket erfarenhet av att testa, men jag hade några enklare JUnit-tester när jag höll på med Java, och efter att ha mixtrat lite med PHPUnit så skulle jag påstå att jag uppnått samma låga kompetensnivå i det.

Såsom jag jobbar nu blir det att jag lägger till ett test och sedan den metod som testet kollar. Jag började med att testa att repository gör det den ska, men det kändes något onödigt. Anledningen: jag vill testa min kod, inte någon annans. Och med tanke på att Symfony kommer med Doctrine så fanns det egentligen inget där att testa för mig förrän jag inkluderade mina egna funktioner. Så de första stegen var att se lite som uppvärmning.

Snart så inkluderade jag ändå egna metoder, och mycket riktigt så hade jag skrivit test för dem. En sådan metod var att repository skulle söka med så kallade wildcards. Enligt testet så funkar det galant.

Testen startar jag från konsolen med utgångspunkt från mitt projekts mapp med instruktionen ./bin/phpunit, och som svar får jag OK (5 tests, 5 assertions). Som ni kan se är jag fortfarande i startgroparna, men det känns bra i magen när man ser testen köras och kan ändra ett fel till ett rätt.

Något som är helt nytt för mig är att testa att Controller presenterar korrekt sida till klienter. Symfony Docs är mig behjälplig och med instruktioner där ifrån så har jag mitt första test klart:
public function testShowHome() {        
$client = static::createClient();
    $client->request('GET', '/');
    $this->assertEquals(200, 

$client->getResponse()->getStatusCode());    
}

Testet kollar att HTTP-statuskoden är 200, vilket vill säga att korrekt sida hittats och levererats till klienten. I detta exempel så kollar testen helt enkelt om index-sidan hittas.


Att hantera innehåll

Helgen är slut och en ny vecka är påbörjad. Denna helg kom jag en bit ut i rymden i Elite: Dangerous. Det är helt fantastiskt hur omfattande det spelet är. Jag gillar att sugas in i det, men det går rätt långsamt. Jag vet inte riktigt vad jag tycker om utforskandet. Där handlar det om att först skicka ut en energipuls för att säkerställa att det finns himlakroppar i ett solsystem att upptäcka. När det är gjort så öppnar man ett fönster där man justerar ”radiofrekvensen” för att ställa in rätt kanal och därefter zooma in mot en himlakroppindikation. Det är bara att kanalen verkar glida något så man måste vara alert för att bibehålla rätt kanal. Jag får fortsätta med detta till nästa helg så kanske det hela klickar för mig.

Denna vecka fortsätter jag med Bokarenan. Innehållet till Bokarenan tänker hanteras av en databas. Databasen innehåller data om bland annat böcker, författare, användare och användares listor och recensioner.

I en tidigare iteration av Bokarenan så hanterade jag interaktioner med databasen genom SQL-instruktioner. När en användare hade skrivit en recension så sparades den till databasen med följande SQL-instruktion:
$q="INSERT INTO review (review, rate, book_id, title, reviewer)
VALUES ('$review', $rate, $book_id, '$title', $reviewer)";

Variablerna lät jag sanera innan de tilläts sättas in i SQL-strängen men det var inte vackert. Jag var inte konsekvent med var i kedjan av funktionsrop som jag lät sanera innehållet.

Datahanteringen behöver göras om från grunden. Det jag vill uppnå är överblickbar kod som är säker och flexibel. Säker är den datahantering som stänger ute användare från att injicera SQL-instruktioner. Flexibel är den kod som gör det enkelt att expandera med fler databasentiteter och justera relationer mellan dem. Eftersom jag gått mot objektorienterat denna gång så kommer jag använda en ORM (Object Relational Mapping). Det innebär att jag inte skriver egna SQL-instruktioner (och det läggs ett ytterligare lager mellan användare och deras möjlighet att injicera SQL). Det innebär också snyggare kod.

Den ORM jag använder mig av här är Doctrine. Genom annotationer kan jag instruera Doctrine hur den ska hantera innehåll i entiteter (objekt som ska sparas i databasen) och hur entiteter relaterar till varandra.

Jag försöker modelera entiteterna så gott det går mot den databas som finns sedan tidigare. En utmaning är att den nya databasen inte kommer bli identisk, men ska ändå tillåta att innehåll från tidigare version ska kunna importeras till den nya.

Nog kan det vara svårt att behärska sig från att kasta sig över än mer saker. Jag har kommit fram till att jag vill köra tester. Det innebär att jag SAMTIDIGT lär mig ORM och PHPUnit. Jag är inte bekväm med mock-objekt som testerna körs på. Alternativet skulle vara att koppla tester till databasen med transaktioner som påbörjas men sedan ”rullas tillbaka”. När det kommer till Repositories rekommenderas att testa dem mot en riktigt databaskoppling ( https://symfony.com/doc/current/testing/doctrine.html ). Jag får testa enligt denna rekommendation.

Anledningen till att jag till slut beslöt mig för att köra PHPUnit var att det är enklare att lägga till tester i ett tidigt skede än att göra dem som en efterhandskonstruktion. Det innebär också att det kommer finnas tester som säkerställer korrekt funktion när jag vill modifiera applikationen.