From e225003fdd438dd6d82522e7e43d07d266716add Mon Sep 17 00:00:00 2001 From: kekefreedog <70959083+kekefreedog@users.noreply.github.com> Date: Mon, 15 Jul 2024 01:10:42 +0200 Subject: [PATCH] Implement mariadb driver --- docs/Docker/Mariadb.md | 85 +++++++++++- src/Core/Model.php | 1 + src/Driver/Model/Config.php | 28 ++++ src/Driver/Model/Mariadb.php | 166 ++++++++++++++++++++++- src/Driver/Model/Mongo.php | 28 ++++ src/Driver/Model/Sql.php | 28 ++++ src/Interface/CrazyDriverModel.php | 18 +++ src/Library/Array/Arrays.php | 46 ++++++- src/Library/Database/Driver/Mariadb.php | 168 ++++++++++++++++++++++-- src/Library/Form/Validate.php | 38 +++++- src/Library/Model/Schema.php | 50 ++++++- src/Model/Router/Create.php | 7 +- tests/Library/Array/ArraysTest.php | 25 ++++ tests/Library/Form/ValidateTest.php | 115 ++++++++++++++++ 14 files changed, 778 insertions(+), 25 deletions(-) create mode 100644 tests/Library/Form/ValidateTest.php diff --git a/docs/Docker/Mariadb.md b/docs/Docker/Mariadb.md index 44f0ee9..24b958e 100644 --- a/docs/Docker/Mariadb.md +++ b/docs/Docker/Mariadb.md @@ -17,8 +17,91 @@ docker-compose ps # Result : crazytest-mariadb-1 ``` -2. Connect to database as admin +## Explore your MariaDB database + +### 1. Connect to database as admin ```sh docker exec -it crazytest-mariadb-1 mysql -p +``` + +### 2. Connect to MySql (Mariadb) + +Execute the command below : + +> You can found the password into the `docker-compose.yml`if you are using docker compose + +```sh +mysql -u root -p +``` + +### 3. Show all databases + +Execute the command below : + +```sh +SHOW DATABASES; +``` + +Result : +```sh +MariaDB [(none)]> SHOW DATABASES; ++--------------------+ +| Database | ++--------------------+ +| crazy_db | +| information_schema | +| mysql | +| performance_schema | ++--------------------+ +``` + +### 4. Select database to explore + +Execute the command below : + +```sh +USE crazy_db; +``` + +Result : +```sh +Database changed +MariaDB [crazy_db]> +``` + +### 5. Show all tables + +Execute the command below : + +```sh +SHOW TABLES; +``` + +Result : +```sh +MariaDB [crazy_db]> SHOW TABLES; ++--------------------+ +| Tables_in_crazy_db | ++--------------------+ +| Booking | ++--------------------+ +``` + +### 6. Show all tables + +Execute the command below : + +```sh +SHOW TABLES; +``` + +Result : +```sh +MariaDB [crazy_db]> SHOW TABLES; ++--------------------+ +| Tables_in_crazy_db | ++--------------------+ +| Booking | ++--------------------+ ``` \ No newline at end of file diff --git a/src/Core/Model.php b/src/Core/Model.php index a2dcfb7..f67dac5 100644 --- a/src/Core/Model.php +++ b/src/Core/Model.php @@ -21,6 +21,7 @@ use CrazyPHP\Library\Array\Arrays; use CrazyPHP\Library\File\Config; use CrazyPHP\Model\Context; +use PDO; /** * Model diff --git a/src/Driver/Model/Config.php b/src/Driver/Model/Config.php index 76c2b56..2daf4df 100644 --- a/src/Driver/Model/Config.php +++ b/src/Driver/Model/Config.php @@ -98,6 +98,34 @@ public function setAttributesAsValues(bool $summary = false):self { } + /** Public methods | Collection / Table + ****************************************************** + */ + + /** + * Create table + * + * @return self + */ + public function createTable():self { + + # Return self + return $this; + + } + + /** + * Create collection + * + * @return self + */ + public function createCollection():self { + + # Return self + return $this; + + } + /** Public methods ****************************************************** */ diff --git a/src/Driver/Model/Mariadb.php b/src/Driver/Model/Mariadb.php index 21b1038..3bb864d 100644 --- a/src/Driver/Model/Mariadb.php +++ b/src/Driver/Model/Mariadb.php @@ -16,7 +16,10 @@ * Dependances */ use CrazyPHP\Library\Database\Driver\Mariadb as MariadbModel; +use CrazyPHP\Library\File\Config as FileConfig; use CrazyPHP\Interface\CrazyDriverModel; +use CrazyPHP\Library\Model\Schema; +use CrazyPHP\Library\Array\Arrays; /** * Crazy Driver Model Interface @@ -27,15 +30,24 @@ * @author kekefreedog * @copyright 2022-2024 Kévin Zarshenas */ -class Mariadb extends CrazyDriverModel { +class Mariadb implements CrazyDriverModel { /** Private parameters ****************************************************** */ + /** @var array $arguments */ + private array $arguments; + /** @var Mysql Instance */ public MariadbModel $mariadb; + /** @var Schema $schema */ + private Schema|null $schema = null; + + /** @var string|null $id for select one item */ + private string|null $id = null; + /** @var bool $attributesAsValues Indicate if attributes is set as values in current schema */ # private bool $attributesAsValues = false; @@ -52,6 +64,9 @@ public function __construct(...$inputs) { # Sql connection $this->newMariadb(); + # Create table + $this->createTable(); + } /** Public mathods | Attributes @@ -72,6 +87,37 @@ public function setAttributesAsValues():self { } + /** Public methods | Collection / Table + ****************************************************** + */ + + /** + * Create table + * + * @return self + */ + public function createTable():self { + + # Create table + $this->mariadb->createTable($this->arguments["table"], $this->arguments["schema"]); + + # Return self + return $this; + + } + + /** + * Create collection + * + * @return self + */ + public function createCollection():self { + + # Return self + return $this; + + } + /** Public methods | Parser ****************************************************** */ @@ -160,6 +206,15 @@ public function parseSql(string $sql, ?array $options = null):self { */ public function ingestData(array $data, ?array $options = null):self { + # New schema + $schema = new Schema($this->arguments["schema"], [$data], [ + "flatten" => true, + "skipEmptyValue" => $this->isUpdate() + ]); + + # Push schema in classe schema + $this->schema = $schema; + # Return self return $this; @@ -200,6 +255,36 @@ public function run():array { # Set result $result = []; + # Insert to mongo Check schema + if($this->schema !== null){ + + # Check collection + $schemaResult = $this->schema->getResult([ + "skipAttributes" => ["id"] + ]); + + # Iteration + foreach($schemaResult as $v){ + + # Declare data + $data = []; + + # Iteration v + foreach($v as $item) + + # Push in data + $data[$item["name"]] = $item["value"]; + + # Unflatten result + $data = Arrays::unflatten($data); + + # Insert + $result[] = $this->mariadb->insertToTable($this->arguments["table"], $data); + + } + + } + # Return result return $result; @@ -271,6 +356,58 @@ public function _pageStateProcess(array $input):array { */ private function ingestParameters(array $inputs):void { + # Set arguments + $this->arguments = self::ARGUMENTS; + + # Check inputs + if(!empty($inputs)) + + # Iteration inputs + foreach($inputs as $name => $value) + + # Check name in arguments + if(array_key_exists($name, $this->arguments)) + + # Set value + $this->arguments[$name] = $value; + + + + # Check if collection + if(isset($this->arguments["table"]) && $this->arguments["table"]){ + + # Get Model Config + $models = FileConfig::getValue("Model"); + + # Search table + $search = Arrays::filterByKey($models, "name", $this->arguments["table"]); + + # Check search + if(!empty($search)) + + # Get schema + $this->arguments["schema"] = ($search[array_key_first($search)]["attributes"]) ?? []; + + } + + # Get database + $databases = FileConfig::getValue("Database.collection.mariadb.database"); + + # If empty database + if(empty($databases)) + + # New error + throw new CrazyException( + "No mongodb database defined in config.", + 500, + [ + "custom_code" => "model-mongodb-001", + ] + ); + + # Set database + $this->arguments["database"] = $databases[array_key_first($databases)]; + } /** @@ -290,4 +427,31 @@ private function newMariadb():void { } + /** Private methods | Options + ****************************************************** + */ + + /** + * Is Update + * + * @return bool + */ + private function isUpdate():bool { + + return $this->id !== null; + + } + + /** Public constants + ****************************************************** + */ + + /** @const array */ + public const ARGUMENTS = [ + "table" => "", + "schema" => [], + "database" => [], + "pageStateProcess" => false, + ]; + } \ No newline at end of file diff --git a/src/Driver/Model/Mongo.php b/src/Driver/Model/Mongo.php index bd837e2..b025b0f 100644 --- a/src/Driver/Model/Mongo.php +++ b/src/Driver/Model/Mongo.php @@ -104,6 +104,34 @@ public function setAttributesAsValues():self { } + /** Public methods | Collection / Table + ****************************************************** + */ + + /** + * Create table + * + * @return self + */ + public function createTable():self { + + # Return self + return $this; + + } + + /** + * Create collection + * + * @return self + */ + public function createCollection():self { + + # Return self + return $this; + + } + /** Public methods | Parser ****************************************************** */ diff --git a/src/Driver/Model/Sql.php b/src/Driver/Model/Sql.php index 2162b2c..726160d 100644 --- a/src/Driver/Model/Sql.php +++ b/src/Driver/Model/Sql.php @@ -72,6 +72,34 @@ public function setAttributesAsValues():self { } + /** Public methods | Collection / Table + ****************************************************** + */ + + /** + * Create table + * + * @return self + */ + public function createTable():self { + + # Return self + return $this; + + } + + /** + * Create collection + * + * @return self + */ + public function createCollection():self { + + # Return self + return $this; + + } + /** Public methods | Parser ****************************************************** */ diff --git a/src/Interface/CrazyDriverModel.php b/src/Interface/CrazyDriverModel.php index 7933d26..0aae324 100644 --- a/src/Interface/CrazyDriverModel.php +++ b/src/Interface/CrazyDriverModel.php @@ -43,6 +43,24 @@ interface CrazyDriverModel { */ public function setAttributesAsValues():self; + /** Public methods | Collection / Table + ****************************************************** + */ + + /** + * Create table + * + * @return self + */ + public function createTable():self; + + /** + * Create collection + * + * @return self + */ + public function createCollection():self; + /** Public methods | Parser ****************************************************** */ diff --git a/src/Library/Array/Arrays.php b/src/Library/Array/Arrays.php index 8c7a4f8..e86f8d3 100644 --- a/src/Library/Array/Arrays.php +++ b/src/Library/Array/Arrays.php @@ -133,11 +133,11 @@ public static function stretch(array $array = [], string $separator = "_"):array * Find parameter than some child parameter correspond to key value * * @param array $array Array to process - * @param any $key Child parameter to use in filter - * @param any $keyValue Child parameter value's to use in filter + * @param mixed $key Child parameter to use in filter + * @param mixed $keyValue Child parameter value's to use in filter * @return array */ - public static function filterByKey(array $array = [], $key = "", $keyValue = ""):array { + public static function filterByKey(array $array = [], string $key = "", mixed $keyValue = ""):array { # Process and return result return array_filter( @@ -149,6 +149,46 @@ function ($var) use ($keyValue, $key) { } + /** + * Remove By Key + * + * Remove items from an array where the specified parameter is equal to the given value. + * + * @param array $array The input array. + * @param mixed $key The parameter to check. + * @param mixed $keyValue The value to compare against. + * @return array The filtered array with items removed. + */ + public static function removeByKey(array $array = [], mixed $key = "", mixed $keyValue = ""):array { + + # Set result + $result = array_filter($array, function($item) use ($key, $keyValue) { + + # Check if item is an associative array and parameter exists + if(is_array($item) && isset($item[$key])) + + # Return match + return $item[$key] !== $keyValue; + + # Check if item is an object and parameter exists as property + if(is_object($item) && isset($item->$key)) + + # Return match + return $item->$key !== $keyValue; + + # If parameter does not exist, do not remove the item + return true; + + }); + + # Reset keys + $result = array_values($result); + + # Return result + return $result; + + } + /** * Merge multidimensional array * diff --git a/src/Library/Database/Driver/Mariadb.php b/src/Library/Database/Driver/Mariadb.php index c117777..17ab7f7 100644 --- a/src/Library/Database/Driver/Mariadb.php +++ b/src/Library/Database/Driver/Mariadb.php @@ -22,6 +22,8 @@ use PDOException; use PDO; +use function PHPUnit\Framework\returnSelf; + /** * Mariadb * @@ -360,13 +362,157 @@ public function createDatabase(string $option = "CHARACTER SET utf8mb4 COLLATE u /** * Create Table * - * @param $table - * @param $schema + * @param string $table + * @param array $attributes + * @param bool $replaceTable + * @param array $option + * @param string $database * @return mixed */ - public function createTable($table, $value) { + public function createTable($tableName = "", $attributes = [], $replaceTable = false, $option = [], string $database = ""):mixed { - + # Check database + if(!$database) + + # Get main database + $database = $this->_getDefaultDatabase(); + + # Set result + $result = null; + + # Check attributes + if(empty($attributes) || !$tableName) + + # Return result + return $result; + + # Switch to the specified database + $this->client->exec("USE " . $database); + + # Prepare query + $query = "CREATE TABLE ".($replaceTable ? "" : "IF NOT EXISTS ")."`$tableName` "; + + # Set columns + $columns = []; + + # Set primary + $primary = ""; + + # Iteration attributes + foreach($attributes as $attribute){ + + # Set id id + $isId = false; + + # Get column name + $columnName = $attribute['name']; + + # Set type + $columnTypeTemp = strtoupper($attribute['type']); + + # Set column type + $columnType = $columnTypeTemp; + + # If varchar + if($columnTypeTemp == "INT" && $columnName == "id" && !$isId){ + + # Set column type + $columnType = "int(6) UNSIGNED"; + + # Set is id + $isId = true; + + # Set primary + $primary = "PRIMARY KEY (`$columnName`)"; + + # Set required + $required = 'NOT NULL'; + + }else + # If varchar + if($columnTypeTemp == "VARCHAR"){ + + # Set column type + $columnType = "VARCHAR(255)"; + + # Set required + $required = isset($attribute['required']) && $attribute['required'] + ? 'NOT NULL' + : 'NULL' + ; + + }else + # If int + if($columnTypeTemp == "INT"){ + + # Set column type + $columnType = "INT(11)"; + + # Set required + $required = isset($attribute['required']) && $attribute['required'] + ? 'NOT NULL' + : 'NULL' + ; + + }else + # If int + if($columnTypeTemp == "BOOL" || $columnTypeTemp == "BOOLEAN"){ + + # Set column type + $columnType = "BOOLEAN"; + + # Set required + $required = isset($attribute['required']) && $attribute['required'] + ? 'NOT NULL' + : 'NULL' + ; + + } + + # Clean column type + $columnType = strtolower($columnType); + + # Set default + $default = isset($attribute['default']) + ? "DEFAULT '{".$attribute['default']."}'" + : '' + ; + + # Push result into columns + $columns[] = trim("`$columnName` $columnType $required $default").($isId ? " AUTO_INCREMENT" : ""); + + } + + # Check columns + if(empty($columns)) + + # Return result + return $result; + + # Fill query + $query .= "(". implode(', ', $columns) . ", $primary)"; + + # Execute the SQL statement to create the table + try { + + # Exec + $result = $this->client->exec($query); + + } catch (PDOException $e) { + + # Error + throw new CrazyException( + "Error creating table: " . $e->getMessage(), + 500, + [ + "custom_code" => "mariadb-006", + ] + ); + + } + + # Return result + return $result; } @@ -500,18 +646,18 @@ public function insertToTable(string $table, array $value, string $database = "" # Set result $result = null; - # Check input - if(!$table || empty($value) || !$database) - - # Return result - return $result; - # Check database if(!$database) # Get main database $database = $this->_getDefaultDatabase(); + # Check input + if(!$table || empty($value) || !$database) + + # Return result + return $result; + # Use database $this->client->exec("USE " . $database); @@ -910,7 +1056,7 @@ public static function getConnectionArray(array $options = []):array { "mysql:host=".$options["host"].";". ( $options["port"] !== false - ? "port=".$options["port"] + ? "port=".$options["port"].";" : "" ). "dbname=".$options["database"] diff --git a/src/Library/Form/Validate.php b/src/Library/Form/Validate.php index 3c48daf..149e993 100644 --- a/src/Library/Form/Validate.php +++ b/src/Library/Form/Validate.php @@ -52,13 +52,15 @@ class Validate { */ private $dispatch = [ "INT" => [ - "isValidHttpStatusCode" + "isValidHttpStatusCode", + "isMobilePhone" ], "VARCHAR" => [ "isEmail", "isIpAddress", "isValidUrl", - "isSemanticVersioning" + "isSemanticVersioning", + "isMobilePhone" ], "ARRAY" => [ ], @@ -859,4 +861,36 @@ public static function isSemanticVersioning(string $version):bool { } + /** + * Is Mobile Phone + * + * @source https://stackoverflow.com/questions/22378736/regex-for-mobile-number-validation + * + * @param string|int $input + * @return bool + */ + public static function isMobilePhone(string|int $input):bool { + + # Set result + $result = false; + + # Check input + if($input){ + + # Set pattern + $pattern = '/^(\+\d{1,3}[- ]?)?\d{10}$/'; + + # Regex test + if(preg_match($pattern,(string)$input)) + + # Set result + $result = true; + + } + + # Return result + return $result; + + } + } \ No newline at end of file diff --git a/src/Library/Model/Schema.php b/src/Library/Model/Schema.php index d37b131..5a5a40b 100644 --- a/src/Library/Model/Schema.php +++ b/src/Library/Model/Schema.php @@ -372,14 +372,28 @@ public function pushValues(?array $values = [], ?array $options = null):void { * * Return schema collection * - * @param bool $validate ValidateValues schema + * @param array $option Option * @return array */ - public function getResult():array { + public function getResult($option = []):array { # Set result $result = []; + # Set skipAttributes + $skipAttributes = []; + + # Check option + if(!empty($option)){ + + # Check if skipAttributes + if(isset($option["skipAttributes"]) && is_array($option["skipAttributes"])) + + # Set skipAttributes + $skipAttributes = $option["skipAttributes"]; + + } + # Set result $results = $this->collectionWithValues; @@ -392,6 +406,15 @@ public function getResult():array { # Iteration result foreach($results as &$currentResult){ + # Check skip attributes + foreach($skipAttributes as $name) + + # Check name + if($name) + + # Clean in current result + $currentResult = Arrays::removeByKey($currentResult, "name", $name); + # Vrocess values $currentResult = (new Process($currentResult))->getResult(); @@ -427,6 +450,20 @@ public function getResultSummary():array|null { # Set result $result = []; + # Set skip Attributes + $skipAttributes = []; + + # Check option + if(!empty($option)){ + + # Check if skipAttributes + if(isset($option["skipAttributes"]) && is_array($option["skipAttributes"])) + + # Set skipAttributes + $skipAttributes = $option["skipAttributes"]; + + } + # Check is attributes as values is not enable if(!$this->attributesAsValues){ @@ -439,6 +476,15 @@ public function getResultSummary():array|null { # Iteration result foreach($results as &$currentResult){ + # Check skip attributes + foreach($skipAttributes as $name) + + # Check name + if($name) + + # Clean in current result + $currentResult = Arrays::removeByKey($currentResult, "name", $name); + # Vrocess values $currentResult = (new Process($currentResult))->getResult(); diff --git a/src/Model/Router/Create.php b/src/Model/Router/Create.php index 31fa820..d91de86 100644 --- a/src/Model/Router/Create.php +++ b/src/Model/Router/Create.php @@ -413,10 +413,7 @@ public function runCreateControllerFile():void { ]; # Create template instance - $template = new Handlebars([ - "template" => Handlebars::PERFORMANCE_PRESET, - "helpers" => false - ]); + $template = new Handlebars(); # Load template $template->load("@crazyphp_root/resources/Hbs/App/Controller/Api/Template.php.hbs"); @@ -425,7 +422,7 @@ public function runCreateControllerFile():void { $result = $template->render($this->router + $additionnal); # Set file path - $filePath = Router::getControllerPath().ucfirst($this->router["Type"])."\/v1/".$this->router["Name"].".php"; + $filePath = Router::getControllerPath().ucfirst($this->router["Type"])."/v1/".$this->router["Name"].".php"; # Create file File::create($filePath, $result); diff --git a/tests/Library/Array/ArraysTest.php b/tests/Library/Array/ArraysTest.php index da9d660..c43cca0 100644 --- a/tests/Library/Array/ArraysTest.php +++ b/tests/Library/Array/ArraysTest.php @@ -468,4 +468,29 @@ public function testUnflatten():void { } + /** + * Test Remove By Key + * + * @return void + */ + public function testRemoveByKey():void { + + # Set input + $input = [ + ['id' => 1, 'name' => 'Alice'], + ['id' => 2, 'name' => 'Bob'], + ['id' => 3, 'name' => 'Charlie'], + ]; + + # Set output + $output = [ + ['id' => 1, 'name' => 'Alice'], + ['id' => 3, 'name' => 'Charlie'], + ]; + + # Assert one + $this->assertEquals($output, Arrays::removeByKey($input, "id", 2)); + + } + } \ No newline at end of file diff --git a/tests/Library/Form/ValidateTest.php b/tests/Library/Form/ValidateTest.php new file mode 100644 index 0000000..243b8ba --- /dev/null +++ b/tests/Library/Form/ValidateTest.php @@ -0,0 +1,115 @@ + + * @copyright 2022-2024 Kévin Zarshenas + */ +namespace Tests\Library\File; + +/** + * Dependances + */ +use CrazyPHP\Library\Form\Validate; +use PHPUnit\Framework\TestCase; +use CrazyPHP\Model\Env; + +/** + * Validate Test + * + * Methods for test validate methods + * + * @package kzarshenas/crazyphp + * @author kekefreedog + * @copyright 2022-2024 Kévin Zarshenas + */ +class ValidateTest extends TestCase{ + + /** Public constants + ****************************************************** + */ + + /** @const array INPUT */ + public const INPUT = [ + "phoneNumber" => [ + "0694450403", + "+33694454345", + "toto", + "0123456789012" + ] + ]; + + /** @const array RESULT */ + public const OUTPUT = [ + "phoneNumber" => [ + true, + true, + false, + false + ] + ]; + + /** Public method | Preparation + ****************************************************** + */ + + /** + * Set Up Before Class + * + * This method is called before the first test of this test class is run. + * + * @return void + */ + public static function setUpBeforeClass():void { + + # Setup env + Env::set([ + # App root for composer class + "crazyphp_root" => getcwd(), + "phpunit_test" => true, + "env_value" => "env_result", + "config_location" => "@crazyphp_root/resources/Yml" + ]); + + } + + + /** + * Tear Down After Class + * + * This method is called after the last test of this test class is run. + * + * @return void + */ + public static function tearDownAfterClass():void { + + # Reset env variables + Env::reset(); + + } + + /** Public method | Tests + ****************************************************** + */ + + /** + * Test Is Mobile Phone + * + * @return void + */ + public function testIsMobilePhone():void { + + # Iteration input + foreach(self::INPUT["phoneNumber"] as $i => $input) + + # Assert + $this->assertEquals(self::OUTPUT["phoneNumber"][$i], Validate::isMobilePhone($input), "\"$input\" is not valid"); + + } + +} \ No newline at end of file