Etikett Doctrine

Uppdaterade nyheter

Hej kära vänner och läsare! Det är fredag och helg. Många av oss sätter oss i soffan ikväll med chips och tittar på något underhållande. De senaste veckorna har jag och min sambo kunnat njuta av den dumma (men roliga!) tv-serien Medical Police på Netflix. Tyvärr var det bara 10 avsnitt så nu får vi ägna oss åt något annat. Men eftersom det är fredag så skriver jag veckans sista inlägg så här på eftermiddagen. Den här veckan har fokus varit på programmering och att lägga till en blogg-funktion till Bokarenan. I onsdags skrev jag om hur Symfony och Doctrine har underlättat skapandet av en ny entitet (BlogPost) och uppdateringen av databasen. Denna gång ska jag skriva om den avslutande delen: hur allt ska kunna presenteras till användaren.

Det finns två komponenter att beakta när det kommer till hur nyheterna presenteras till användarna: template och Controller. Jag tänkte börja med Controller.

Min tanke är att den som är administratör ska kunna skriva ett blogginlägg (nyhet). Jag löste detta genom att använda AdminController och lägga till två funktioner till denna Controller som jag alltså har haft sedan tidigare. Den första funktionen innebär att administratören kan lägga till ett nytt inlägg. Något som jag vill lyfta fram som några detaljer är att administratören kan välja publiceringsdatum. Det innebär att inläggen inte dyker upp under Nyheter om inte publiceringsdatumet har nåtts. Den andra detaljen jag vill lyfta fram är ”slugs”. Det innebär att varje inlägg har en unik länk i stil med 2020-01-31-Det_här_är_en_nyhet. Detta kommer till användning när man vill titta på endast en nyhet, eller för sökmotorers möjligheter att länka direkt till ett inlägg.

Den andra Controllern som behövde uppdateras var NewsController. Denna kontroller har tilluppgift att presentera nyheter till användare. I kontrollern så skapade jag en ny funktion som hämtar de senaste inläggen och skickar dem till template-engine. Det tillkommer lite fler detaljer som innebär att jag kan paginera allt ihop och bara hämta de inlägg som ska vara på den aktuella sidan som användaren är på. Oftast är detta första sidan, men länkar till övriga sidor dyker upp längst ned.

Vad är då template-engine? Det är i princip en funktion som renderar html (eller php-filer med html-element) med dynamiskt innehåll. Som template-engine använder jag Twig. Från NewsController skickar jag de aktuella inläggen till Twig som löser det hela med att placera innehåll på rätt ställen och presenterar det med html-element och innehåll till användaren.

I NewsController så kan det se ut så här:

    /**
     * @Route("/{page}", name="nyheter_index", methods={"GET"})
     */
    public function index(BlogPostRepository $blogRepository, $page=1): Response
    {
        if (!\is_numeric($page)) {
            $page=1;
        }

        
        $count = $blogRepository->getCount();
        $limit = 1;
        $pages = $count/$limit;
        $pages = \ceil($pages);
        if ($page > $pages) {
            $page = $pages;
        }
        $posts = $blogRepository->getLatest($page, $limit);
        return $this->render("nyheter/index.html.twig", [
            "count" => $count,
            "page" => $page,
            "pages" => $pages,
            "posts" => $posts
        ]);
    }

Man kan se att det finns en template fil under filmappen nyheter som heter index.html.twig. Den filen ser ut så här:

{% extends "content.html.twig" %}
{% block navNyheter %}active{% endblock %}
{% block content %}
    {% for post in posts %}
        <article>
            <div class="article-header">
                <h1><a href="{{ path("nyheter_view", {'slug': post.slug}) }}">{{ post.title }}</a></h1>
            </div>
            <div class="article-content">
                <p class="time">{{ post.publish|date("d/m/Y") }} av {{ post.user }}</p>
                <p>{{ post.text|striptags('<em><strong><i><mark><cite><dfn>')|raw|nl2br }}</p>
                {% if app.user == post.user %}
                    <p><a href=""></a></p>
                {% endif %}
                
            </div>
        </article>
    {% endfor %}
    <div class="flex-center">
        {% for i in 1.. pages %}
            {% if i == page %}
                <a class="page-number active" href="/nyheter/{{i}}">{{ i }}</a>
            {% else %}
                <a class="page-number" href="/nyheter/{{i}}">{{ i }}</a>
            {% endif %}
        {% endfor %}
    </div>
{% endblock %}

