# M Model (optioneel)

### Wat is een model?

Een model is een object die gerelateerd is aan een database tabel.
Voorbeeld model User (let op naam is in enkelvoud en met hoofdletter) heeft in de database de tabel 'users'

Omdat we met models object georienteerd programmeren is het mogelijk om overerving te gebruiken. Elke model moet kunnen worden aangemaakt, opgeslagen, gewijzigd etc.
Dit kunnen we één keer doen in een Model class. Deze Model class kunnen we als blauwdruk voor onze User model of Bericht model gebruiken.

### Aanmaken van een Model

Het is netjes om al je Models in een directory 'models' te zetten. Dit is overzichtelijk, maar niet noodzakelijk.

Een voorbeeld van een user Model

    <?php

    class User extends Model
    {
        protected $table = "users";
    }

Je ziet dat er 'extends Model' achter staat. Dat neemt alle eigenschappen van Model over.

Omdat je alle bestanden moet kunnen gebruiken. Moet je deze wel allemaal toevoegen aan je index.php onder de require van Database.php.

    require "Model.php";
    require "models/User.php";
    require "models/Post.php";

### Model voor queries

Je kan met je Model queries bouwen. Hieronder een voorbeeldje

    //query op users tabel
    $users = (new User())
        ->where('voornaam', 'LIKE', '%p%') //voornaam LIKE '%p%'
        ->where('role', 'user') // role = 'user'
        ->whereNull('tussenvoegsel') // tussenvoegsel IS NULL
        ->get(); //uitvoeren van de query
    dd($users);

### Niet alle resultaten ophalen (limit)

    $users = (new User)
        ->limit(3)
        ->get();

Zal maximaal 3 users tonen.

### Alle users ophalen

    $users = (new User)->all();
    dd($users);

### User aanmaken

    //aanmaken van een user
    $user = (new User())->create([
        'name' => 'Piet Puck',
        'email' => 'testttw@mail.nl',
        'password' => password_hash('password', PASSWORD_BCRYPT)
    ]);
    dd($user);

### Zoeken van een user op id

    //zoeken van een user op id
    $user = (new User)->find(2);
    dd($user);

### Gegevens van een opgehaalde user tonen

    $user = (new User)->find(2);
    //voornaam van de gebruiker op het scherm schrijven
    echo $user->voornaam;

### Gegevens van een user aanpassen

    $user = (new User)->find(2);

    //naam wijzigen
    $user->name = 'John Doe';

    //opslaan in database (alleen als er werkelijk iets gewijzigd is)
    $user->save();

### User verwijderen

    //user met id=6 ophalen
    $user = (new User)->find(6);
    //de zojuist opgehaalde user verwijderen uit de database
    $user->delete();

### Zelf eigenschappen toevoegen

Bij een User zullen we vaak de volledige naam op het scherm willen tonen. We kunnen hier een methode toevoegen aan User.php

    <?php

    class User extends Model
    {
        protected $table = "users";

        public function name(){
            return $this->voornaam." ".
                $this->tussenvoegsel." ".
                $this->achternaam;
        }
    }

Let op dat er voor bovenstaande code wel velden in de database moeten bestaan met de namen 'voornaam', 'tussenvoegsel' en 'achternaam' deze velden zijn in onze default database niet aanwezig. De code zal dan ook fouten opleveren bij aanroep.

Nu kun je in je code een user selecteren en snel de naam op het scherm schrijven

    $user = (new User)->find(4);
    echo $user->name();

### Debuggen

Soms wil je graag even zien welke queries er allemaal worden uitgevoerd. Dat kan door de dumpQuerLog() methode te gebruiken. Dit zal alle uitvoerde queries op het scherm schrijven en het script verder afbreken.

    $user = (new User)->find(2);
    //uitgevoerde queries bekijken
    $user->dumpQueryLog();

### De Model.php

Dit script is de 'parent' en vrij ingewikkeld qua code. Maar dat is niet erg, want je hoeft dit ook niet allemaal te snappen. Als je bovenstaande kan gebruiken weet je genoeg.

