aboutsummaryrefslogtreecommitdiffhomepage
path: root/WpfTest/Model.php
diff options
context:
space:
mode:
Diffstat (limited to 'WpfTest/Model.php')
-rw-r--r--WpfTest/Model.php219
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