[Se]   En

Infected Mushroom LIVE

  Annice      2022-04-17      Musik

Igår fick jag äntligen uppleva dem LIVE på Trädgår'n här i Göteborg, och en ungdomsdröm gick i uppfyllelse.

Jag må vara allätare inom musik, men det är få ljudslingor som förmedlat så euforiska känslor i min kropp som deras. De har betytt otroligt mycket för mig genom åren från att jag hörde dem för första gången i tidig 20-års ålder. Så tack för att ni finns och för allt underbart ni gjort, Infected Mushroom. <3

Två musikgenier ackompanjerar:

Spellista från ovan video:

2:19 - More of Just the Same
8:52 - Only Solutions
13:11 - Ani Mevushal
19:26 - Symphonatic Remix
25:23 - No Line In Midi
31:05 - Splicon
35:02 - Freedom Bill
40:23 - Infected Megamix
47:25 - Electro Panic Remix
51:03 - Wider Remix
55:00 - Astrix - Coolio (Infected Mushroom Remix)
1:02:22 - Bust A Move (Bliss Remix)
1:07:50 - Muse Breaks Reborn Remix
1:12:54 - Suliman Remix
1:19:23 - Dracul Reborn Remix
1:25:40 - Head Of Nasa
1:32:21 - Gravity Waves
1:38:58 - Return Of The Shadows Reborn Remix

Gå till inlägg »

Linuxkommandon

  Annice      2021-10-19      Kommandon, Linux

I min nuvarande arbetsroll som integrationsutvecklare arbetar jag huvudsakligen med PHP som serverspråk och då främst baserat på Linux filsystem. I detta inlägg har jag således passat på att lista ett gäng Linuxkommandon jag finner användbara.

Kommando: Beskrivning:
chmod +rwx Lägg till läs-, skriv- och exekveringsrättigheter för en fil.
chmod -rwx Ta bort läs-, skriv- och exekveringsrättigheter för en fil.
clear Rensa terminalfönstret.
cp Kopiera flera filer till en destination i form av en sökväg.
diff -y -W 70 --supress-common-lines Jämför skillnader mellan två filer. y-flaggan visar skillnaderna sida vid sida, medan w-flaggan låter en välja radbredden för att undvika omlindade rader.

find . -type f -name "*.php"

Hitta alla php-filer i den aktuella katalogen.

find . -name file.txt

Hitta alla filer vars namn är "file.txt" i den aktuella katalogen.

grep *.txt

Sök efter ett visst ord i alla txt-filer i den aktuella katalogen.

grep -rl "text here"

Sök efter alla filer som innehåller "text here" inom en katalogsökväg.

gzip -k

Zippa en fil men behåll orginalfilen med k-flaggan.

kill

Avsluta en systemprocess genom att ange dess PID.

kill -9

Tvinga terminering av en systemprocess.

less

Läs en fils innehåll utan redigeringsläge, inklusive paginering.

ls -a

Visa allt innehåll såsom filer och mappar i den aktuella katalogen.

lsof -i :

Kolla vilken process som använder en viss port.

mkdir

Skapa en mapp i den aktuella katalogen.

mv

Flytta en fil till en destination.

mv

Byt filnamn på en fil genom att "flytta" den angivna filen till dess nya filnamn.

mv *.png ~/

Flytta alla filer med filändelsen PNG från den aktuella katalogen till önskad katalogsökväg.

php -a

Aktivera en php-editor direkt i terminalen.

ping

Pinga en host och kolla dess status.

ps

Visa alla aktiva systemprocesser.

ps -a

Visa alla systemprocesser, inklusive de som ej körs.

ps -ax | grep

Hitta en process efter dess namn eller PID.
pwd Skriv ut sökvägen till den arbetskatalog du står i.
rm -R Radera en mapp och dess innehåll i den aktuella katalogen.
rm -f *.pdf Radera (med en force-flagga) två angivna filer, samt alla filer med filändelsen .pdf i den aktuella katalogen.
scp Fjärrfilkopiera en fil, t.ex. via "scp fil.txt"
ssh Anslut till en fjärrserver, t.ex. via "ssh ".
su -c Kör ett kommando som en annan systemanvändare.
sudo Exekvera ett kommando som superanvändare. Kan vara användbart när vissa kommandon kräver adminbehörighet för att kunna genomföras.
tail Skriv ut det sista stycket ur en fil, t.ex. "tail log.txt". Detta kommando är användbart vid t.ex. diverse felsökning.
touch Skapa en önskad fil i den aktuella katalogen.

uuidgen | awk '{print tolower($0)}'

Skapa en UUID-sträng (version 4) utskrivet i små bokstäver.
whoami Skriv ut den inloggade systemanvändaren i terminalfönstret.
!! Kör det senaste kommandot.

Gå till inlägg »

Doctrine ORM med PHP Symfony - del 2

  Annice      2021-04-10      PHP, Symfony, MySQL, Doctrine

