aboutsummaryrefslogtreecommitdiffhomepage
path: root/WpfTest/Model.php
blob: c006ffd6c3dfe588355c56075d165e59c2a0e088 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<?php
namespace WpfTest;

abstract class Model {
    private static function mangleName(string $name): string {
        $chars = str_split($name);
        $chars[0] = strtolower($chars[0]);
        for ($i = 0; $i < count($chars); $i++) {
            $c = $chars[$i];
            if (ctype_upper($c)) {
                array_splice($chars, $i, 1, ['_', strtolower($c)]);
            }
        }
        return implode($chars);
    }

    private static function unmangleName(string $name): string {
        $chars = str_split($name);
        for ($i = 0; $i < count($chars); $i++) {
            if ($chars[$i] == '_') {
                array_splice($chars, $i, 2, [strtoupper($chars[$i + 1])]);
            }
        }
        return implode($chars);
    }

    public function toDbRow(): array {
        $refl = new \ReflectionObject($this);
        $props = $refl->getProperties();
        $result = [];
        foreach ($props as $p) {
            $result[self::mangleName($p->getName())] = $p->getValue();
        }
        return $result;
    }

    public static function fromDbRow(array|false $columns): ?static {
        if ($columns === false)
            return null;
        // create the model
        $obj = new static();
        $refl = new \ReflectionObject($obj);
        // for each field of the db row ...
        foreach (array_keys($columns) as $name) {
            $value = $columns[$name];
            $name = self::unmangleName($name); // guess the appropriate field in the model
            try {
                // try to assign it directly (works for simple values)
                $obj->{$name} = $value;
            } catch (\TypeError $e) {
                // if it's not the right type we have to try to wrangle it into the right one
                // there's a few strategies for this
                $type = $refl->getProperty($name)->getType();
                if ($type == null || !is_a($type, \ReflectionNamedType::class))
                    throw $e;
                $typeClass = $type->getName();
                if (enum_exists($typeClass)) {
                    // if it's an enum we can find the enum case with the same name
                    $obj->{$name} = (new \ReflectionEnum($typeClass))->getCase($value)->getValue();
                } elseif (is_subclass_of($typeClass, Model::class) && is_int($value)) {
                    // if it's another model we can try to look it up by id automatically
                    $obj->{$name} = $typeClass::find($value);
                } else {
                    // otherwise try to instantiate the correct class and pass it the simple value
                    $obj->{$name} = new $typeClass($value);
                }
            }
        }
        // set up any extra-complicated fields if needed
        $obj->hydrate($columns);
        return $obj;
    }

    /**
     * Initialises fields of the model that contain more complex objects
     * @param array $row the row of the database
     * @return void
     */
    protected function hydrate(array $row) {
    }

    public static function findWhere(string $whereClause, array $parameters): ?static {
        $classNameParts = explode('\\', static::class);
        $className = $classNameParts[count($classNameParts) - 1];
        $tableName = self::mangleName($className);
        $stmt = Db::getInstance()->getPdo()->prepare("SELECT * FROM $tableName WHERE $whereClause LIMIT 1");
        $stmt->execute($parameters);
        return static::fromDbRow($stmt->fetch(\PDO::FETCH_ASSOC));
    }

    public static function findAllWhere(string $whereClause, array $parameters): array {
        $classNameParts = explode('\\', static::class);
        $className = $classNameParts[count($classNameParts) - 1];
        $tableName = self::mangleName($className);
        $stmt = Db::getInstance()->getPdo()->prepare("SELECT * FROM $tableName WHERE $whereClause");
        $stmt->execute($parameters);
        return array_map(function ($row) {
            return static::fromDbRow($row);
        }, $stmt->fetchAll(\PDO::FETCH_ASSOC));
    }

    public static function find(int $id): ?static {
        return static::findWhere('id = ?', [$id]);
    }
}