Sla onderstaande script op als Model.php en plaats in je root directory

    <?php

    class Model
    {
        //Private parameters zijn kan je alleen binnen het object gebruiken
        private $query_log = [];
        private $query = '';
        private $bind_params = [];
        private $limit = '';
        private $where = [];
        private $original = [];
        //deze kan je overschrijven in je child class
        protected $table = '';
        protected $primaryKey = 'id';

        //ophalen van één object dmv de primaire sleutel
        public function find($id): self
        {
            $db = new Database();
            $this->setQuery("SELECT * FROM {$this->getTable()} WHERE `{$this->primaryKey}` = ?");
            $this->original = $db->query($this->query, [$id])->fetch();
            if ($this->original) {
                foreach ($this->original as $k => $v) {
                    $this->$k = $v;
                }
            } else {
                echo "id=$id not found";
                die();
            }
            return $this;
        }

        //Object opslaan in de database
        public function save(): self
        {
            if ($this->original[$this->primaryKey]) {
                $db = new Database();
                $update = [];
                $cols = [];
                foreach ($this->original as $k => $v) {
                    if ($this->$k != $v) {
                        $cols[] = "`$k` = ?";
                        $update[] = $this->$k;
                    }
                }
                if (!empty($cols)) {
                    $update[] = $this->original[$this->primaryKey];
                    $this->setQuery("UPDATE {$this->getTable()} SET " .
                        implode(",", $cols) .
                        " WHERE `{$this->primaryKey}`=?"
                    );
                    $db->query($this->query, $update);
                    $this->find($this->original[$this->primaryKey]);
                }
            }
            return $this;
        }

        //nieuw object aanmaken (en opslaan in de database)
        public function create($array): self
        {
            $placeholders = [];
            $cols = [];
            $values = [];
            foreach ($array as $k => $v) {
                $cols[] = "`" . $k . "`";
                $values[] = $v;
                $placeholders[] = " ?";
            }
            if (!empty($cols)) {
                $db = new Database();
                $this->setQuery("INSERT INTO {$this->getTable()} (" . implode(",", $cols) . ") " .
                    "VALUES (" . implode(",", $placeholders) . ")");
                $db->query($this->query, $values);
                $id = $db->lastInsertId();
                $this->find($id);
            }
            return $this;
        }

        //Object verwijderen
        public function delete()
        {
            if ($this->original[$this->primaryKey]) {
                $db = new Database();
                $this->setQuery("DELETE FROM {$this->getTable()} WHERE `{$this->primaryKey}` = ?");
                $db->query($this->query, [$this->original[$this->primaryKey]]);
                $this->destruct();
            }
            return null;
        }

        //alle objecten ophalen
        public function all()
        {
            $db = new Database();
            $this->setQuery("SELECT * FROM {$this->getTable()}");
            return $db->query($this->query)->fetchAll();
        }

        //bouwen van een query op de betreffende tabel
        public function where(...$args): self
        {
            if (!isset($args[2])) {
                $value = $args[1];
                $operator = "=";
            } else {
                $value = $args[2];
                $operator = $args[1];
            }
            $this->where[] = ['column' => $args[0], 'value' => $value, 'operator' => $operator];
            return $this;
        }

        //bouwen van een query op de betreffende tabel
        public function whereNull($col): self
        {
            $this->where[] = ['column' => $col, 'operator' => ' IS NULL'];
            return $this;
        }

        //bouwen van een query op de betreffende tabel
        public function whereNotNull($col): self
        {
            $this->where[] = ['column' => $col, 'operator' => ' IS NOT NULL'];
            return $this;
        }

        public function limit(...$args): self
        {
            if (isset($args[1])) {
                $this->limit = " LIMIT {$args[0]},{$args[1]}";
            } else {
                $this->limit = " LIMIT {$args[0]}";
            }
            return $this;
        }

        //Werkelijk uitvoeren van de query
        public function get(): array
        {
            $this->buildQuery();
            $db = new Database();
            return $db->query($this->query, $this->bind_params)->fetchAll();
        }

        //in plaats van get() kan je deze gebruiken om de querie te dumpen ipv uitvoeren
        public function dumpQuery(): void
        {
            $this->buildQuery();
            dd($this->query);
        }

        //alle uitgevoerde queries dumpen
        public function dumpQueryLog(): void
        {
            dd($this->query_log);
        }

        //niets mee doen
        protected function getTable(): string
        {
            return $this->table ?? rtrim(strtolower(get_class($this)), "s");
        }

        //om een query log op te bouwen (wordt intern gebruikt)
        private function setQuery($query)
        {
            $this->query = $query;
            $this->query_log[] = $query;
        }

        //bouwen van een query aan de hand van where
        private function buildQuery(): void
        {
            $where = [];
            $this->bind_params = [];
            foreach ($this->where as $v) {
                $where[] = "`{$v['column']}` {$v['operator']}" . (isset($v['value']) ? ' ?' : '');
                if ($v['value'] ?? null) {
                    $this->bind_params[] = $v['value'];
                }
            }
            $this->setQuery("SELECT * FROM {$this->getTable()} " .
                (!empty($where) ? ' WHERE ' . implode(" AND ", $where) : '') .
                $this->limit
            );
        }

        //object leegmaken
        private function destruct(): void
        {
            foreach ($this->original as $k => $v) {
                unset($this->$k);
            }
            $this->original = [];
        }
    }