När man bygger webbsidor med PHP utifrån Symfony-ramverket kan man smidigt interagera med databastabeller med hjälp av t.ex. EntityManagerInterface samt Doctrine ORM (Object Relational Mapper) i koppling till genererade entitetsklasser. Beroende på vilka tabellstrukturer som gäller samt vilka SQL-frågor som ska köras kan man dock behöva gå runt detta ibland.

Säg att man vill sätta in en ny rad i en tabell vars struktur för enkelhetens skull bygger på nedanstående kod med en primärnyckel som autoinkrementeras vid varje ny radinsättning:

CREATE TABLE Person (
    Id INT AUTO_INCREMENT PRIMARY KEY,
    Email VARCHAR(100) UNIQUE,
    Firstname VARCHAR(30),
    Lastname VARCHAR(30)
);

I en Symfony-applikation med Doctrine ORM installerat - samt med en genererad entitetsklass med getters och setters motsvarande ovanstående databastabell och kolumner - kan man då enkelt lägga till en ny rad i tabellen enligt följande exempelkod:

namespace App\Repository;

use App\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;

class PersonRepository
{
    /**
     * Inject dependency to EntityManagerInterface.
     */
    public function createPerson(EntityManagerInterface $em)
    {
        $person = new Person();
        $person->setEmail("jane.doe@mail.com");
        $person->setFirstname("Jane");
        $person->setLastname("Doe");

        $em->persist($person);
        $em->flush();
    }
}

So far so good! Men säg att man istället vill skapa nya tabellrader där en primärnyckel anpassas för varje ny rad, t.ex. baserat på nedanstående tabellstruktur:

CREATE TABLE Person (
    Email VARCHAR(100) PRIMARY KEY,
    Firstname VARCHAR(30),
    Lastname VARCHAR(30)
);

Då får man dock hitta ett annat sätt att hantera detta på då persist()- och flush()-metoderna tenderar att motarbeta en vid dessa typer av radinsättningar. Och ett sätt kan då vara att använda EntityManagerInterface med Doctrine DBAL (DataBase Abstraction Layer):

namespace App\Repository;

use Doctrine\ORM\EntityManagerInterface;

class PersonRepository
{   
    public function createPerson(EntityManagerInterface $em)
    {
        $email = 'jane.doe@mail.com';
        $firstName = 'Jane';
        $lastName = 'Doe';

        // Prevent SQL injections by using placeholders ("?") in the query:
        $sql = 'INSERT INTO Person (Email, Firstname, Lastname) VALUES (?, ?, ?)';

        $em->getConnection()
            ->prepare($sql)
            ->execute(
                [
                    $email,
                    $firstName,
                    $lastName
                 ]
             );
     }
}

Här går man alltså ifrån ORM-principen, men vid just INSERT-frågor är det ofta smidigast att bara utgå ifrån ovanstående typ av persistering vid tillägg av anpassade primärnycklar.

Gå till inlägg »

Kryptering av cookies

  Annice      2021-03-19      C#, ASP.NET Core, Säkerhet

Cookies är ju vanligt att använda sig av i webbutvecklingssammanhang, t.ex. för att komma ihåg en viss användarinloggning då man kan sätta en cookie på ett inloggat användar-ID. Cookie-värden som sparas i webbläsaren innebär dock att vem som helst kan se dessa värden genom att välja att inspektera webbläsarens element.

Av säkerhetsskäl vill man helst undvika att exponera faktiska användar-ID:n som läses fram från databaser för obehöriga, och ett sätt att dölja användar-ID:n som används i cookies kan då vara att kryptera dessa cookie-värden.

Inom C# ASP.NET Core kan detta göras genom att t.ex. använda sig av tillägget DataProtection. I en MVC ASP.NET Core-lösning görs detta genom att först registrera tillägget ifråga i filen "Startup.cs" enligt nedan:

 public class Startup
 {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            // Register support for DataProtection extension:
            services.AddDataProtection();
        }

       // ...

Därefter kan man sedan skapa en separat tjänsteklass som imlementerar detta DataProtection-tillägg, t.ex. genom följande klass och kod som kan läggas i ett eget tjänstelager (mapp) i en applikation:

using System;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;

namespace TestApp.Services
{
    public class DataProtector
    {
        private IDataProtector CookieProtector()
        {
            var dataProtectionProvider = DataProtectionProvider.Create("TestApp");
            var protector = dataProtectionProvider.CreateProtector("Cookie.Encryption");
            return protector;
        }

        /// Create a user cookie and encrypt it to prevent the real value
        /// from being exposed on the client side (via the browser).
        public void EncryptUserId(HttpContext context, int userId)
        {
            string protectedUserId = CookieProtector().Protect(userId.ToString());
            context.Response.Cookies.Append("User", protectedUserId);
        }

        /// Receive the encrypted user cookie and decrypt it to be able to handle its
        /// real value on the server side.
        public int? ReadUserId(HttpContext context)
        {
            int userId;

            if (!string.IsNullOrEmpty(context.Request.Cookies["User"]))
            {
                string id = CookieProtector().Unprotect(context.Request.Cookies["User"]);
                userId = Convert.ToInt32(id);
                return userId;
            }
            return (int?)null;
        }

    }
}

