Sixtens hemsida Uppgifter Blogg Om

Bankapplikation med databas

Gå till sida

Källkod

bank_sql/

logout.php

transfer.php

index.php

login.php

manage.php

signup.php

change_password.php

PREP/

POPULATE.php

QUERIES.sql

modules/

user.php

page.php

database.php

session.php

utility.php

wesweb01/bank_sql/modules/database.php

1 lines
<?php
# Fil som sköter anslutning till databas, samt metoder för att komma åt datan på ett enklare sätt.

class DatabaseConnection
{
    
# Din kod för att ansluta till en databas. Rätt så självklar.
    
public static $host "server1.serverdrift.com";
    public static 
$user "Intentionally removed by CSource";
    public static 
$pwd "Intentionally removed by CSource";
    public static 
$db "als070804sn_bank";
    public static 
$options = array(
        
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'",
        
PDO::ATTR_ERRMODE,
        
PDO::ERRMODE_EXCEPTION,
        
PDO::ATTR_EMULATE_PREPARES,
        
false
    
);

    private 
$pdo;
    public function 
__construct()
    {
        
# Här etablerar vi anslutningen när vi skapar en ny instans av DatabaseConnection.
        
$host self::$host;
        
$db self::$db;
        
$dsn "mysql:host=$host;dbname=$db";
        try {
            
$this->pdo = new PDO($dsnself::$userself::$pwdself::$options);
        } catch (
Exception $e) {
            die(
"<p>Could not connect to the database:<br/>{$e}</p>");
        }
    }
    private function 
execute($sql$params null): PDOStatement
    
# Privat funktion som används av query och queryAll. Exekverar en SQL-sats.
    
{
        
//echo "<p>{$sql}</p>";
        
$stm $this->pdo->prepare($sql);
        if (isset(
$params))
            
$stm->execute($params);
        else
            
$stm->execute();
        return 
$stm;
    }

    public function 
query($sql$params null): mixed
    
# Gör en query och kör fetch.
    
{
        return 
$this->execute($sql$params)->fetch(PDO::FETCH_ASSOC);
    }

    public function 
queryAll($sql$params null): array
    
# Gör en query och kör fetchAll.
    
{
        return 
$this->execute($sql$params)->fetchAll(PDO::FETCH_ASSOC);
    }

    public function 
getLastId(): bool|string
    
# Ger den sista PK-id som skapades på databasen.
    
{
        return 
$this->pdo->lastInsertId();
    }
}

$db = new DatabaseConnection(); # Etablerar en anslutning till databasen.
# Detta ser även till att alla filer som kör require på denna fil skapar en databasanslutning.

abstract class DatabaseTable
    
