diff options
| author | winter | 2024-12-09 21:31:54 +0000 |
|---|---|---|
| committer | winter | 2024-12-09 21:31:54 +0000 |
| commit | 4c7bde1400820f36caf8b2a5374007384c3018f3 (patch) | |
| tree | 83ed26ff368117b8e392f7bc28736482077fc2f4 /WpfTest/Model.php | |
| parent | 50edbd3907c26a2d4d616421de9bb51a4d083c1b (diff) | |
rename to Digitigrade / PawPub
:3
Diffstat (limited to 'WpfTest/Model.php')
| -rw-r--r-- | WpfTest/Model.php | 219 |
1 files changed, 0 insertions, 219 deletions
diff --git a/WpfTest/Model.php b/WpfTest/Model.php deleted file mode 100644 index 53b2d83..0000000 --- a/WpfTest/Model.php +++ /dev/null @@ -1,219 +0,0 @@ -<?php -namespace WpfTest; - -use WpfTest\Model\FetchableModel; - -abstract class Model { - private bool $saved = false; - - protected function setOwnerId(int $id) { - } - - /** - * @return ?string a condition for a WHERE clause if this object already - * exists in the database, or null otherwise - */ - protected function getUpdateWhereClause(\PDO $db): ?string { - if (isset($this->id) && static::find($this->{'id'}) != null) { - return 'id = ' . $this->{'id'}; - } - return null; - } - - /** - * Converts a field or type name into the version used in the database - */ - 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); - } - - /** - * Converts a database column name into the corresponding field name - */ - 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) { - $pName = $p->getName(); - if (!isset($this->{$pName})) - continue; - // we need to determine the plain value to insert ... - $value = $this->{$pName}; - $discarded = false; - if ($value instanceof \DateTimeInterface) { - // datetime objects should be turned into iso 8601 timestamps - $value = $value->format('c'); - } elseif ($value instanceof Model) { - // models should generally be turned into their id if there is one or removed if not - if (property_exists($value, 'id') && isset($value->id)) - $value = $value->{'id'}; // doing it like this to shut up the warning (lol) - else - $discarded = true; - } elseif (is_array($value)) { - // arrays ought to be handled separately when saving (different db table) - $discarded = true; - } - if (!$discarded) - $result[self::mangleName($pName)] = $value; - } - 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 a backed enum we can find the enum case with the same value - $obj->{$name} = $typeClass::from($value); - } 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 referenced fields if needed - $obj->hydrate(); - $obj->saved = true; - return $obj; - } - - /** - * Initialises fields of the model that contain referenced / foreign keyed objects - * which couldn't be initialised automatically - * @return void - */ - protected function hydrate() { - } - - 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]); - } - - /** - * Inserts or updates the model's corresponding database record. - * Also sets the model's id field if it has one. - */ - public function save() { - // figure out all the parameters and such for the query - $paramData = $this->toDbRow(); - $columnNames = array_keys($paramData); - $parameterNames = array_map(function (string $column) { - return ":$column"; - }, $columnNames); - $columns = implode(', ', $columnNames); - $parameters = implode(', ', $parameterNames); - - $classNameParts = explode('\\', static::class); - $className = $classNameParts[count($classNameParts) - 1]; - $tableName = self::mangleName($className); - - $db = Db::getInstance()->getPdo(); - - $whereClause = $this->getUpdateWhereClause($db); - $shouldUpdate = $whereClause != null; - $stmt = $db->prepare( - $shouldUpdate - ? "UPDATE $tableName SET ($columns) = ($parameters) WHERE $whereClause" - : "INSERT INTO $tableName ($columns) VALUES ($parameters)" - ); - - // pdo is really stupid and casts `false`/0 to an empty string, which breaks the query - // so i have to cast `false`s and `0`s manually. hooray - // i also have to convert backed enums to their value because they dont implement Stringable - $paramData = array_map(function ($item) { - if ($item === false || $item === 0) - return '0'; - if (is_a($item, \BackedEnum::class)) - return $item->value; - return $item; - }, $paramData); - // actually save it !!!! yay - $stmt->execute($paramData); - - if (property_exists($this, 'id')) { - $this->id = $shouldUpdate - ? static::findWhere($whereClause, [])->id - : (int) $db->lastInsertId(); - } - - // recursively save inner models - // but not fetchable ones because they should really be saved separately - $reflProps = (new \ReflectionObject($this))->getProperties(); - foreach ($reflProps as $reflProp) { - assert($reflProp instanceof \ReflectionProperty); - $reflType = $reflProp->getType(); - $propName = $reflProp->getName(); - if (!($reflType instanceof \ReflectionNamedType)) - continue; - $type = $reflType->getName(); - if (is_subclass_of($type, Model::class) && !is_subclass_of($type, FetchableModel::class)) { - if (property_exists($this, 'id')) - $this->{$propName}->setOwnerId($this->id); - $this->{$propName}->save(); - } - } - - $this->saved = true; - } -}
\ No newline at end of file |