För att sedan nyttja denna tjänst vid rendering (skapande) av webbsidor genom en controller kan det se ut så här om man vill försäkra sig om att cookien är krypterad innan man presenterar en sida vid namn "Start":

using Microsoft.AspNetCore.Mvc;
using TestApp.Services; // Include the service layer to reach our data protector service.

namespace TestApp.Controllers
{
    public class HomeController : Controller
    {
        // Autowire our DataProtector service via dependency injection:
        public IActionResult Start(DataProtector protector)
        {
            // E.g. userId fetched from DB, but hard coded in this example for simplicity:
            int userId = 1;
            protector.EncryptUserId(HttpContext, userId);

            // ...

            return View();
        }
    
     }
 }

Efter att ovanstående användar-ID satts som cookie samt krypterats via ovanstående tjänst kommer cookie-värdet "1" alltså bara exponeras som ett krypterat värde i webbläsaren enligt följande screenshot:

När man sedan vill dekryptera cookie-värdet för att kunna behandla dess faktiska värde på serversidan igen kan detta göras genom att anropa den implementerade tjänsteklassen som dekrypterar värdet likt exemplet nedan:

/// A method used in a suitable place on the server-side where we want to call our DataProtection service
/// via a dependency injection to decrypt our cookie back to its actual value.
public void HandleDecryptedCookieValue(DataProtector protector)
{
    // This will assign a userId variable with the decrypted cookie value:
    var userId = protector.ReadUserId(HttpContext);

    // Now do something with the decrypted userId...
}

Gå till inlägg »

Nya foton

  Annice      2021-03-08      Uppdateringar, Foto

Har laddat upp ett par nya foton till sidan digital fotografering.

Gå till inlägg »

Docker-kommandon

  Annice      2021-02-15      Docker, Kommandon

Docker är ett kraftfullt verktyg som kan virtualisera ett operativsystem med diverse tekniker genom uppdelade, oberoende tjänster och containrar som i sin tur baseras på en eller flera Docker images.

Med andra ord kan en Docker image skapas med t.ex. Ubuntu som virtuell OS-bas som i nästa tur kan installera t.ex. Apache web server, PHP och MySQL för att ge stöd för en webbapplikation baserad på dessa tekniker. Denna image kan sedan köra igång dessa tekniker genom att starta olika containrar: en som kör Apache och PHP, en annan som kör MySQL etc.

Att skapa appar baserat på mikrotjänster på detta sätt gör att en applikation kan köras på vilken maskin som helst som har Docker installerat. En container-baserad infrastruktur blir också mer resurseffektiv då man kan stänga av, byta och slå på nya containrar och tjänster utan att behöva ta hänsyn till en värddators/servers OS, vilka de är helt isolerade ifrån.

Hur som helst tänkte jag i detta inlägg bara passa på att lista några Docker-kommandon jag finner användbara för hantering av images och containrar via en systemterminal:

Kommando: Beskrivning:
docker build . Bygg en Docker image i samma mapp där dockerfilen (Dockerfile) är.
docker build -t : Bygg en Docker-image med ett givet image-namn och label (version) baserat på en Dockerfile.
docker-compose up Givet att man står i samma mapp som önskad Dockerfile: Bygg, (åter)skapa, och bifoga till containers för en tjänst.
docker-compose start Givet att man står i samma mapp som önskad Dockerfile: Starta alla containrar som skapats av en image.
docker-compose stop Givet att man står i samma mapp som önskad Dockerfile: Stoppa alla körande containrar av en viss image.
docker images Lista alla Docker images.
docker run

Skapa och starta en container baserad på given image. Antalet ggr detta kommando körs skapar och startar lika många containrar baserad på samma image.

docker ps

Lista alla containrar som körs.

docker ps -a

Lista alla containrar - inklusive de som stoppats.

docker start

Starta en container.

docker stop

Stoppa en container.

docker rm

Radera en container (givet att containern stoppats).

docker rmi

Radera en image (givet att tillhörande containrar stoppats).

docker exec -i -t /bin/bash

SSH:a (anslut till/gå in i) containern.

Gå till inlägg »

Event subscribers i PHP Symfony

  Annice      2020-12-27      PHP, Symfony

Applikationer som är uppbyggda utifrån MVC-mönstret (Model-View-Controller) använder ju controller-klasser som ett slags interaktionslager mellan ett view- och model-lager. Entitets/objektsklasser och deras egenskaper har då för avsikt att finnas i model-lagret med syfte att avspegla databastabeller och deras kolumner, medan controller-klasser och deras action-metoder har till syfte att avspegla en applikations olika sidor, vilka finns i view-lagret.

Med andra ord innebär detta att det är controller-metoder som dels tar emot datainput från slutanvändare via view-lagret för att passa det vidare till databas via entitetslagret, dels att dessa metoder renderar de olika webbsidorna som i slutänden presenteras för slutanvändare.

När man surfar till en webbsida som använder MVC är det alltså först en controller-metod som anropas på serversidan för att förbereda den efterfrågade webbsidan med all nödvändig data, vilken då kan hämtas från en eller flera databastabeller via en eller flera entiteter. När jag har möjligheten att välja brukar jag föredra att utgå ifrån MVC som designmönster då jag tycker det är ett väldigt strukturerat sätt att bygga och hantera applikationskod på.