# En klass jag har skapat för att göra kopplingen mellan databasen och PHP-koden enklare.
    # Klassen i sig är abstract och extendas av andra klasser som motsvarar tabeller i databasen.
    # Detta leder till att en ny instans av klassen som ärver DatabaseTable automatiskt ansluts till databasen.
    # Läs mer om den på WordPress.
{
    protected static 
$dbPrimaryKey# PK för tabellen.
    
protected static $dbTable# Tabellens namn på databasen.
    
protected static $dbReadTable# Nullable namn på tabell(vyn) som den läser ifrån.
    # Används ifall den ska läsa från en vy medans den skriver på själva tabellen.

    # Notera att dessa tre är static, men de överskrids av klassen som ärver DatabaseTable.

    
private static $relations = array(); # En static array som kopplar ihop relationerna mellan flera tabeller.

    
public $id# ID(PK)-värdet för detta objektet som motsvarar databaskolumnen.
    
private $dbData null# Variabel som senare blir en array, vilket innehåller all data som databasen har att ge.

    
public static function AddRelation($table1$table2$foreignKey$propertyName$order null): void
    
# Skapar en property i $table1 vilket låter en instans av den klassen komma åt alla instanser av $table2 som har en foreign key 
    # med värdet av instansen i $table1.

    # Exempelvis om variabel $user1 är av klass User och User har en 1:N relation med Account, ska man kunna köra
    # $user1->accounts för att få en lista på alla Account-instanser.
    
{
        
array_push(self::$relations, array(
            
"table1" => $table1,
            
"table2" => $table2,
            
"foreignKey" => $foreignKey,
            
"propertyName" => $propertyName,
            
"order" => $order
        
));
    }

    public function 
__construct($id)
    
# När en ny instans skapas sätts ID-värdet.
    
{
        
$this->id $id;
    }

    public function 
__get($name): mixed
    
# Här kommer den krångligaste funktionen i klassen. Den ser till att när man indexerar properties i ärvda instanser av DatabaseTable
    # laddas datan från databasen ifall den inte redan är laddad. Detta gör det mycket smidigt att läsa data från databasen och använda
    # funktioner som kommer med klasserna.
    
{
        
# I __get är $name i det här fallet den indexen man försöker indexera objektet med.
        # Om $user = new User() där class User extends DatabaseTable skulle jag exempelvis
        # kunna köra $user->username och då kommer $name="username"
        # Det som funktionen returnerar blir värdet på $user->username.

        
global $db;
        
$readTable $this::$dbReadTable ?? $this::$dbTable# Läser av dbReadTable om den finns, annars dbTable.
        
$sqlQuery "{$readTable} WHERE {$this::$dbPrimaryKey}=:id"# Del av flera SQL-satser.

        
if ($this->dbData === null# Ifall dbData är null, alltså om ingen data redan är laddad körs en SELECT *, vilket laddar all data.
            
$this->dbData $db->query("SELECT * FROM {$sqlQuery}", array("id" => $this->id));

        
# Nu kan vi kolla ifall propertyn vi indexerar finns i dbData.
        
if (isset($this->dbData[$name]))
            return 
$this->dbData[$name];

        
# Ifall den inte gör det går vi igenom alla relationer och ser ifall vi har definierat en relation som en property.
        
foreach (self::$relations as $relation) {
            
# Kollar ifall relationen finns.
            
if ($relation["table1"] != $this::class || $relation["propertyName"] != $name)
                continue;

            
# Om en relation finns, exempelvis $user->accounts, kommer en lista på alla kolumner av account som har matchande userId
            # läggas till i listan som ett objekt av Account.
            
$instances = array(); # En ny lista för alla instanser.
            
$table2 $relation["table2"]; # Andra tabellen i relationen, typ: klass.
            
$order = isset($relation["order"]) ? "ORDER BY {$relation['order']}""# Ifall det finns en ORDER BY.

            # Här kör vi själva satsen. Vi hämtar alla kolumner i andra tabellen där FK matchar PK i vårt objekt.
            
$dbInstances $db->queryAll(
                
"SELECT * FROM {$table2::$dbReadTable}
            WHERE 
{$relation['foreignKey']}=:id {$order}",
                array(
"id" => $this->id)
            );

            
# Resultatet itereras igenom och skapar objekt av den klassen. Exempelvis när $user->accounts indexeras skapas
            # en lista av Account.
            
foreach ($dbInstances as $instanceData) {
                
$instanceId $instanceData[$table2::$dbPrimaryKey]; # PK för instansen
                
$instance = new $relation["table2"]($instanceId); # Nytt objekt av instansens klass.
                
foreach ($instanceData as $index => $value) { # Alla properties loopas igenom och läggs i vår instans.
                    
$instance->{$index} = $value;
                }
                
array_push($instances$instance); # Instansen läggs till i listan av instanser.
            
}
            
$this->{$name} = $instances# Till sist sparas alla instanser i vårt objekt med index $name, exempelvis "accounts".
            
return $instances;
        }

        
# Här behövde jag lägga in värdet rakt i SQL-satsen, utan att lägga till det i PDO-prepare parametrarna.
        # Det bör inte vara ett problem, eftersom $name är alltid förprorgammerad, och beror aldrig på användarinput.

        # Ifall inget värde hittats än är det möjligt att det inte är laddat. Därför försöker kolumnen laddas in.
        
$value $db->query("SELECT `{$name}` FROM {$sqlQuery}", array("id" => $this->id));
        if (
$value) {
            
$this->dbData[$name] = $value[$name];
            return 
$value[$name];
        }

        
# Ifall ingenting hittas får man en varning(om man inte redan fått en error av databasförfrågan kort innan).
        
trigger_error("Undefined property: $name"E_USER_WARNING);
        return 
null;
    }

    public function 
__set($name$value): void
    
# Den här funktionen körs när man försöker ändra ett värde som inte finns i objektet.
    # Det gör det möjligt att i vissa fall skapa ett tomt objekt och fylla den med data ifall man redan har
    # gjort en databasförfrågan.
    
{
        
# Om $this->dbData === null innebär det att raden i databasen kommer laddas in när man indexerar en property i objektet.
        # Ifall man ändrar ett värde(exempelvis en funktion som hämtar användare utifrån användarnamn, som redan har hämtat nödvändig data från databasen)
        # kommer den inte ladda in datan igen nästa gång man indexerar någit i objektet.
        
if (!isset($this->dbData))
            
$this->dbData = array(); # Skapar en array ifall det inte redan finns.
        
$this->dbData[$name] = $value# Sätter värdet i dbData till det nya värdet.
    
}

    public function 
updateValues($values): void
    
# Funktion som uppdaterar alla givna värden på databasen till motsvarande värden i objektet.
    
{
        global 
$db;
        
$newValues = array(); # Lista på alla databaskolumner som ska uppdateras.
        
$params = array("id" => $this->id); # Lista på parametrar som ska användas i SQL-satsen.
        # Vi går igenom varje värde som ska uppdateras.
        
foreach ($values as $value) {
            
# För varje värde lägger vi till i newValues, exempelvis "`username` = :parameter_username".
            
array_push($newValues"`{$value}` = :parameter_{$value}");
            
# Värdet i objektet på den kolumn som ska uppdateras läggs till som parameter.
            
$params["parameter_{$value}"] = $this->{$value};
        }
        
$stringValues implode(","$newValues); # Alla newValues omvandlas till en sträng med komma mellan.
        # SQL-satsen körs så att uppdateringen sker.
        
$db->query("UPDATE {$this::$dbTable} SET {$stringValues} WHERE {$this::$dbPrimaryKey}=:id"$params);
    }

    public function 
rereadValues(): void
    
# I vissa fall har en rad på databasen ändrats efter att alla värden har laddats in.
    # Då kör man den här funktionen för att tvinga att datan laddas om nästa gång den indexeras.
    
{
        
$this->dbData null;
    }
}
?>