Som man kan se i ovanstående kod så kan jag komma åt variabeln $posts genom instruktionen {{ post }}. Och när jag använder det på rätt sätt så kan jag få ett resultat som liknar det nedan.

Så det var allt för denna gången. Jag har skrivit ett blogginlägg om att göra en bloggfunktion för att göra blogginlägg. Blogg. Nu har jag skrivit det så mycket att jag börjar läsa det hela som grogg. Och apropå det: spel! Jag har visserligen en viss varm känsla för Secret of Monkey Island, men något som jag ser fram emot nu är att börja spela Skyrim igen. Senast jag gjorde det var 2013. Nu har jag den uppdaterade versionen på PS4. Jag får se om det fortfarande kan hålla mig underhållen. Men det är lugnt. Jag har några partyspel som nog funkar gott ändå!

Trevlig helg, allihopa!


Att göra en nyhetsblogg

Denna onsdag skriver jag om Bokarenan och möjligheten att bygga på den med fler funktioner. Det som står på dagordningen denna gång är en nyhetsblogg. För att få till detta så är det tre saker som ska komma på plats:

  • Ny entitet
  • Uppdaterad databas
  • Uppdaterad presentation/view

Det enklaste är att skapa en ny entitet med hjälp av MakerBundle i Symfony. Det innebär att jag via ett CLI (kommandotolken) kan instruera Symfony att göra den nya entiteten åt mig. På vägen får jag hjälp av Symfony för vad jag ska göra härnäst. För att starta det hela så utgår jag från projektets root-mapp och kör följande kommando:
php .\bin\console make:entity

Detta ger följande interaktion:

Jag döper min nya entitet till BlogPost, och ger den följande fält:
– title [string]
– text [text]
– entered [datetime]
– modified [datetime]
– publish [datetime]
– user [ManyToOne]

Exempel på hur fältet user i BlogPost relaterar till en annan entitet. User är det objekt som alltså refereras av BlogPost. ManyToOne innebär att respektive BlogPost bara kan referera till en användare, men en användare kan skapa flera BlogPost.

Genom hela denna process så har Symfony skapat och uppdaterat klassen BlogPost. Om vi tar en översikt över kursen så har den följande metoder och fält:

Då var steg ett avklarat. Nästa steg är att uppdatera databasen. Återigen är Symfony behjälplig… eller rättare sagt så är det faktiskt Doctrine. I CLI:t så kör jag följande kommandon:
php .\bin\console make:migration
Detta är en instruktion från Symfony till Doctrine hur databasen ska uppdateras efter att en till entitet har lagts till. Då återstår att låta Doctrine uppdatera databasen:
php .\bin\console doctrine:migrations:migrate
Databasen är nu uppdaterad och har rätta relationerna mellan BloggPost-tabellen och User-tabellen.

Då var två av tre steg avklarade. Det som återstår nu är att uppdatera presentationen för denna nya entitet. Detta steg består faktiskt av flera detaljer. Det innebär att en kontroller (eller två) ska uppdateras, en template för att skapa bloggposter ska skapas, en template för att presentera bloggposter ska uppdateras. Detta steg återkommer jag till på fredag. Vi ses då!


Sökfunktion och DQL

Denna veckan blir antagligen den sista på ett tag med uppdateringar om Bokarenan och programmering. Jag påbörjar en kurs om statistik och kommer använda Femte Arenan för att beskriva de lärdomar jag drar av det. Så jag passar på att beskriva det senaste jag gjort i programmeringsväg över helgen.

Jag lämnade förra veckan med en fundering på om jag skulle hinna få på plats en sökfunktion. När jag gjorde en sökfunktion till den nuvarande Bokarenan så svalde jag efter för mycket. Jag gjorde så att användaren kunde söka efter varje detalj i varje liten entitet. Det gjorde sökfunktionen lite svår att använda. Istället har jag gjort sökfunktionen till ett textfält som alltid finns tillhands i navigationsfältet och det man söker efter är BÖCKER. Jag har för närvarande gjort så att man kan söka efter författare eller boktitel, men sökresultatet är alltid BÖCKER. Inte författare eller något annat. Jag är rätt nöjd med resultatet. Det var också rätt så enkelt att implementera med hjälp av Doctrine och dess queryBuilder.