Inom C# ASP.NET benämns dessa lager som just model, view och controller, men i PHP Symfony-ramverket benämns de som entity (model), templates (view), samt controller. I en större MVC-applikation är det ju inte ovanligt att använda flera olika controllers där varje controller i nästa tur har flera olika action-metoder, varvid många av dessa metoder kanske också har till uppgift att rendera olika adminsidor.

För att säkersställa att obehöriga användare inte når dessa sidor behöver man ju då kontrollera detta på något sätt innan dessa sidor presenteras. Men istället för att behöva lägga samma loginkontroll i varje berörd action-metod, vilket bl.a. skulle strida mot DRY-principen (Don't Repeat Yourself), kan detta hanteras via t.ex. en middleware vilken kontrollerar detta innan controllers anropas (som jag även beskrivit i ett tidigare inlägg).

Ett liknande sätt att hantera detta på som jag upptäckt inom PHP Symfony är att använda sig av event subscribers. Principen går då ut på att man implementerar en event subscriber-klass som i sin tur implementerar ett interface vid namn "EventSubscriberInterface". EventSubscriberInterfacet är vidare tillgängligt via en EventDispatcher-komponent som kan installeras som bundle via Symfony-ramverket med pakethanteraren composer.

Vidare kan man då låta en event subscriber agera kontrollant av t.ex. en giltig admininloggning. Man kan sedan säkerställa att subscribern anropas innan önskade controllers genom att låta dessa controllers implementera ett tomt interface, vilket subscribern kontrollerar om controllern är en instans av. Jag har försökt illustrera detta kontextflöde i sekvensdiagrammet nedan:

Exempel på en implemenation av detta kan se ut som nedan där jag först valt att skapa ett separat lager (mapp) namngiven "Service". I denna mapp har jag vidare skapat ett tomt interface vid namn "AuthInterface" som jag lagt i en undermapp vid namn "Abstraction". Följande kod utgår för övrigt från PHP 7.4 utifrån Symfony 5.1.8

namespace App\Service\Abstraction;

interface AuthInterface
{
    // No need for method declarations in this interface.
}

Vidare har jag skapat ytterligare en mapp vid namn "EventSubscriber" i vilken jag skapat en AuthSubscriber-klass. Under förutsättning att EventDispatcher-komponenten som sagt installerats har jag sedan kunnat låta denna AuthSubscriber-klass implementera ett EventSubscriberInterface likt nedan:

namespace App\EventSubscriber;

use App\Service\Abstraction\AuthInterface;
use App\Service\AuthManager;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * The AuthSubscriber is called before the controllers to check valid admin login
 * for password protected controllers.
 */
class AuthSubscriber implements EventSubscriberInterface
{
    private $authManager;
    private $router;

    public function __construct(AuthManager $auth, UrlGeneratorInterface $router)
    {
        $this->authManager = $auth;
        $this->router = $router;
    }

    public function onKernelController(ControllerEvent $event)
    {
        $controller = $event->getController();

        // When a controller class defines multiple action methods, the controller
        // is returned as [$controllerInstance, 'methodName']:
        if (is_array($controller)) {
            $controller = $controller[0];
        }

        // Check if the controller implements our AuthInterface:
        if ($controller instanceof AuthInterface) {

           // Call a separately implemented authManager class in which we check if an admin session is set.
           // If the session is not set, just redirect the user to a public start page.
            if (!$this->authManager->isLoggedIn()) {

                $redirectUrl = $this->router->generate('start');

                $event->setController(function () use ($redirectUrl) {
                    return new RedirectResponse($redirectUrl);
                });
            }
        }
    }

    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::CONTROLLER => 'onKernelController',
        ];
    }

} // End class.


Slutligen kan jag bestämma vilka controller-klasser som ska kontrolleras av denna AuthSubscriber genom att låta dessa controllers implementera AuthInterfacet likt nedanstående kod:

namespace App\Controller;

use App\Service\Abstraction\AuthInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

/**
 * Controller class to handle the admin pages.
 */
class AdminController extends AbstractController implements AuthInterface
{
    /**
     * @Route("/admin", name="admin_page")
     * @Method({"GET"})
     */
    public function adminPageAction()
    {
        // Prepare data etc here for the admin page...

        return $this->render("admin/admin-page.html.twig");
    }

} // End class.

Gå till inlägg »

Doctrine ORM med PHP Symfony

  Annice      2020-12-06      PHP, Symfony, MySQL, Doctrine

När jag började bygga om Annice.se utifrån PHP med Symfony-ramverket hoppades jag på att PHP skulle ha någon motsvarighet till Lambda-uttryck med LINQ (Language-Integrated Query). Lambda-uttryck med LINQ var nämligen något jag använde mig ganska flitigt av inom C# ASP.NET Core vad gällde diverse filtrering av listor och arrayer, och som ofta grundade sig på uppslagsfrågor mot databas.

