From 7b0d42f8dfc0f04295c6f68d6035719b2e6ba326 Mon Sep 17 00:00:00 2001 From: KingMaj0r Date: Tue, 6 Aug 2024 13:42:20 +0700 Subject: [PATCH] Inital commit. --- .gitignore | 1 + LICENSE | 21 ++++ composer.json | 18 ++++ src/Database/Blueprint.php | 73 ++++++++++++++ src/Database/Migration.php | 9 ++ src/Database/Migrations/CreatePulseFrame.php | 24 +++++ src/Database/Models/PulseFrameModel.php | 11 +++ src/Database/Seeder.php | 10 ++ src/Database/Seeders/InsertPulseFrame.php | 38 ++++++++ src/Facades/Database.php | 97 +++++++++++++++++++ src/Facades/Schema.php | 88 +++++++++++++++++ src/Methods/Static/Database/All.php | 26 +++++ src/Methods/Static/Database/CountByColumn.php | 34 +++++++ src/Methods/Static/Database/Delete.php | 14 +++ src/Methods/Static/Database/Execute.php | 18 ++++ src/Methods/Static/Database/Find.php | 80 +++++++++++++++ src/Methods/Static/Database/Insert.php | 58 +++++++++++ src/Methods/Static/Database/Query.php | 44 +++++++++ src/Methods/Static/Database/Update.php | 31 ++++++ src/Model.php | 12 +++ 20 files changed, 707 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 composer.json create mode 100644 src/Database/Blueprint.php create mode 100644 src/Database/Migration.php create mode 100644 src/Database/Migrations/CreatePulseFrame.php create mode 100644 src/Database/Models/PulseFrameModel.php create mode 100644 src/Database/Seeder.php create mode 100644 src/Database/Seeders/InsertPulseFrame.php create mode 100644 src/Facades/Database.php create mode 100644 src/Facades/Schema.php create mode 100644 src/Methods/Static/Database/All.php create mode 100644 src/Methods/Static/Database/CountByColumn.php create mode 100644 src/Methods/Static/Database/Delete.php create mode 100644 src/Methods/Static/Database/Execute.php create mode 100644 src/Methods/Static/Database/Find.php create mode 100644 src/Methods/Static/Database/Insert.php create mode 100644 src/Methods/Static/Database/Query.php create mode 100644 src/Methods/Static/Database/Update.php create mode 100644 src/Model.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a725465 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c3e72f0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 kingmaj0r + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f7fbc1a --- /dev/null +++ b/composer.json @@ -0,0 +1,18 @@ +{ + "name": "pulseframe/database", + "description": "PulseFrame's database functionality for php.", + "type": "library", + "license": "MIT", + "autoload": { + "psr-4": { + "PulseFrame\\": "src/" + } + }, + "authors": [ + { + "name": "KingMaj0r" + } + ], + "minimum-stability": "stable", + "require": {} +} diff --git a/src/Database/Blueprint.php b/src/Database/Blueprint.php new file mode 100644 index 0000000..9cbe1f1 --- /dev/null +++ b/src/Database/Blueprint.php @@ -0,0 +1,73 @@ +table = $tableName; + } + + public function id($type = 'int') + { + $this->addColumn('id', $type, ['primary' => true]); + return $this; + } + + public function text($name) + { + $this->addColumn($name, 'text'); + return $this; + } + + public function timestamp($name = 'timestamp') + { + $this->addColumn($name, 'timestamp'); + return $this; + } + + public function nullable() + { + $this->setAttribute('nullable', true); + return $this; + } + + public function primary() + { + $this->setAttribute('primary', true); + return $this; + } + + public function unique() + { + $this->setAttribute('unique', true); + return $this; + } + + protected function addColumn($name, $type, $attributes = []) + { + $this->columns[$name] = array_merge(['type' => $type], $attributes); + } + + protected function setAttribute($attribute, $value) + { + $lastColumn = array_key_last($this->columns); + if ($lastColumn !== null) { + $this->columns[$lastColumn][$attribute] = $value; + } + } + + public function getColumns() + { + return $this->columns; + } + + public function getTable() + { + return $this->table; + } +} diff --git a/src/Database/Migration.php b/src/Database/Migration.php new file mode 100644 index 0000000..174e00c --- /dev/null +++ b/src/Database/Migration.php @@ -0,0 +1,9 @@ +id('uuid'); + $table->text('data')->nullable(); + $table->timestamp()->nullable(); + }); + } + + public function down() + { + Schema::dropTable('pulseframe'); + } +} diff --git a/src/Database/Models/PulseFrameModel.php b/src/Database/Models/PulseFrameModel.php new file mode 100644 index 0000000..e1072ea --- /dev/null +++ b/src/Database/Models/PulseFrameModel.php @@ -0,0 +1,11 @@ + '328ef6b3-68d0-4f47-9ffe-7529d5d392b3', + 'data' => json_encode([ + 'name' => Env::get('app.name'), + 'version' => Config::get('app', 'version'), + 'stage' => Config::get('app', 'stage'), + 'key' => Env::get('app.key') + ]), + 'timestamp' => date('Y-m-d H:i:s') + ]); + } else { + exit; + } + } +} diff --git a/src/Facades/Database.php b/src/Facades/Database.php new file mode 100644 index 0000000..f87ad67 --- /dev/null +++ b/src/Facades/Database.php @@ -0,0 +1,97 @@ +getAttribute(PDO::ATTR_DRIVER_NAME) !== $databaseConfig[$connectionName]['driver']) { + $host = $databaseConfig[$connectionName]['host']; + $username = $databaseConfig[$connectionName]['username']; + $password = $databaseConfig[$connectionName]['password']; + $database = $databaseConfig[$connectionName]['database']; + $port = $databaseConfig[$connectionName]['port']; + $driver = $databaseConfig[$connectionName]['driver']; + + try { + self::$conn[$connectionName] = new PDO("$driver:host=$host;dbname=$database;port=$port", $username, $password); + self::$conn[$connectionName]->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch (\PDOException $e) { + throw new \Exception("Connection failed: " . $e->getMessage()); + } + } + + return self::$conn[$connectionName]; + } + + public static function getModelInstance($model) + { + if (is_string($model) && class_exists($model)) { + $className = $model; + } elseif (is_string($model)) { + $className = "\\App\\Models\\" . $model; + } elseif (is_object($model) && method_exists($model, 'class')) { + $className = get_class($model); + } + + if (!class_exists($className)) { + throw new \Exception("Model not found: {$className}"); + } + + return new $className(); + } + + public static function __callStatic($method, $args) + { + $methodClass = "\\PulseFrame\\Methods\\Static\\Database\\" . ucfirst($method); + if (class_exists($methodClass) && method_exists($methodClass, 'handle')) { + $instance = new $methodClass(...$args); + if (method_exists($instance, $method)) { + return $instance->$method(...$args); + } elseif (method_exists($instance, 'handle')) { + return $instance->handle(...$args); + } else { + throw new \BadMethodCallException("Method {$method} does not exist in {$methodClass}."); + } + } else { + throw new \BadMethodCallException("Method {$method} does not exist."); + } + } + + public function __call($method, $args) + { + $methodClass = "\\PulseFrame\\Methods\\Static\\Database\\" . ucfirst($method); + if (class_exists($methodClass)) { + $instance = new $methodClass($this->instance); + if (method_exists($instance, $method)) { + return $instance->$method(...$args); + } elseif (method_exists($instance, 'handle')) { + return $instance->handle($instance, ...$args); + } else { + throw new \BadMethodCallException("Method {$method} does not exist in {$methodClass}."); + } + } else { + throw new \BadMethodCallException("Class {$methodClass} does not exist."); + } + } + + protected static function resolveConnectionName($model) + { + if (is_string($model)) { + $connectionName = $model; + } elseif (is_object($model) && property_exists($model, 'connection')) { + $connectionName = $model->connection; + } else { + throw new \InvalidArgumentException("Invalid model type. Must be an object with a 'connection' property or a string."); + } + return $connectionName; + } +} diff --git a/src/Facades/Schema.php b/src/Facades/Schema.php new file mode 100644 index 0000000..8139632 --- /dev/null +++ b/src/Facades/Schema.php @@ -0,0 +1,88 @@ +getColumns(); + $columnsSql = []; + + foreach ($columns as $name => $attributes) { + $type = $attributes['type']; + $sql = "\"$name\" $type"; + + if (isset($attributes['nullable']) && $attributes['nullable']) { + $sql .= " NULL"; + } else { + $sql .= " NOT NULL"; + } + + if (isset($attributes['primary']) && $attributes['primary']) { + $sql .= " PRIMARY KEY"; + } + + if (isset($attributes['unique']) && $attributes['unique']) { + $sql .= " UNIQUE"; + } + + $columnsSql[] = $sql; + } + + $columnsString = implode(", ", $columnsSql); + $sql = "CREATE TABLE IF NOT EXISTS \"{$tableName}\" ({$columnsString})"; + + Database::Execute(self::$connection, $sql); + } + + public static function dropTable($tableName) + { + $sql = "DROP TABLE IF EXISTS \"{$tableName}\""; + Database::Execute(self::$connection, $sql); + } + + public static function insert($tableName, array $data) + { + if (empty($data)) { + throw new \InvalidArgumentException('Data array cannot be empty.'); + } + + $columns = array_keys($data); + + $placeholders = array_map(function ($col) { + return ':' . $col; + }, $columns); + + $sql = "INSERT INTO \"{$tableName}\" (" . implode(", ", array_map(function ($col) { + return "\"$col\""; + }, $columns)) . ") VALUES (" . implode(", ", $placeholders) . ")"; + + $params = []; + foreach ($data as $key => $value) { + $params[':' . $key] = $value; + } + + try { + $result = Database::Query(self::$connection, $sql, $params); + } catch (\Exception $e) { + throw new \InvalidArgumentException("Failed to insert record: " . $e->getMessage()); + } + + return $result; + } +} diff --git a/src/Methods/Static/Database/All.php b/src/Methods/Static/Database/All.php new file mode 100644 index 0000000..23b0be3 --- /dev/null +++ b/src/Methods/Static/Database/All.php @@ -0,0 +1,26 @@ +table; + $attributes = []; + + if ($whereClause !== null) { + $conditions = []; + foreach ($whereClause as $key => $value) { + $conditions[] = "$key = :$key"; + $attributes[":$key"] = $value; + } + $sql .= " WHERE " . implode(" AND ", $conditions); + } + + return Database::Query($instance, $sql, $attributes, true); + } +} \ No newline at end of file diff --git a/src/Methods/Static/Database/CountByColumn.php b/src/Methods/Static/Database/CountByColumn.php new file mode 100644 index 0000000..e4b4b11 --- /dev/null +++ b/src/Methods/Static/Database/CountByColumn.php @@ -0,0 +1,34 @@ +table . " WHERE " . $instance->primaryKey . " = :id"; + $params = [":id" => $id]; + } else { + if (empty($attributes) || !is_array($attributes)) { + throw new InvalidArgumentException('Attributes must be a non-empty array when $id is null.'); + } + + $whereClauses = []; + $params = []; + foreach ($attributes as $key => $value) { + $whereClauses[] = "$key = :$key"; + $params[":$key"] = $value; + } + $query = "SELECT COUNT(*) AS count FROM " . $instance->table . " WHERE " . implode(" AND ", $whereClauses); + } + + $result = Database::query($instance, $query, $params); + + return (int) $result['count']; + } +} \ No newline at end of file diff --git a/src/Methods/Static/Database/Delete.php b/src/Methods/Static/Database/Delete.php new file mode 100644 index 0000000..18c16db --- /dev/null +++ b/src/Methods/Static/Database/Delete.php @@ -0,0 +1,14 @@ +table . " WHERE " . $instance->primaryKey . " = :id", ["id" => $id]); + } +} diff --git a/src/Methods/Static/Database/Execute.php b/src/Methods/Static/Database/Execute.php new file mode 100644 index 0000000..74d0d5f --- /dev/null +++ b/src/Methods/Static/Database/Execute.php @@ -0,0 +1,18 @@ +prepare($sql); + foreach ($params as $key => $value) { + $statement->bindValue(is_numeric($key) ? $key+1 : $key, $value); + } + return $statement->execute(); + } +} diff --git a/src/Methods/Static/Database/Find.php b/src/Methods/Static/Database/Find.php new file mode 100644 index 0000000..bd8eb5d --- /dev/null +++ b/src/Methods/Static/Database/Find.php @@ -0,0 +1,80 @@ +willReturn = $willReturn; + self::$model = $model; + self::$fields = $fields; + } + + public function Execute() { + return $this->handle(); + } + + public function All() { + $this->isAll = true; + return $this; + } + + public function Like() + { + $this->willReturn = true; + self::$isLike = true; + return $this; + } + + public function handle() + { + if (is_array(self::$fields)) { + $conditions = []; + foreach (self::$fields as $key => $value) { + if (self::$isLike) { + $conditions[] = "$key::text LIKE :$key"; + $this->attributes[":$key"] = '%' . $value . '%'; + } else { + $conditions[] = "$key::text = :$key"; + $this->attributes[":$key"] = $value; + } + } + $sql = "SELECT * FROM " . self::$instance->table . " WHERE " . implode(" AND ", $conditions); + } else { + if (self::$isLike) { + $sql = "SELECT * FROM " . self::$instance->table . " WHERE " . self::$instance->primaryKey . "::text" . " LIKE :id"; + $this->attributes = [":id" => '%' . self::$fields . '%']; + } else { + $sql = "SELECT * FROM " . self::$instance->table . " WHERE " . self::$instance->primaryKey . "::text" . " = :id"; + $this->attributes = [":id" => self::$fields]; + } + } + + try { + if ($this->willReturn) { + return Database::Query(self::$instance, $sql, $this->attributes, $this->isAll); + } else { + Database::Query(self::$instance, $sql, $this->attributes, $this->isAll); + return new self(self::$model, self::$fields, $this->willReturn); + } + } catch (\Exception $e) { + throw new \Exception($e); + } + } + + public function __toString() { + return $this->string; + } +} diff --git a/src/Methods/Static/Database/Insert.php b/src/Methods/Static/Database/Insert.php new file mode 100644 index 0000000..38a1579 --- /dev/null +++ b/src/Methods/Static/Database/Insert.php @@ -0,0 +1,58 @@ +fillable; + + $nonFillableFields = array_diff_key($attributes, array_flip($fillable)); + if (!empty($nonFillableFields)) { + throw new \Exception("Attempting to insert non-fillable fields: " . implode(', ', array_keys($nonFillableFields))); + } + + $fields = array_keys($attributes); + $placeholders = array_map(function ($field) { return ':' . $field; }, $fields); + + $sql = "INSERT INTO \"" . $instance->table . "\" (" . implode(", ", array_map(function($field) { return "\"" . $field . "\""; }, $fields)) . ") VALUES (" . implode(", ", $placeholders) . ")"; + + if (!empty($conflictColumns)) { + $conflictFormatted = implode(', ', array_map(function ($column) { + return "\"" . $column . "\""; + }, $conflictColumns)); + + if (!empty($updateFields)) { + $updatePlaceholders = array_map(function ($field) { + return "\"" . $field . "\" = excluded.\"" . $field . "\""; + }, $updateFields); + + $updateFormatted = implode(', ', $updatePlaceholders); + $sql .= " ON CONFLICT (" . $conflictFormatted . ") DO UPDATE SET " . $updateFormatted; + } else { + $sql .= " ON CONFLICT (" . $conflictFormatted . ") DO NOTHING"; + } + } + + $sql .= " RETURNING *"; + + $params = []; + foreach ($attributes as $key => $value) { + $params[':' . $key] = $value; + } + + try { + self::$result = Database::Query($instance, $sql, $params); + } catch (\Exception $e) { + throw new \Exception("Failed to insert record into $model: " . $e->getMessage()); + } + + return self::$result; + } +} diff --git a/src/Methods/Static/Database/Query.php b/src/Methods/Static/Database/Query.php new file mode 100644 index 0000000..89a9ee7 --- /dev/null +++ b/src/Methods/Static/Database/Query.php @@ -0,0 +1,44 @@ +beginTransaction(); + break; + + case 'COMMIT': + return $db->commit(); + break; + } + + $statement = $db->prepare($sql); + + foreach ($params as $key => $value) { + $statement->bindValue($key, $value); + } + + $statement->execute(); + + if ($fetch_all) { + return $statement->fetchAll(PDO::FETCH_ASSOC); + } else { + return $statement->fetch(PDO::FETCH_ASSOC); + } + } +} \ No newline at end of file diff --git a/src/Methods/Static/Database/Update.php b/src/Methods/Static/Database/Update.php new file mode 100644 index 0000000..482912e --- /dev/null +++ b/src/Methods/Static/Database/Update.php @@ -0,0 +1,31 @@ +fillable; + + $nonFillableFields = array_diff_key($attributes, array_flip($fillable)); + if (!empty($nonFillableFields)) { + throw new \Exception("Attempting to insert non-fillable fields: " . implode(', ', array_keys($nonFillableFields))); + } + + $fields = array_keys($attributes); + $placeholders = array_map(function ($field) { return $field . " = :" . $field; }, $fields); + + $sql = "UPDATE " . $instance->table . " SET " . implode(", ", $placeholders) . " WHERE " . $instance->primaryKey . " = :id"; + + $params = [":id" => $id]; + foreach ($attributes as $key => $value) { + $params[':' . $key] = $value; + } + + return Database::Query($instance, $sql, $params); + } +} \ No newline at end of file diff --git a/src/Model.php b/src/Model.php new file mode 100644 index 0000000..7cee601 --- /dev/null +++ b/src/Model.php @@ -0,0 +1,12 @@ +