Så efter att ha fått på plats sökfunktionen så kände jag att jag var på rull. Därnäst så implementerade jag ett navigationsträd för böcker. I navigationsfältet har användaren en länk som heter Böcker. Klickar man på den så kommer man till sida för att välja böcker enligt genrer. Genrer är uppbyggda hierarkiskt, så att till exempel thriller är en underkategori till skönlitteratur. Klickar man på Skönlitteratur så får man upp en ny lista på genrer inom skönlitteratur samt de populäraste böckerna associerade med genren skönlitteratur.

Implementeringen av genrer i kombination med lista på populära böcker innebar att jag hade att hantera tre olika entiteter: Book, BookAnalysis och Genre. Tidigare har jag helt enkelt använt den repository som var lämplig. Jag försökte mig på att använda repository för BookAnalysis med förhoppning att kunna använda den Book-referens som den entiteten har. Book har i sin tur referens till Genre-entitet. Jag vred och vände men fick inte till det. Jag hade några alternativ kvar, som tur var. Nuvarande Bokarena använder sig av SQL-kod som jag skrivit. Nackdelen med att använda det är att jag inte får tillbaka entitets-objekt, utan det är något som jag fått initialisera ”manuellt”. Men jag behöver inte göra på samma sätt här. Jag kan använda DQL (Doctrine Query Language) istället. Det innebär att jag blir lika flexibel som om jag skrev SQL men jag får fördelen med att få tillbaka de rätta entiteterna. Med följande sats så blev det inte så komplicerat trots allt:
"SELECT a FROM App\Entity\BookAnalysis a JOIN a.book b WHERE b.genre = $id ORDER BY a.numReviews DESC, a.avgRate DESC"

Ha en sån trevlig vecka. Nästa gång skriver jag om data i stora mängder.


Kommer i form

Sagan fortsätter. Senast hade jag problem med att uppdatera användares roller. Rollerna avgör vad användare kan göra på hemsidan. I huvudsak gäller det om en användare ska vara administratör eller inte. När jag skrev senaste inlägget så lyckades jag inte med att uppdatera användares roller och någonstans låg problemet i administratörskontrollen (controller) eller i formuläret. Jag visste inte exakt var problemet låg, men det gör jag nu: det var i båda!

Så jag är lite av en härmapa när jag håller på att lära mig nya saker, och när det kom till användare så härmade jag hur upplägget såg ut för att uppdatera till exempel författare. Och här är kruxet: upplägget för författare och användare är olika. För författare så färdiggenereras en formulärklass som man kan skicka till Twig, vilket är en så kallad template engine. Det innebär att utifrån givna parametrar så genererar Twig den html-kod som behövs för att visa givet innehåll. Och jag byggde upp en formulärklass för användare också, men använde inte Twig till att generera den. Istället så skräddarsydde jag html-koden för att motsvara exakt vad som behövs för att uppdatera användare med rätt roller. I ett parallellt spår med detta så byggde jag controller enligt samma sätt som controller för författare. Det innebar att controllern kontrollerade om det färdiggenererade formuläret innehöll någon ny data. Och eftersom jag inte använde mig av det färdiggenererade formuläret så innebar det att controllern inte fick någon uppdaterad data. Lösningen blev därför att uppdatera controllern med att kolla i svarsobjektet istället för i formulärobjektet. Och därmed hade jag på 5 minuter löst ett problem som hållit mig fast i ett par timmer.

Ytterligare utveckling denna veckan har varit att lägga till en aktiveringsprocess när man registrerar sig som ny användare. Det innebär att applikationen kommer kontrollera att den som registrerat sig också är användare av den epost som har angetts. På den fronten har det gått snabbt, men det beror också på att jag kunnat sitta i åtminstone 20 minuter åt gången för att programmera.

På återseende på fredag!


Snubblarmåndag