Ett slags motsvarighet till ovanstående inom PHP är Doctrine, och det jag känt på lite hittills i samband med ombygget av Annice.se är Doctrine ORM med Symfony. ORM står alltså för Object-Relational Mapper som på svenska skulle kunna översättas till objektrelationskartläggare.

Som ORM-akronymen antyder är alltså syftet med detta frågespråk att underlätta PHP-objekts (entiteters) interaktion mot en eller flera relationsdatabaser, i vilka databastabellerna då alltså avspeglar PHP-kodens entiteter, samt där tabellattributen (kolumnerna) mappas mot PHP-entiteternas olika egenskaper.

Det som tilltalade mig med Doctrine ORM var att man också tillåts formulera sig i ren SQL-kod, d.v.s. som de faktiska uttryck som i slutänden exekveras mot databas. Vid de tillfällen man använder sig av nästlade frågor och/eller olika joinar för att komma åt diverse data är jag nämligen en av dem som föredrar att uttrycka mig i ren SQL.

Givet att man har PHP Symfony installerat med konfigurerad uppkoppling mot en databas är vidare förutsättningar för att köra Doctrine ORM inom Symfony att ha installerat pakethanteraren Composer, vilket då i nästa tur kan installera stöd för paketet Doctrine. Av vana från C# ASP.NET Core-världen använder jag ordet "paket" då man där pratar om motsvarigheten NuGet packages. Inom Symfony ska väl dock tilläggas att paket egentligen benämns som bundles. Utförligare guide om detta finns vidare på Symfonys egen dokumentationssida: Databases and the Doctrine ORM.

Hur som helst ville jag egentligen med detta inlägg passa på att lista några Doctrine ORM-frågor jag samlat på mig som jag själv finner användbara att blicka tillbaka på som utgångspunkt för olika databasfrågor. Och för att slutligen kunna möjliggöra denna funktion behöver man anropa/importera ett EntityManagerInterface där man vill använda sig av Doctrine ORM, t.ex. i en controller.

Nedanstående kod exemplifierar en helhetskontext där ett beroende av EntityManagerInterfacet har injecerats in en controller-konstruktor, vilket är användbart om man t.ex. vet att Doctrine-frågor ska användas i många metoder i samma controller-klass. Den konstruktorinjecerade EntityManager-tjänsten kan sedan appliceras på en Doctrine-fråga för att hämta ut poster från en databastabell i actionmetoder likt nedan. Nedanstående kod baseras för övrigt på Symfony 5.1.8 med Doctrine 2, vilket i nästa tur kräver åtminstone PHP 7.1 :

namespace App\Controller;

use App\Entity\Entry;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\ORM\EntityManagerInterface;

class EntryController extends AbstractController
{
    private $entityManager;

    /**
     * Inject dependencies.
     */
    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    /**
     * @Route("/entry/entry_list", name="entry_list")
     * @Method({"GET"})
     */
    public function entryListAction()
    {
        // Call the entity manager service:
        $em = $this->entityManager;
        // Get all entries from DB via Doctrine entity mapping and return the result in descending order by entry date:
        $entries = $em->getRepository(Entry::class)->findBy([], ['date' => 'DESC']);

        // Finally, pass the entry list to be reached via an "entries" variable from a twig template/view:
        return $this->render('entries/entry_list.html.twig', [
            "entries" => $entries
        ]);
    }
}


Om man istället skulle vilja formulera samma fråga utan att injecera EntityManagerInterfacet i en konstruktor, d.v.s. bara använda det direkt från någon enstaka metod, kan man utesluta konstruktorinjeceringen och istället anropa tjänsten såsom nedan:

 public function entryListAction()
 {
     // Call the entity manager service:
     $em = $this->getDoctrine()->getManager();
     // Get all entries from DB via Doctrine entity mapping and return the result in descending order by entry date:
     $entries = $em->getRepository(Entry::class)->findBy([], ['date' => 'DESC']);

     // ...


Om man t.ex. skulle vilja hämta ut ett specifikt Entry-objekt på ett specifikt villkor i Doctrine ORM skulle det kunna se ut som nedan (givet att Entity Manager-tjänsten har importerats samt instantierats):

$entry = $em->getRepository(Entry::class)->createQueryBuilder("alias")
    ->select("alias")
    ->where("alias.email = :email")
    ->setParameter("email", "user@email.com")
    ->getQuery()
    ->getOneOrNullResult();


Genom att välja att returnera ovanstående resultat m.h.a. metoden getOneOrNullResult() kommer frågan returna null om inga resultat hittas, vilket innebär att detta skulle kunna hanteras och fångas upp direkt i ett PHP-villkor utan att applikationen behöver krascha. En motsvarighet i detta fall är annars att använda sig av metoden getSingleResult(), men detta skulle kasta ett undantag om inget objekt matchas, eller om det skulle uppstå fler än en objektsmatchning.

Om man skulle vilja hämta ut objekt baserat på flera villkor och returnera resultatet som en lista i fallande ordning efter datum kan det i nästa tur se ut så här:

$entries = $em->getRepository(Entry::class)->createQueryBuilder("alias")
    ->select("alias")
    ->where("alias.date = :date")
    ->andWhere("alias.categoryid = :categoryid")
    ->setParameters(["date" => "2020-12-06", "categoryid" => 1])
    ->orderBy("alias.date", "DESC")
    ->getQuery()
    ->getResult();


Skulle man vilja hämta samma sak som ovan men formulerat i ren MySQL med Doctrine kan det se ut så här:

$entries = $em->createQuery(
    "SELECT alias FROM App\Entity\Entry alias
     WHERE alias.date = :date AND alias.categoryid = :categoryid
     ORDER BY alias.date DESC"
)->setParameters(["date" => "2020-12-06", "categoryid" => 1])
 ->getResult();


I relationsdatabaser där många-till-många-relationer uppstår mellan tabeller kan man ibland behöva hämta värden via en s.k. kopplingstabell, eller "länktabell". Länktabellen ska i nästa tur då bestå av åtminstone referensnycklar som pekar till alla primärnycklar för vardera tabell där många-till-många-relationen äger rum.

Om en verksamhetsregel säger att ett inlägg kan ha flera kategorier samtidigt som varje kategori kan länkas till flera inlägg uppstår alltså en många-till-många-relation mellan en inläggstabell samt en kategoritabell. Länktabellen som kopplar samman dessa två tabeller måste då bestå av åtminstone en referensnyckel som pekar mot primärnyckeln till inläggstabellen, samt en referensnyckel som pekar mot primärnyckeln till kategoritabellen.

Låt säga att jag skulle vilja använda Doctrine för att i ren MySQL-kod hämta alla kategorier tillhörande ett inlägg - som i nästa tur ska returneras som en kategorilista sorterad i bokstavsordning efter kategorinamn. Då kan det se ut så här:

$entryCategories = $em->createQuery(
    "SELECT c FROM App\Entity\Category c
     WHERE c.id IN (
         SELECT IDENTITY(ec.categoryId) FROM App\Entity\EntryCategories ec
         WHERE ec.entryId = :entryid
     )
     ORDER BY c.name ASC"
)->setParameter("entryid", $entryIdInput)
 ->getResult();


För att ovanstående fråga ska fungera förutsätter det - förutom att EntityManagerInterfacet är importerat och instantierat - också att berörda entiteter "Category" och "EntryCategories" finns tillgängliga i PHP-kodens entitetslager, samt att de är importerade i den PHP-klass i vilken frågan används. Vidare utgår ovanstående fråga ifrån att entiteten "Category" har egenskaper vid namn "id" och "name", samt att länkentiteten "EntryCategories" har egenskaper vid namn "categoryId" och "entryId".

Gå till inlägg »

Uppgraderat bloggskript till "Blog System 2.2"

  Annice      2020-11-28      Uppdateringar, C#, ASP.NET Core

Jag har nu uppgraderat C#-skriptet "Blog System 2.0" till version 2.2. Det nya skriptet "Blog System 2.2" finns nu också tillgängligt för nedladdning inne på skriptsidan.

Den ändring som gjorts är att säkerställa att ingen anonym besökare kan direktnavigera till ett inläggs-ID som skulle ha statusen "utkast", d.v.s. ett icke-publicerat blogginlägg.

Autentiseringen i detta skript hanteras huvudsakligen via en middleware som känner av namn på controllers som efterfrågas i varje HTTP-begäran av olika admin-sidor. Med andra ord säkerställer den s.k. auth-servern genom denna middleware att controllers som renderar olika adminsidor inte visar dessa sidor för obehöriga besökare. Nedanstående kodavsnitt som hanterar denna kontroll hittas i skriptfilen "AuthServer.cs" i mappen "Middleware":

public async Task InvokeAsync(HttpContext context, [FromServices] IAuthHandler handler)
{
  string[] paths = { "/Entry", "/Comment", "/Category", "/User" };

  foreach(string path in paths)
  {
    if (!handler.IsLoggedIn() && context.Request.Path.ToString().StartsWith(path))
    {
      context.Response.StatusCode = 401; // Unauthorized.
      context.Response.Redirect(context.Request.Scheme + "://" + context.Request.Host + "/Home/AccessDenied");
      return;
    }
  }

  await _next.Invoke(context);
}


För att säkerställa att dessa controllers dock alltid nås av en inloggad admin anropas samtidigt en s.k. auth-handler-tjänst. Auth-handler-tjänsten kontrollerar i nästa tur om en sessionsvariabel är satt som tillfälligt sparat en admins användar-ID vid en giltig inloggning. Kontrollmetoden som anropas via auth-servern ovan och som alltså kontrollerar om detta sessionsvärde existerar kan vidare hittas i skriptfilen "AuthHandler.cs" i mappen "Services":

public bool IsLoggedIn()
{
  var scope = _scope.CreateScope();
  var context = scope.ServiceProvider.GetService ();

  // If user session is set, it means we have a valid login:
  if (context.HttpContext.Session.GetString("UserID") != null)
      return true;
  else
     return false;
}


Den controller som vidare hanterar de publika sidorna i detta bloggsystem heter i nästa tur "HomeController". Det är också den controllern som hanterar varje blogginläggs detaljsida, d.v.s. där man kan läsa ett inlägg i sin helhet samt där besökare kan lämna kommentarer. Då denna controller hanterar dessa detaljsidor oavsett om de är utkast eller publicerade - samt att controllernamnet "Home" inte kontrolleras av auth-servern - behövde alltså en check läggas till i denna controller för att säkerställa att endast adminanvändare kan navigera och se utkastsinlägg. Det gjordes vidare genom att lägga till ett villkor i action-metoden "EntryComments" som renderar detaljsidor enligt kodavsnittet nedan:

[HttpGet]
public IActionResult EntryComments(int? id)
{
  // Catch posted feedback:
  ViewBag.Success = TempData["Success"];
 
  // Check valid admin login if draft entry:
  if (HttpContext.Session.GetString("UserID") == null &&
      _db.BsEntries.Where(e =>e.Id == id && e.IsPublished == false).Any())
  {
    return RedirectToAction(nameof(Index));
  }

  // ...


Villkoret i if-satsen ovan kontrollerar nu alltså om admin-sessionen är okänd samtidigt som inläggs-ID:t som efterfrågas är ett utkast. I detta fall kommer i så fall besökaren tolkas som anonym och bara skickas vidare till den publika startsidan, d.v.s. aldrig komma åt det efterfrågade inlägget. Den tillagda koden ovan hittas för övrigt i sin helhetskontext i skriptfilen "HomeController.cs" i mappen "Controllers".

Gå till inlägg »

Annice.se baseras nu på PHP med Symfony

  Annice      2020-11-22      Uppdateringar, PHP, Symfony, MySQL

Annice.se är nu helt ombyggd så att den i skrivandes stund baseras på webbapplikationsspråket PHP 7.4 utifrån Symfony 5.1.8. Det innebär att all serverlogik har flyttats från min tidigare OutSystems cloud-server till mitt nuvarande webbhotell på One.com som istället för C# alltså kör PHP som serverspråk. Arbetet har vidare medfört en total databasmigrering från en tidigare SQL Server-baserad DBMS till en nuvarande MySQL-baserad med MariaDB.

Anledningen till att jag har valt att bygga om sidan är dels för att hålla serverlogiken så centraliserad som möjligt, dels för att jag vill ha större kontroll över all applikations- samt serverkod. Då OutSystems baseras på Low-Code-utveckling - samt att jag också bara hade en gratisinstans på deras server - innebär det också begränsade utvecklingsmöjligheter. Sedan ska jag inte sticka under stol med att jag i ärlighetens namn tycker det är roligare att utveckla i ren kod. :)

Varför jag nu valt PHP som applikationskod istället för t.ex. C# har dock ingenting att göra med att jag skulle tycka C# är sämre på något sätt. Anledningen är istället att webbhotellet jag hostar sidan på inte har stöd för C#. Vidare har jag hostat hela eller delar av min hemsida på webbhotellet One.com i snart 15 år och alltid varit nöjd med deras tjänster, service samt priser. Följaktligen ser jag helt enkelt ingen anledning till att byta, så cred till One.com!

Liksom C# är PHP ett utvecklingsspråk jag också alltid haft gott intryck av. Faktum är att PHP även är det första programmeringsspråket jag kom i kontakt med när jag började intressera mig för webbutveckling på riktigt i tidig 20-årsålder. Det är också ett språk jag intermittent utvecklat i sedan dess.

När jag började plugga på systemvetarprogrammet på Linköpings universitet, 2013, var dock Java det första programmeringsspråket vi läste där. Det var också då jag för första gången kom i kontakt med objektorienterad programmering, vilket var en typ av kodstruktur jag fick väldigt gott intryck av, strukturerad som jag är. :-] När jag senare upptäckte det objektorienterade språket C# - som enligt mitt tycke känns som en mer clean version av Java - blev väl det i nästa tur lite av ett nytt favoritspråk för mig.