Så kör vi igång med en ny vecka. Målet för förra veckan var att få igång ett auktoriseringssystem, och till viss del har jag fått igång det. Målet denna vecka är att få klart det.

Varför blev det inte klart förra veckan? Jo, för att jag vet inte vilket problem jag har. Mer exakt vet jag inte varför min kontroll och formulär inte lyckas med att spara när jag ändrar typ av roll som en användare ska ha. Jag kommer återkomma i veckan med var problemet ligger någonstans och hur jag väljer att hantera det. Om jag inte lyckas med detta så återstår att jobba direkt mot databasen.

En annan anledning till att jag inte blivit klar är att jag för närvarande oftast får 10 minuter åt gången när jag kan sätta mig ned och programmera. Det är alldeles för korta stunder för att hinna sätta in mig i vad jag försöker göra. Vad jag istället hinner med är att teckna lite:

”Skakar hand.”

Alltså, det där är vad min hjärna kan göra när den har 10 minuter åt gången.

Nu hoppas jag att jag kan få mer gjort denna vecka, men det beror på sömn och bebisar. Annars blir det fler teckningar.


Fredagsform

Hur länge ska jag försöka få till formulär? Det kanske märks på mina blogginlägg att utvecklingen inte går snabbt, men det sker åtminstone små steg i rätt riktning varje dag. Det har att göra med konceptet att vara pappa till två bebisar. För när tid finns över till programmering så är frågan om jag är tillräckligt klar i huvudet för det. Men som sagt: det går framåt!

Jag har fått till ett formulär nu, som framgick av föregående inlägg. Den har rätt beståndsdelar, men är inte snygg och har ett blandat språk (på grund av anledningar). Detta kommer justeras längre fram. Det som är av största vikt när formuläret är på plats är att det nu ska finnas en kontroll som kan hantera formuläret och uppdatera databasen med formulär-datan.

Jag har använt mig av en hjälp-klass som jag kallar EntityBasket. Den har beskrivits i tidigare inlägg, men i stort så har den koll på hur formuläret är uppbyggt och kan hämta motsvarande entiteter ur den. Det innebär att en kontrollklass kan hämta en entitet och sedan skicka den till databasen. Det som min kontrollklass gör i detta sammanhang är att den hämtar samtliga relevanta entiteter från formuläret, skapar nödvändiga relationer dem emellan, och sedan sparar dem i databasen.

Det var inte lite förvånad jag blev när jag upptäckte att Doctrine (som är ramverket för databashantering i applikationen) var så lätt att använda som jag trodde. Doctrine gav mig entitets-id som jag kunde använda när jag ville skapa relationer mellan entiteterna, innan jag hade sparat dem mot databasen. Jag kunde ange för Doctrine vilka entiteter jag skulle spara och sedan sparades alla samtidigt när jag använda funktionen flush().

Så när jag nu fått till ”baksidan” av formuläret så tänkte jag härnäst få till att avgränsa funktioner att lägga till mot databasen. Endast användare som är inloggade ska få lägga till entiteter.

Och så går åter en vecka till helg. Den här veckan spelar jag annat än Elite Dangerous. Elite är ett spel som inte går att pausa när man spelar, vilket gör att man håller tummarna att inget händer när bebisar kallar. Och denna vecka har bebisar kallat mer än förr. Som omväxling till detta har jag tagit med dem till biblioteket och kollat på deras spelutbud. Jag fann spelet Control där. Spelet handlar om Jesse som lyckats hitta en hemlig myndighet; Federal Bureau of Control (FDC). Det som lett henne dit är försvinnandet av hennes bror. FDC var de som tog hennes bror ifrån henne när de var små, och hon har sökt efter dem sedan dess. Spelet är i tredje person, vilket utvecklarna Remedy är vana att göra spel i. Tidigare har de gjort Max Payne och Alan Wake, och denna gång är det som en mix av dem. Det är mycket action och mycket berättelse (särskilt i de rapporter som finns att hitta i spelvärlden). Jag gillar vad jag spelat hittills, och uppskattar att det går att pausa var som helst (även i filmsekvenser).

Glöm inte att det är kanelbullens dag denna fredag!

Trevlig helg, allihop!


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.