PHP som däremot inte är ett objektorienterat språk från början har dock hållit sig kvar stadigt som ett rent webbutvecklingsspråk genom åren. Med tiden har dock PHP vidareutvecklats till att idag även ha ett fantastiskt bra stöd för ren objektorientering. Och det är väl lite där jag också kommer in på valet av ramverket Symfony.

När jag började göra lite research på ett tänkbart nytt kodupplägg för Annice.se stod det tillslut mellan PHP Symfony och PHP Laravel. Det verkar dock som att mångas omdömen gällande de två ramverken i jämförelse är att Symfony ändå ger bäst detaljkontroll i slutänden. Vidare slog det mig att Symfony-upplägget i grundprincip är slående likt sättet att arbeta med C# ASP.NET Core. I synnerhet gällande objektorientering med MVC-mönster, klasser, komponenter, dependency injections av diverse tjänster och dylikt. Jag skulle kunna gå igenom grundligare jämförelser mellan dessa ramverk i ett annat blogginlägg, men slutsatsen var i.a.f. att Symfony visade sig falla mig precis i smaken.

Så summan av kardemumman är alltså att valet då föll på PHP med Symfony. Och med den introduktion jag gett mig själv hittills av ramverket i fråga i samband med ombygget av Annice.se har jag dessutom bara fått mersmak!

Till den senaste versionen av Annice.se har jag nu också passat på att bygga en blogg. Syftet med bloggen är i första hand att skriva om sådant som främst intresserar mig själv, d.v.s. webbutveckling och digitalt skapande. Jag har nämligen ändå alltid haft en förmåga att skriva ner lite tutorials, kodsnuttar och diverse bra-att-ha-information i allmänhet i mitt OneNote-verktyg för att hålla reda på saker och ting. Och istället för att bara hålla sådan information i mina privata OneNote-anteckningar, så varför inte skapa mitt eget webbuppslagsverk i form av en blogg då istället! :)

Gå till inlägg »

Uppgraderat PHP-skriptet "REST API JWT Auth" till version 1.2

  Annice      2020-10-20      Uppdateringar, PHP, REST API

En uppgraderad version av PHP-skriptet "REST API JWT Auth" finns tillgängligt för gratis nedladdning inne på skriptsidan som version 1.2.

Skriptet i fråga är ett litet integrationssystem byggt i HTML5, CSS3, PHP, samt MySQL. Systemintegrationen baseras vidare på REST API:er med JSON web tokens för autentisering av uppdateringsförfrågningar.

För övrigt tillhandahåller skriptet en inloggningsfunktion där en adminanvändare kan logga in och uppdatera sina adminuppgifter, samt en responsiv design med formulärsvalidering.

Till version 1.2 har en endpoint lagts till för att trigga en radering av användarens sparade email i "server 2"-appen när användaren loggar ut från "server 1"-appen.

Mer information och illustrationer såsom sekvensdiagram och dylikt finns vidare via skriptets relaterade README-länk inne på skriptsidan.

Gå till inlägg »

Uppgraderat C#-skript vid namn "Blog System 2.0" tillgängligt

  Annice      2020-10-18      Uppdateringar, C#, ASP.NET Core, Entity Framework Core, T-SQL

Jag har nu uppgraderat och laddat upp en plattformsoberoende version av det tidigare C#-skriptet "Blog System 1.0", vilket nu är tillgängligt för gratis nedladdning som "Blog System 2.0" inne på skriptsidan.

Skriptet är ett databasdrivet bloggsystem baserat på ASP.NET Core och Entity Framework Core och tillhandahåller full CRUD-funktionaliet för en adminanvändare för att kunna skapa och redigera bloggens inlägg, kategorier och kommentarer.

Övriga funktioner som ingår är responsiv design med Bootstrap, sök- och filtreringsfunktion, paginering, klient- och servervalidering, lösenordsåterställning via en tokenlänk, WYSIWYG HTML-editor, etc. Mer information finns via skriptets  relaterade README-länk inne på skriptsidan.

Gå till inlägg »

Nytt C#-skript är uppladdat

  Annice      2020-09-22      Uppdateringar, C#, ASP.NET Core, Entity Framework Core

Ett nytt C#-skript vid namn "Visitor Statistics 1.0" finns nu tillgängligt inne på skriptsidan.

Skriptet kan användas för att registrera besöksstatistik för en applikation för att kunna visa information såsom besökares IP- och värdadresser, samt besökstider, referens-URL:er, etc. Skriptet är vidare byggt i C# ASP.NET Core MVC med Entity Framework Core - databas först med T-SQL.

Övriga kodspråk som använts är HTML5, CSS samt JavaScript med jQuery. Ytterligare använder sig skriptet av Ajax för förbättrade laddnignstider av postad formlärsdata. Mer information såsom setup-guide och diverse illustrationer finns via README-länken inne på skriptsidan.

Gå till inlägg »

Fixad mejlbugg

  Annice      2020-08-14      Uppdateringar

Jag har nu fixat en bugg som gjorde kontaktsidans mejlformulär opålitligt då jag inte alltid mottog mejl p.g.a. av en icke-fungerande SMTP-server. SMTP-servern är hur som helst nu utbytt så att mejl alltid ska nå inkorgen i fortsättningen!

Gå till inlägg »

Nytt inloggningssystem i C#

  Annice      2020-06-23      Uppdateringar, C#, ASP.NET Core, Entity Framework Core

Ett nytt inloggningssystem i C# med stöd för flera adminnivåer baserat på ASP.NET Core Identity och Entity Framework Core finns nu tillgängligt inne på skriptsidan vid namn "IdentityLoginSystem 1.0".

Gå till inlägg »

Nytt integrationsskript i C#

  Annice      2020-04-25      Uppdateringar, C#, ASP.NET Core, REST API

Ett nytt C# ASP.NET Core-integrationsskript vid namn "REST API JWT Auth 1.0" finns nu tillgängligt för nedladdning inne på skriptsidan.

Gå till inlägg »

Nya foton

  Annice      2019-08-27      Uppdateringar, Foto

Jag har nu laddat upp ett par nya foton som finns tillgängliga inna på sidan: Digital fotografering.

Gå till inlägg »

Nytt C#-skript

  Annice      2019-08-25      Uppdateringar, C#

Ett nytt C#-skript finns nu tillgängligt inne på skriptsidan vid namn "MvcNews 1.0".

Gå till inlägg »

1