From 2fd47e201e7e089083c8f81e3ed647ec64f7ff13 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sat, 28 Dec 2024 16:03:47 +0700 Subject: [PATCH 1/4] Add PicoDatabase::getDatabaseTimeZone() and PicoDatabase::getDatabaseTimeZoneOffset() --- src/Database/PicoDatabase.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php index 77c2ee63..7b316f38 100644 --- a/src/Database/PicoDatabase.php +++ b/src/Database/PicoDatabase.php @@ -1056,6 +1056,33 @@ public function getDatabaseType() return $this->databaseType; } + /** + * Retrieves the time zone used by the database. + * + * This function calls the `getTimeZone()` method from the `databaseCredentials` + * object to fetch the time zone configured for the database connection. + * + * @return string The time zone of the database (e.g., "UTC", "America/New_York"). + */ + public function getDatabaseTimeZone() + { + return $this->databaseCredentials->getTimeZone(); + } + + /** + * Retrieves the time zone offset of the database connection. + * + * This function retrieves the time zone offset by calling the static method + * `getTimeZoneOffset()` with the `databaseConnection` as an argument. + * The offset is returned in seconds from UTC. + * + * @return string The time zone offset, typically in hours and minutes (e.g., "+02:00"). + */ + public function getDatabaseTimeZoneOffset() + { + return self::getTimeZoneOffset($this->databaseConnection); + } + /** * Convert the object to a JSON string representation for debugging. * From 49425db98d83a75785a95514b96939c8fc9c9ffe Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Tue, 31 Dec 2024 20:53:58 +0700 Subject: [PATCH 2/4] Add query translator --- src/Util/Database/PicoQueryTranslator.php | 842 ++++++++++++++++++++++ src/Util/Database/PicoTableParser.php | 414 +++++++++++ 2 files changed, 1256 insertions(+) create mode 100644 src/Util/Database/PicoQueryTranslator.php create mode 100644 src/Util/Database/PicoTableParser.php diff --git a/src/Util/Database/PicoQueryTranslator.php b/src/Util/Database/PicoQueryTranslator.php new file mode 100644 index 00000000..39ecff4a --- /dev/null +++ b/src/Util/Database/PicoQueryTranslator.php @@ -0,0 +1,842 @@ +dbToSqlite = [ + "int" => "INTEGER", + "tinyint(1)" => "BOOLEAN", // NOSONAR + "tinyint" => "INTEGER", + "smallint" => "INTEGER", + "mediumint" => "INTEGER", + "bigint" => "INTEGER", + "real" => "REAL", + "float" => "REAL", + "double" => "REAL", + "decimal" => "REAL", + "nvarchar" => "NVARCHAR", + "varchar" => "NVARCHAR", + "character varying" => "NVARCHAR", // NOSONAR + "char" => "NVARCHAR", + "tinytext" => "TEXT", + "mediumtext" => "TEXT", + "longtext" => "TEXT", + "text" => "TEXT", + "datetime" => "DATETIME", + "timestamp" => "TIMESTAMP", + "date" => "DATE", + "time" => "TIME", + "year" => "INTEGER", + "boolean" => "INTEGER", + "json" => "TEXT", + "jsonb" => "TEXT", + "integer" => "INTEGER", + "serial" => "INTEGER", + "bigserial" => "INTEGER", + "double precision" => "REAL", + "timestamptz" => "TIMESTAMP" + ]; + + $this->dbToMySQL = [ + "bigint" => "BIGINT", + "mediumint" => "MEDIUMINT", + "smallint" => "SMALLINT", + "integer" => "INT", + "double" => "DOUBLE", + "float" => "FLOAT", + "real" => "DOUBLE", + "decimal" => "DECIMAL", + "numeric" => "NUMERIC", + "tinytext" => "TINYTEXT", + "mediumtext" => "MEDIUMTEXT", + "longtext" => "LONGTEXT", + "text" => "TEXT", + "nvarchar" => "VARCHAR", + "varchar" => "VARCHAR", + "character varying" => "VARCHAR", + "tinyint(1)" => "TINYINT(1)", // NOSONAR + "tinyint" => "TINYINT", + "boolean" => "TINYINT(1)", + "int" => "INT", + "datetime" => "DATETIME", + "date" => "DATE", + "timestamptz" => "TIMESTAMP", + "timestamp with time zone" => "TIMESTAMP", + "timestamp without time zone" => "DATETIME", + "timestamp" => "TIMESTAMPTZ", + "json" => "JSON", + "enum" => "ENUM", + "set" => "SET", + "char" => "CHAR" + ]; + + $this->dbToPostgreSQL = [ + "bigint" => "BIGINT", + "mediumint" => "INTEGER", + "smallint" => "INTEGER", + "tinyint(1)" => "BOOLEAN", + "tinyint" => "INTEGER", + "integer" => "INTEGER", + "real" => "REAL", + "longtext" => "TEXT", + "mediumtext" => "TEXT", + "smalltext" => "TEXT", + "tinytext" => "TEXT", + "text" => "TEXT", + "character varying" => "CHARACTER VARYING", // NOSONAR + "nvarchar" => "CHARACTER VARYING", + "varchar" => "CHARACTER VARYING", + "char" => "CHARACTER", + "boolean" => "BOOLEAN", + "datetime" => "TIMESTAMP WITHOUT TIME ZONE", + "date" => "DATE", + "timestamptz" => "TIMESTAMP WITH TIME ZONE", + "timestamp" => "TIMESTAMP WITH TIME ZONE", + "time" => "TIME", + "json" => "JSONB" + ]; + } + + /** + * Replaces all occurrences of a substring in the given string. + * + * This function uses a regular expression to replace all matches of the search pattern with the specified replacement. + * + * @param string|null $str The string to search and replace in. If null, returns null. + * @param string $search The substring to search for. + * @param string $replacement The substring to replace the search pattern with. + * @return string|null The modified string, or null if the input string is null. + */ + private function replaceAll($str, $search, $replacement) { + return str_ireplace($search, $replacement, $str); + } + + /** + * Translates the provided SQL query to a specified target database format. + * + * This method converts the provided SQL string into a format compatible with the specified target database (MySQL, PostgreSQL, or SQLite), + * ensuring that all relevant data types and structures are adapted. + * + * @param string $value The SQL query to be translated. + * @param string $targetType The target database type ('mysql', 'pgsql', 'sqlite'). + * @return string The translated SQL query in the target database format. + */ + public function translate($value, $targetType) { + $dropTables = []; + $tableInfo = $this->extractDropTableQueries($value, $targetType); + + foreach ($tableInfo as $table) { + $dropTables[] = "-- DROP TABLE IF EXISTS " . $table['table'] . ";"; + } + + $value = $this->replaceAll($value, '`', ''); + $value = $this->replaceAll($value, ' timestamp with time zone', ' timestamptz'); + $value = $this->replaceAll($value, ' timestamp without time zone', ' timestamp'); + $value = $this->replaceAll($value, ' character varying', ' varchar'); + $value = $this->replaceAll($value, ' COLLATE pg_catalog."default"', ''); + $value = $this->replaceAll($value, ' TINYINT(1)', ' boolean'); + + $tableParser = new PicoTableParser(); // Assuming this is a predefined class in your code + $tableParser->parseAll($value); + $tables = $tableParser->getResult(); + + $lines = []; + foreach ($tables as $table) { + $convertedTable = $this->convertQuery($table, $targetType); + $lines[] = $convertedTable; + $lines[] = ''; + } + + if (!empty($dropTables)) { + $dropTables[] = "\r\n\r\n"; + } + + return implode("\r\n", $dropTables) . implode("\r\n", $lines); + } + + /** + * Converts a table schema to a query compatible with the specified database type. + * + * This method takes the parsed table schema and converts it to the corresponding SQL syntax for the target database. + * + * @param array $table The table schema to convert. + * @param string $targetType The target database type ('mysql', 'pgsql', 'sqlite'). + * @return string The converted table schema in the target database format. + */ + private function convertQuery($table, $targetType) { + if ($this->isSQLite($targetType)) { + return $this->toSqliteOut($table, $targetType); + } elseif ($this->isMySQL($targetType)) { + return $this->toMySQLOut($table, $targetType); + } elseif ($this->isPGSQL($targetType)) { + return $this->toPostgreSQLOut($table, $targetType); + } + } + + /** + * Converts a table schema to SQLite-specific SQL syntax. + * + * This method adapts the provided table schema to SQLite format, including data type conversions and primary key handling. + * + * @param array $table The table schema to convert. + * @param string $targetType The target database type ('sqlite'). + * @return string The converted table schema in SQLite format. + */ + private function toSqliteOut($table, $targetType) { + $sqliteTable = [ + 'tableName' => $table['tableName'], + 'primaryKey' => $table['primaryKey'], + 'columns' => array_map(function($column) { + $column['Type'] = $this->toSqliteType($column['Type'], $column['Length']); + return $column; + }, $table['columns']) + ]; + return $this->toSqliteTable($sqliteTable, $targetType); + } + + /** + * Converts a table schema to MySQL-specific SQL syntax. + * + * This method adapts the provided table schema to MySQL format, including data type conversions and primary key handling. + * + * @param array $table The table schema to convert. + * @param string $targetType The target database type ('mysql'). + * @return string The converted table schema in MySQL format. + */ + private function toMySQLOut($table, $targetType) { + $mysqlTable = [ + 'tableName' => $table['tableName'], + 'primaryKey' => $table['primaryKey'], + 'columns' => array_map(function($column) { + $column['Type'] = $this->toMySQLType($column['Type'], $column['Length']); + return $column; + }, $table['columns']) + ]; + return $this->toMySQLTable($mysqlTable, $targetType); + } + + /** + * Converts a table schema to PostgreSQL-specific SQL syntax. + * + * This method adapts the provided table schema to PostgreSQL format, including data type conversions and primary key handling. + * + * @param array $table The table schema to convert. + * @param string $targetType The target database type ('pgsql'). + * @return string The converted table schema in PostgreSQL format. + */ + private function toPostgreSQLOut($table, $targetType) { + $pgTable = [ + 'tableName' => $table['tableName'], + 'primaryKey' => $table['primaryKey'], + 'columns' => array_map(function($column) { + $column['Type'] = $this->toPostgreSQLType($column['Type'], $column['Length']); + return $column; + }, $table['columns']) + ]; + return $this->toPostgreSQLTable($pgTable, $targetType); + } + + + /** + * Converts a given table for SQLite target type. + * + * This method uses a common `toTable` function to convert a table for SQLite. + * + * @param string $sqliteTable The name of the SQLite table. + * @param string $targetType The target database type (e.g., 'sqlite'). + * @return mixed The result of the `toTable` method. + */ + private function toSqliteTable($sqliteTable, $targetType) { + return $this->toTable($sqliteTable, $targetType); + } + + /** + * Converts a given table for MySQL target type. + * + * This method uses a common `toTable` function to convert a table for MySQL. + * + * @param string $mysqlTable The name of the MySQL table. + * @param string $targetType The target database type (e.g., 'mysql'). + * @return mixed The result of the `toTable` method. + */ + private function toMySQLTable($mysqlTable, $targetType) { + return $this->toTable($mysqlTable, $targetType); + } + + /** + * Converts a given table for PostgreSQL target type. + * + * This method uses a common `toTable` function to convert a table for PostgreSQL. + * + * @param string $pgTable The name of the PostgreSQL table. + * @param string $targetType The target database type (e.g., 'pgsql'). + * @return mixed The result of the `toTable` method. + */ + private function toPostgreSQLTable($pgTable, $targetType) { + return $this->toTable($pgTable, $targetType); + } + + /** + * Checks if the target type is MySQL or MariaDB. + * + * This method verifies if the target type matches MySQL or MariaDB. + * + * @param string $targetType The target database type. + * @return bool Returns true if the target type is 'mysql' or 'mariadb', otherwise false. + */ + private function isMySQL($targetType) { + return $targetType === 'mysql' || $targetType === 'mariadb'; + } + + /** + * Checks if the target type is PostgreSQL. + * + * This method verifies if the target type matches PostgreSQL or PGSQL. + * + * @param string $targetType The target database type. + * @return bool Returns true if the target type is 'pgsql' or 'postgresql', otherwise false. + */ + private function isPGSQL($targetType) { + return $targetType === 'pgsql' || $targetType === 'postgresql'; + } + + /** + * Checks if the target type is SQLite. + * + * This method verifies if the target type matches SQLite. + * + * @param string $targetType The target database type. + * @return bool Returns true if the target type is 'sqlite', otherwise false. + */ + private function isSQLite($targetType) { + return $targetType === 'sqlite'; + } + + /** + * Checks if the given column type is a real number (e.g., FLOAT, DOUBLE, REAL, DECIMAL). + * + * This method checks if the column type corresponds to a real number type. + * + * @param string $columnType The column type to check. + * @return bool Returns true if the column type is a real number, otherwise false. + */ + private function isReal($columnType) { + return stripos($columnType, 'FLOAT') !== false || + stripos($columnType, 'DOUBLE') !== false || + stripos($columnType, 'REAL') !== false || + stripos($columnType, 'DECIMAL') !== false; + } + + /** + * Checks if the given column type is a boolean type (e.g., BOOLEAN, BOOL, TINYINT(1)). + * + * This method checks if the column type corresponds to a boolean type. + * + * @param string $columnType The column type to check. + * @return bool Returns true if the column type is boolean, otherwise false. + */ + private function isBoolean($columnType) { + return strtoupper($columnType) == 'BOOLEAN' || + strtoupper($columnType) == 'BOOL' || + strtoupper($columnType) == 'TINYINT(1)'; + } + + /** + * Converts a column type to the SQLite type format. + * @param string $type The original column type. + * @param int|null $length The column length (optional). + * @return string The converted SQLite column type. + */ + public function toSqliteType($type, $length = null) { + $type = strtolower($type); + + if ($type === 'tinyint' && $length === 1) { + return 'BOOLEAN'; + } + + $sqliteType = 'TEXT'; + foreach ($this->dbToSqlite as $key => $value) { + if (strpos($type, strtolower($key)) === 0) { + $sqliteType = $value; + break; + } + } + + if (strpos(strtoupper($type), 'ENUM') !== false || strpos(strtoupper($type), 'SET') !== false) { + $parsedEnum = $this->parseEnumValue($length); + $sqliteType = 'NVARCHAR(' . ($parsedEnum['maxLength'] + 2) . ')'; + } elseif (($sqliteType === 'NVARCHAR' || $sqliteType === 'INT') && $length > 0) { + $sqliteType .= "($length)"; + } + + return $sqliteType; + } + + /** + * Converts a column type to the MySQL type format. + * @param string $type The original column type. + * @param int|null $length The column length (optional). + * @return string The converted MySQL column type. + */ + public function toMySQLType($type, $length = null) { + $type = strtolower($type); + $mysqlType = 'TEXT'; + + if ($this->isTinyInt1($type, $length)) { + return 'TINYINT(1)'; + } + + if ($this->isInteger($type) && $length > 0) { + return "{$type}($length)"; + } + + foreach ($this->dbToMySQL as $key => $value) { + if (strpos($type, strtolower($key)) === 0) { + $mysqlType = $value; + break; + } + } + + $mysqlType = str_replace('TIMESTAMPTZ', 'TIMESTAMP', $mysqlType); + + if (strpos(strtoupper($type), 'ENUM') !== false) { + $parsedEnum = $this->parseEnumValue($length); + $mysqlType = 'ENUM(\'' . implode('\',\'', $parsedEnum['resultArray']) . '\')'; + } elseif (strpos(strtoupper($type), 'SET') !== false) { + $parsedEnum = $this->parseEnumValue($length); + $mysqlType = 'SET(\'' . implode('\',\'', $parsedEnum['resultArray']) . '\')'; + } elseif (strpos(strtoupper($type), 'DECIMAL') !== false) { + $parsedNumeric = $this->parseNumericType($length); + $mysqlType = 'DECIMAL(' . implode(', ', $parsedNumeric['resultArray']) . ')'; + } elseif (strpos(strtoupper($type), 'NUMERIC') !== false) { + $parsedNumeric = $this->parseNumericType($length); + $mysqlType = 'NUMERIC(' . implode(', ', $parsedNumeric['resultArray']) . ')'; + } + + if (($mysqlType === 'VARCHAR' || $mysqlType === 'CHAR') && $length > 0) { + $mysqlType .= "($length)"; + } + + return $mysqlType; + } + + /** + * Converts a column type to the PostgreSQL type format. + * @param string $type The original column type. + * @param int|null $length The column length (optional). + * @return string The converted PostgreSQL column type. + */ + public function toPostgreSQLType($type, $length = null) { + $type = strtolower($type); + $pgType = 'TEXT'; + + foreach ($this->dbToPostgreSQL as $key => $value) { + if (strpos($type, strtolower($key)) === 0) { + $pgType = $value; + break; + } + } + $pgType = strtoupper($pgType); + if (strpos(strtoupper($type), 'TINYINT') !== false && $length == 1) { + $pgType = 'BOOLEAN'; + } elseif (strpos(strtoupper($type), 'ENUM') !== false || strpos(strtoupper($type), 'SET') !== false) { + $parsedEnum = $this->parseEnumValue($length); + $pgType = 'CHARACTER VARYING(' . ($parsedEnum['maxLength'] + 2) . ')'; + } elseif (($pgType === 'CHARACTER VARYING' || $pgType === 'CHARACTER' || $pgType === 'CHAR') && $length > 0) { + $pgType .= "($length)"; + } + + return $pgType; + } + + /** + * Parses an ENUM type value and extracts the values in single quotes, also calculating the maximum length. + * @param string $inputString The ENUM values in a string format. + * @return array An associative array containing the result array and maximum length of ENUM values. + */ + private function parseEnumValue($inputString) { + preg_match_all("/'([^']+)'/", $inputString, $matches); + $resultArray = $matches[1]; + $maxLength = max(array_map('strlen', $resultArray)); + + return ['resultArray' => $resultArray, 'maxLength' => $maxLength]; + } + + /** + * Parses a numeric type value like DECIMAL(6,3), NUMERIC(10,2), etc. + * @param string $inputString The numeric value in string format, like 'DECIMAL(6, 3)'. + * @return array An associative array containing the type (e.g., DECIMAL) and the length (total digits) and scale (digits after the decimal point). + */ + private function parseNumericType($inputString) { + preg_match_all("/([A-Za-z0-9_]+)/", $inputString, $matches); // NOSONAR + $resultArray = $matches[1]; + $maxLength = max(array_map('strlen', $resultArray)); + + return ['resultArray' => $resultArray, 'maxLength' => $maxLength]; + } + + /** + * Converts the table schema into a SQL CREATE TABLE statement for the specified database. + * + * This method generates a SQL CREATE TABLE statement based on the provided table schema and target database type, + * adapting column definitions, default values, and other relevant attributes. + * + * @param array $table The table schema to convert. + * @param string $targetType The target database type ('mysql', 'pgsql', 'sqlite'). + * @return string The SQL CREATE TABLE statement. + */ + public function toTable($table, $targetType) { + $tableName = $table['tableName']; + + $lines = []; + + // Fix table name if necessary + $tableName = $this->fixTableName($tableName, $targetType); + + $lines[] = "CREATE TABLE IF NOT EXISTS $tableName ("; + $linesCol = []; + + foreach ($table['columns'] as $column) { + $columnName = $this->fixColumnName($column['Field'], $targetType); + $columnType = $column['Type']; + $primaryKey = ($column['Field'] === $table['primaryKey']); + $colDef = "\t$columnName $columnType"; + + // Handle primary key + if ($primaryKey) { + $colDef .= " PRIMARY KEY"; + if ($this->isAutoIncrement($column['AutoIncrement'], $targetType)) { + $colDef .= " AUTO_INCREMENT"; + } + $column['Nullable'] = false; + } + // Handle nullability + else if ($column['Nullable']) { + $colDef .= " NULL"; + } else { + $colDef .= " NOT NULL"; + } + + // Handle default values + $defaultValue = $column['Default']; + if ($this->hasDefaultValue($primaryKey, $defaultValue)) { + $defaultValue = $this->replaceAll($defaultValue, '::character varying', ''); + $defaultValue = $this->fixDefaultValue($defaultValue, $targetType); + if ($this->isNotEmpty($defaultValue)) { + $colDef .= $this->getDefaultData($defaultValue, $columnType); + } + } + + // Handle column comments + if (isset($column['Comment'])) { + $colDef .= " COMMENT '" . $this->addslashes($column['Comment']) . "'"; + } + + $linesCol[] = $colDef; + } + + $lines[] = implode(",\r\n", $linesCol); + $lines[] = ");"; + + return implode("\r\n", $lines); + } + + + + /** + * Checks if the column type is TINYINT with a length of 1. + * + * This method checks if the given column type is 'TINYINT' and if its length + * is exactly 1, which is commonly used to represent boolean values in certain databases. + * + * @param string $type The data type of the column (e.g., 'TINYINT'). + * @param int $length The length of the column (e.g., 1). + * @return bool True if the type is 'TINYINT' and length is 1, otherwise false. + */ + public function isTinyInt1($type, $length) { + return strtoupper($type) === 'TINYINT' && $length == 1; + } + + /** + * Checks if the column type is an integer (e.g., TINYINT, SMALLINT, INT, BIGINT). + * + * @param string $type The column data type (e.g., 'INT', 'BIGINT'). + * @return bool True if the column type is an integer type, otherwise false. + */ + public function isInteger($type) { + $type = strtoupper($type); + return $type === 'TINYINT' || + $type === 'SMALLINT' || + $type === 'MEDIUMINT' || + $type === 'BIGINT' || + $type === 'INTEGER' || + $type === 'INT'; + } + + /** + * Determines if a column is auto-incremented for MySQL databases. + * + * @param bool $autoIncrement Whether the column is set to auto-increment. + * @param string $targetType The target database type (e.g., 'mysql', 'mariadb'). + * @return bool True if the column is auto-incremented in MySQL or MariaDB, otherwise false. + */ + public function isAutoIncrement($autoIncrement, $targetType) { + return $this->isMySQL($targetType) && $autoIncrement; + } + + /** + * Checks if a value is not empty (not null or an empty string). + * + * @param string $value The value to check. + * @return bool True if the value is not empty, otherwise false. + */ + public function isNotEmpty($value) { + return $value !== null && $value !== ''; + } + + /** + * Determines if a column has a default value, excluding primary keys. + * + * @param bool $primaryKey Whether the column is a primary key. + * @param string $defaultValue The default value of the column. + * @return bool True if the column has a default value, otherwise false. + */ + public function hasDefaultValue($primaryKey, $defaultValue) { + return !$primaryKey && $defaultValue !== null && $defaultValue !== ''; + } + + /** + * Fixes the table name according to the target database type. + * + * This method adjusts the table name by removing any database prefix and applying + * the appropriate syntax for the target database (e.g., quoting for MySQL or PostgreSQL). + * + * @param string $tableName The name of the table to fix. + * @param string $targetType The target database type (e.g., 'mysql', 'pgsql'). + * @return string The fixed table name. + */ + public function fixTableName($tableName, $targetType) { + if (strpos($tableName, '.') !== false) { + $tableName = explode('.', $tableName)[1]; + } + + if ($this->isMySQL($targetType)) { + $tableName = '`' . $tableName . '`'; + } + else if ($this->isPGSQL($targetType)) { + $tableName = '"' . $tableName . '"'; + } + + return $tableName; + } + + /** + * Fixes the column name according to the target database type. + * + * This method applies proper quoting for column names based on the target database + * (e.g., MySQL uses backticks for column names). + * + * @param string $columnName The name of the column to fix. + * @param string $targetType The target database type (e.g., 'mysql'). + * @return string The fixed column name. + */ + public function fixColumnName($columnName, $targetType) { + if ($this->isMySQL($targetType)) { + $columnName = '`' . $columnName . '`'; + } + + return $columnName; + } + + /** + * Generates the default value SQL for a column based on its type. + * + * This method returns the appropriate default value syntax for the column's type, + * handling different types such as BOOLEAN, INTEGER, and REAL. + * + * @param string $defaultValue The default value to apply to the column. + * @param string $columnType The type of the column (e.g., 'BOOLEAN', 'INT'). + * @return string The default value SQL definition for the column. + */ + public function getDefaultData($defaultValue, $columnType) { + $colDef = ""; + + if (strtoupper($defaultValue) == 'NULL') { + $colDef .= ' DEFAULT NULL'; // NOSONAR + } + else if ($this->isBoolean($columnType)) { + $colDef .= ' DEFAULT ' . $this->convertToBoolean($defaultValue); // NOSONAR + } + else if (strpos(strtoupper($columnType), 'INT') !== false) { + $colDef .= ' DEFAULT ' . $this->convertToInteger($defaultValue); // NOSONAR + } + else if ($this->isReal($columnType)) { + $colDef .= ' DEFAULT ' . $this->convertToReal($defaultValue); // NOSONAR + } + else { + $colDef .= ' DEFAULT ' . $defaultValue; + } + + return $colDef; + } + + /** + * Converts a value to a boolean format. + * + * @param mixed $value The value to convert. + * @return string The converted boolean value ('TRUE' or 'FALSE'). + */ + public function convertToBoolean($value) { + return (strtolower($value) == 'true' || $value === 1) ? 'TRUE' : 'FALSE'; + } + + /** + * Converts a value to an integer format. + * + * @param mixed $value The value to convert. + * @return int The converted integer value. + */ + public function convertToInteger($value) { + return (int) $value; + } + + /** + * Converts a value to a real number format. + * + * @param mixed $value The value to convert. + * @return float The converted real number value. + */ + public function convertToReal($value) { + return (float) $value; + } + + /** + * Fixes default value for SQLite. + * + * @param string $defaultValue The default value to fix. + * @param string $targetType The target database type. + * @return string The fixed default value. + */ + public function fixDefaultValue($defaultValue, $targetType) { + if ($this->isSQLite($targetType) && stripos($defaultValue, 'now(') !== false) { + $defaultValue = ''; + + } + return $defaultValue; + } + + /** + * Escapes special characters in a string for use in SQL statements. + * + * This method wraps the PHP `addslashes()` function, which adds backslashes before characters + * that need to be escaped in SQL, such as quotes, backslashes, and NULL characters. + * + * @param string $text The input string to escape. + * @return string The escaped string with special characters properly handled. + */ + public function addslashes($text) + { + return addslashes($text); + } + + /** + * Extracts the DROP TABLE IF EXISTS queries from the provided SQL string. + * + * @param string $sql The SQL string to be processed. + * @param string $targetType The type of database ('pgsql', 'mysql', or 'mariadb') to format the table names accordingly. + * @return array An array of objects, each containing the name of a table to be dropped. + */ + public function extractDropTableQueries($sql, $targetType) { + + // Remove backticks (`) from the entire SQL string before processing + $sqlWithoutBackticks = str_replace('`', '', $sql); + $result = []; + try + { + // Regular expression to capture DROP TABLE IF EXISTS command + $regex = '/DROP\s+TABLE\s+IF\s+EXISTS\s+([^\s]+)/i'; + preg_match_all($regex, $sqlWithoutBackticks, $matches); + + // Loop through all matches found + foreach ($matches[1] as $match) { + // Store the result in the desired format + $tableName = $this->extractTableName($match); + + // Format the table name based on the target database type + if ($this->isPGSQL($targetType)) { + $tableName = '"' . $tableName . '"'; + } else if ($this->isMySQL($targetType)) { + $tableName = '`' . $tableName . '`'; + } + $result[] = [ + 'table' => $tableName // Table name + ]; + } + } + catch(Exception $e) + { + // Do nothing + } + + return $result; + } + + /** + * Extracts the table name from the input string, removing schema if present. + * + * @param string $input The input string (may contain schema.table or just table). + * @return string The extracted table name without schema. + */ + public function extractTableName($input) { + // Check if the input contains a dot (indicating a schema) + if (strpos($input, '.') !== false) { + // If there is a dot, take the part after the dot as the table name + $input = explode('.', $input)[1]; + } + // If there is no dot, it means the input is just the table name + return preg_replace('/[^a-zA-Z0-9_]/', '', $input); // NOSONAR + } + + +} diff --git a/src/Util/Database/PicoTableParser.php b/src/Util/Database/PicoTableParser.php new file mode 100644 index 00000000..bd0287ab --- /dev/null +++ b/src/Util/Database/PicoTableParser.php @@ -0,0 +1,414 @@ +getResult(); + * + * The returned result contains structured information about each table, including + * columns, their types, constraints, and additional attributes. + * + * @package MagicObject\Util\Database + */ +class PicoTableParser { + + /** + * List of valid SQL data types used for column validation. + * + * @var array + */ + private $typeList = array(); + + /** + * Holds information about the parsed tables, including columns and their properties. + * + * @var array + */ + private $tableInfo = array(); + + /** + * PicoTableParser constructor. + * + * Initializes the PicoTableParser instance and optionally parses an SQL string. + * + * @param string|null $sql Optional SQL string to parse during initialization. + */ + public function __construct($sql = null) { + $this->init(); + + if ($sql) { + $this->parseAll($sql); + } + } + + /** + * Initializes the type list for valid SQL column types. + * + * This function sets up an array of valid SQL column types, which will be used + * to validate column data types during parsing. + */ + private function init() { + $typeList = 'TIMESTAMPTZ,TIMESTAMP,SERIAL4,BIGSERIAL,INT2,INT4,INT8,TINYINT,BIGINT,LONGTEXT,MEDIUMTEXT,TEXT,NVARCHAR,VARCHAR,ENUM,SET,NUMERIC,DECIMAL,CHAR,REAL,FLOAT,INTEGER,INT,DATETIME,DATE,DOUBLE,BOOLEAN,BOOL,TIME,UUID,MONEY,BLOB,BIT,JSON'; + $this->typeList = explode(',', $typeList); + } + + /** + * Checks if a value exists in an array. + * + * @param array $haystack The array to search. + * @param string $needle The value to search for. + * @return bool Returns true if the needle is found in the haystack, otherwise false. + */ + private function inArray($haystack, $needle) { + return in_array($needle, $haystack); + } + + /** + * Checks if a field is a primary key. + * + * @param string $field The field definition. + * @return bool True if the field is a primary key, otherwise false. + */ + private function isPrimaryKey($field) { + $f = strtoupper(trim(preg_replace('/\s+/', ' ', $field))); // NOSONAR + return strpos($f, 'PRIMARY KEY') !== false; + } + + /** + * Checks if a field is auto-incremented. + * + * @param string $line The field definition. + * @return bool True if the field is auto-incremented, otherwise false. + */ + private function isAutoIncrement($line) { + $f = strtoupper(trim(preg_replace('/\s+/', ' ', $line))); + return strpos($f, 'AUTO_INCREMENT') !== false || + strpos($f, 'SERIAL') !== false || + strpos($f, 'BIGSERIAL') !== false || + strpos($f, 'NEXTVAL') !== false; + } + + /** + * Parses a CREATE TABLE SQL statement and extracts table and column information. + * + * @param string $sql The SQL string representing a CREATE TABLE statement. + * @return array An array containing table name, columns, and primary key information. + */ + public function parseTable($sql) // NOSONAR + { + $rg_tb = '/(create\s+table\s+if\s+not\s+exists|create\s+table)\s(?.*)\s\(/i'; + $rg_fld = '/(\w+\s+key.*|\w+\s+bigserial|\w+\s+serial4|\w+\s+serial8|\w+\s+tinyint.*|\w+\s+bigint.*|\w+\s+longtext.*|\w+\s+mediumtext.*|\w+\s+text.*|\w+\s+nvarchar.*|\w+\s+varchar.*|\w+\s+char.*|\w+\s+real.*|\w+\s+float.*|\w+\s+integer.*|\w+\s+int.*|\w+\s+datetime.*|\w+\s+date.*|\w+\s+double.*|\w+\s+timestamp.*|\w+\s+timestamptz.*|\w+\s+boolean.*|\w+\s+bool.*|\w+\s+enum\s*\(.*\)|\w+\s+set\s*\(.*\)|\w+\s+numeric\s*\(.*\)|\w+\s+decimal\s*\(.*\)|\w+\s+int2.*|\w+\s+int4.*|\w+\s+int8.*|\w+\s+time.*|\w+\s+uuid.*|\w+\s+money.*|\w+\s+blob.*|\w+\s+bit.*|\w+\s+json.*)/i'; // NOSONAR + $rg_fld2 = '/(?\w+)\s+(?\w+)(?.*)/i'; + $rg_enum = '/enum\s*\(([^)]+)\)/i'; + $rg_set = '/set\s*\(([^)]+)\)/i'; + $rg_not_null = '/not\s+null/i'; + $rg_pk = '/primary\s+key/i'; + $rg_fld_def = '/default\s+([^\'"]+|\'[^\']*\'|\"[^\"]*\")\s*(comment\s+\'[^\']*\')?/i'; + $rg_fld_comment = '/COMMENT\s*\'([^\']*)\'/i'; + $rg_pk2 = '/(PRIMARY|UNIQUE) KEY[a-zA-Z_0-9\s]+\(([a-zA-Z_0-9,\s]+)\)/i'; // NOSONAR + + preg_match($rg_tb, $sql, $result); + $tableName = $result['tb']; + + $fieldList = []; + $primaryKey = null; + $columnList = []; + $primaryKeyList = []; + + preg_match_all($rg_fld, $sql, $matches); + + foreach ($matches[0] as $f) { + $line = $f; + preg_match($rg_fld2, $f, $fld_def); + $dataTypeRaw = $fld_def[0]; + $dataType = $fld_def['ftype']; + $dataTypeOriginal = $dataType; + $isPk = false; + $enumValues = null; + $enumArray = null; + + if (preg_match($rg_enum, $dataTypeRaw, $matches)) { + $enumValues = $matches[1]; + $enumArray = array_map('trim', explode(',', $enumValues)); + } + if (preg_match($rg_set, $dataTypeRaw, $matches)) { + $enumValues = $matches[1]; + $enumArray = array_map('trim', explode(',', $enumValues)); + } + + if ($this->isValidType($dataType) || $this->isValidType($dataTypeOriginal)) { + $attr = trim(str_replace(',', '', $fld_def['fattr'])); + $nullable = !preg_match($rg_not_null, $attr); + $attr2 = str_replace($rg_not_null, '', $attr); + + $isPk = preg_match($rg_pk, $attr2) || $this->isPrimaryKey($line); + $isAi = $this->isAutoIncrement($line); + + preg_match($rg_fld_def, $attr2, $def); + $defaultValue = isset($def[1]) ? trim($def[1]) : null; + $defaultValue = $this->fixDefaultValue($defaultValue); + + preg_match($rg_fld_comment, $attr2, $cmn); + $comment = isset($cmn[1]) ? trim($cmn[1]) : null; + + $dataType = trim($dataType); + $length = $this->getLength($attr); + + $columnName = trim($fld_def['fname']); + if ($isPk) + { + $primaryKeyList[] = $columnName; + } + if (!in_array($columnName, $columnList)) { + $fieldList[] = [ + 'Field' => $columnName, + 'Type' => $dataType, + 'Length' => $length, + 'Key' => $isPk, + 'Nullable' => $nullable, + 'Default' => $defaultValue, + 'AutoIncrement' => $isAi, + 'EnumValues' => $enumArray, + 'Comment' => $comment + ]; + $columnList[] = $columnName; + } + } else if ($this->isPrimaryKey($line)) { + preg_match('/\((.*)\)/', $f, $matches); + if ($primaryKey == null) { + $primaryKey = isset($matches[1]) ? $matches[1] : null; + } + } + + if ($primaryKey != null) { + $primaryKey = str_replace(['(', ')'], '', $primaryKey); + foreach ($fieldList as &$column) // NOSONAR + { + if ($column['Field'] == $primaryKey) { + $column['Key'] = true; + } + } + } + + if (preg_match($rg_pk2, $f) && preg_match($rg_pk, $f)) { + $x = str_replace(preg_match($rg_pk, $f)[0], '', $f); + $x = str_replace(['(', ')'], '', $x); + $pkeys = array_map('trim', explode(',', $x)); + foreach ($fieldList as &$column) { + if (in_array($column['Field'], $pkeys)) { + $column['Key'] = true; + } + } + } + } + + if ($primaryKey == null) { + $primaryKey = $primaryKeyList[0] ?? null; + } + + return ['tableName' => $tableName, 'columns' => $fieldList, 'primaryKey' => $primaryKey]; + } + + /** + * Fixes and normalizes default values in SQL statements. + * + * @param string $defaultValue The raw default value from SQL. + * @return string|null The normalized default value or null if empty. + */ + private function fixDefaultValue($defaultValue) { + $defaultValue = trim($defaultValue); + return empty($defaultValue) || $defaultValue == 'null' ? null : $defaultValue; + } + + /** + * Validates if a type is one of the known types. + * + * @param string $type The data type to validate. + * @return bool True if the type is valid, otherwise false. + */ + private function isValidType($type) { + return $this->inArray($this->typeList, $type); + } + + /** + * Extracts the length for types that require it, such as varchar. + * + * @param string $attr The attributes of the field. + * @return int|null The length of the field or null if not applicable. + */ + private function getLength($attr) { + preg_match('/\((\d+)\)/', $attr, $matches); + return isset($matches[1]) ? (int) trim($matches[1], '()') : null; + } + + /** + * Parses multiple SQL statements. + * + * @param string $sql The SQL statements. + */ + public function parseAll($sql) { + + $tables = $this->parseSQL($sql); + + foreach ($tables as $table) { + if (isset($table['query'])) { + try + { + $info = $this->parseTable($table['query']); + + $this->tableInfo[] = $info; + } + catch(Exception $e) + { + // Do nothing + } + } + } + } + + /** + * Parses a SQL text, splits it into individual SQL queries, and processes them by handling + * delimiters, comments, and empty lines. It returns an array of queries, each with its delimiter. + * + * This method processes the SQL text by: + * 1. Normalizing newlines. + * 2. Removing comment lines and empty lines. + * 3. Splitting the SQL text into queries based on the delimiter. + * 4. Handling multiple queries that may span across multiple lines. + * 5. Changing the delimiter if a new one is specified within the SQL text. + * + * @param string $sqlText The input SQL text to be parsed. + * + * @return array An array of queries, each containing the SQL query string and its corresponding delimiter. + * Each item in the array is an associative array with two keys: + * - 'query': the SQL query string. + * - 'delimiter': the delimiter used for that query (e.g., ";"). + */ + public function parseSQL($sqlText) // NOSONAR + { + $sqlText = str_replace("\n", "\r\n", $sqlText); + $sqlText = str_replace("\r\r\n", "\r\n", $sqlText); + $arr = explode("\r\n", $sqlText); + $arr2 = array(); + foreach($arr as $key=>$val) + { + $arr[$key] = ltrim($val); + if (stripos($arr[$key], "-- ") !== 0 && $arr[$key] !== "--" && $arr[$key] !== "") { + $arr2[] = $arr[$key]; + } + } + $arr = $arr2; + unset($arr2); + + $append = 0; + $skip = 0; + $start = 1; + $nquery = -1; + $delimiter = ";"; + $query_array = array(); + $delimiter_array = array(); + + foreach($arr as $line=>$text) + { + if($text == "" && $append == 1) + { + $query_array[$nquery] .= "\r\n"; + } + if($append == 0) + { + if(stripos(ltrim($text, " \t "), "--") === 0) + { + $skip = 1; + $nquery++; + $start = 1; + $append = 0; + } + else + { + $skip = 0; + } + } + if($skip == 0) + { + if($start == 1) + { + $nquery++; + $query_array[$nquery] = ""; + $delimiter_array[$nquery] = $delimiter; + $start = 0; + } + $query_array[$nquery] .= $text."\r\n"; + $delimiter_array[$nquery] = $delimiter; + $text = ltrim($text, " \t "); + $start = strlen($text)-strlen($delimiter)-1; + if(stripos(substr($text, $start), $delimiter) !== false || $text == $delimiter) + { + $nquery++; + $start = 1; + $append = 0; + } + else + { + $start = 0; + $append = 1; + } + $delimiter_array[$nquery] = $delimiter; + if(stripos($text, "delimiter ") !== false) + { + $text = trim(preg_replace("/\s+/"," ",$text)); + $arr2 = explode(" ", $text); + $delimiter = $arr2[1]; + $nquery++; + $delimiter_array[$nquery] = $delimiter; + $start = 1; + $append = 0; + } + } + } + $result = array(); + foreach($query_array as $line=>$sql) + { + $delimiter = $delimiter_array[$line]; + if (stripos($sql, "delimiter ") !== 0) { + $sql = rtrim($sql, " \r\n\t "); + $sql = substr($sql, 0, strlen($sql) - strlen($delimiter)); + $result[] = array("query" => $sql, "delimiter" => $delimiter); + } + } + return $result; + } + + /** + * Returns the table information parsed from the SQL statements. + * + * @return array An array of parsed table information. + */ + public function getTableInfo() { + return $this->tableInfo; + } + + /** + * Get the value of tableInfo. + * + * @return array The table information. + */ + public function getResult() + { + return $this->getTableInfo(); + } +} From 7bcad702cae4d4c6a3c38e64d6e0a6177b28c12b Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sat, 4 Jan 2025 12:34:14 +0700 Subject: [PATCH 3/4] Update PicoSession --- src/Session/PicoSession.php | 50 +++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/Session/PicoSession.php b/src/Session/PicoSession.php index ffcfc2ad..8540c64c 100644 --- a/src/Session/PicoSession.php +++ b/src/Session/PicoSession.php @@ -195,26 +195,29 @@ public function destroy() } /** - * Sets cookie parameters for the session. + * Sets the session cookie parameters, including lifetime, path, domain, security attributes, and SameSite settings. * - * This method sets parameters for the session cookie, including lifetime, - * security attributes, and SameSite settings. + * This method configures the session cookie parameters such as maximum lifetime, path, domain, and security settings + * like whether the cookie should be accessible only over HTTPS or only via HTTP. It also sets the SameSite attribute + * for compatibility with different browsers and PHP versions. * - * @param int $maxlifetime Maximum lifetime of the session cookie. + * @param int $maxlifetime Maximum lifetime of the session cookie in seconds. + * @param string $path The path where the cookie will be available on the server. + * @param string $domain The domain to which the cookie is available. * @param bool $secure Indicates if the cookie should only be transmitted over a secure HTTPS connection. - * @param bool $httponly Indicates if the cookie is accessible only through the HTTP protocol. - * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None). + * @param bool $httponly Indicates if the cookie should be accessible only through the HTTP protocol. + * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None). Default is 'Strict'. * @return self Returns the current instance for method chaining. */ - public function setSessionCookieParams($maxlifetime, $secure, $httponly, $samesite = self::SAME_SITE_STRICT) + public function setSessionCookieParams($maxlifetime, $path, $domain, $secure, $httponly, $samesite = self::SAME_SITE_STRICT) { if (PHP_VERSION_ID < 70300) { - session_set_cookie_params($maxlifetime, '/; samesite=' . $samesite, $_SERVER['HTTP_HOST'], $secure, $httponly); + session_set_cookie_params($maxlifetime, $path.'; samesite=' . $samesite, $domain, $secure, $httponly); } else { session_set_cookie_params(array( 'lifetime' => $maxlifetime, - 'path' => '/', - 'domain' => $_SERVER['HTTP_HOST'], + 'path' => $path, + 'domain' => $domain, 'secure' => $secure, 'httponly' => $httponly, 'samesite' => $samesite @@ -224,18 +227,19 @@ public function setSessionCookieParams($maxlifetime, $secure, $httponly, $samesi } /** - * Sets a cookie with SameSite attribute support for different PHP versions. + * Sets a cookie with the SameSite attribute, supporting both older and newer PHP versions. * - * This method sets a cookie with the specified SameSite attribute, supporting both older and newer PHP versions. + * This method sets a cookie with a specified SameSite attribute, ensuring compatibility with both PHP versions + * prior to and after PHP 7.3. It supports cookies with the 'Lax', 'Strict', or 'None' SameSite attributes. * * @param string $name The name of the cookie. * @param string $value The value of the cookie. - * @param int $expire The expiration time of the cookie. - * @param string $path The path on the server in which the cookie will be available. - * @param string $domain The domain that the cookie is available to. + * @param int $expire The expiration time of the cookie as a Unix timestamp. + * @param string $path The path on the server where the cookie is available. + * @param string $domain The domain to which the cookie is available. * @param bool $secure Indicates if the cookie should only be transmitted over a secure HTTPS connection. * @param bool $httponly Indicates if the cookie is accessible only through the HTTP protocol. - * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None). + * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None). Default is 'Strict'. * @return self Returns the current instance for method chaining. */ public function setSessionCookieSameSite($name, $value, $expire, $path, $domain, $secure, $httponly, $samesite = self::SAME_SITE_STRICT) // NOSONAR @@ -355,4 +359,18 @@ public function setSessionId($id) @session_id($id); return $this; } + + /** + * Converts the session data to a string representation. + * + * This method returns the session data as a JSON-encoded string, which can be useful for debugging + * or logging the contents of the session. It encodes the global `$_SESSION` array into a JSON string. + * + * @return string The JSON-encoded string representation of the session data. + */ + public function __toString() + { + return json_encode($_SESSION); + } + } From b0fdeac04d29e2af2cfc782505af52debab1b6f7 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sat, 4 Jan 2025 12:44:51 +0700 Subject: [PATCH 4/4] Update session documentation --- manual/includes/_session.md | 48 +++++++++++++++++++++++------- manual/index.html | 58 +++++++++++++++++++++++++++++-------- tutorial.md | 48 +++++++++++++++++++++++------- 3 files changed, 120 insertions(+), 34 deletions(-) diff --git a/manual/includes/_session.md b/manual/includes/_session.md index 460042cd..06b856c7 100644 --- a/manual/includes/_session.md +++ b/manual/includes/_session.md @@ -8,16 +8,37 @@ Session variables keep information about one single user, and are available to a ```yaml session: - name: MUSICPRODUCTIONMANAGER - max_life_time: 86400 - save_handler: files - save_path: /tmp/sessions + name: ${SESSION_NAME} + max_life_time: ${SESSION_MAX_LIFE_TIME} + save_handler: ${SESSION_SAVE_HANDLER} + save_path: ${SESSION_SAVE_PATH} + cookie_lifetime: ${SESSION_COOKIE_LIFETIME} + cookie_path: ${SESSION_COOKIE_PATH} + cookie_secure: ${SESSION_COOKIE_SECURE} + cookie_httponly: ${SESSION_COOKIE_HTTPONLY} + cookie_domain: ${SESSION_COOKIE_DOMAIN} ``` -- `name`: Name of the session. -- `max_life_time`: Maximum lifetime of the session in seconds (e.g., 86400 seconds = 24 hours). -- `save_handler`: Specifies the session storage mechanism (in this case, files). -- `save_path`: Directory where session files are stored. +### Explanation: + +- **name**: Specifies the name of the session. The value `${SESSION_NAME}` refers to an environment variable or a parameter that should be defined elsewhere in the application or server environment. It determines the session's name identifier. + +- **max_life_time**: Defines the maximum lifetime of the session in seconds. The value `${SESSION_MAX_LIFE_TIME}` is expected to come from an environment variable or configuration setting, controlling how long the session will last before it expires. + +- **save_handler**: Specifies the handler used to save session data. In this case, `${SESSION_SAVE_HANDLER}` likely refers to the session handler, such as `redis`, `files`, or `database`, depending on the server configuration. + +- **save_path**: Defines the path where session data will be stored. The value `${SESSION_SAVE_PATH}` is an environment variable or configuration parameter, which could specify a directory or Redis connection string (if `redis` is used as the save handler). + +- **cookie_lifetime**: Specifies the lifetime of the session cookie in seconds, which controls how long the cookie will persist in the user's browser. `${SESSION_COOKIE_LIFETIME}` is the environment variable or configuration value that sets this duration. + +- **cookie_path**: The path on the server where the session cookie is valid. `${SESSION_COOKIE_PATH}` determines the directory or URL prefix for which the session cookie is set. + +- **cookie_secure**: A boolean flag that determines whether the session cookie should be transmitted over HTTPS only. If `${SESSION_COOKIE_SECURE}` is set to `true`, the cookie will only be sent over secure connections. + +- **cookie_httponly**: A boolean flag that indicates whether the session cookie is accessible only through the HTTP protocol (i.e., not accessible via JavaScript). If `${SESSION_COOKIE_HTTPONLY}` is `true`, it increases security by preventing client-side access to the cookie. + +- **cookie_domain**: Specifies the domain to which the session cookie is valid. `${SESSION_COOKIE_DOMAIN}` can define the domain for which the session cookie should be sent, allowing cross-subdomain access to the session. + **PHP Script** @@ -68,10 +89,15 @@ For example: ```yaml session: - name: MUSICPRODUCTIONMANAGER - max_life_time: 86400 - save_handler: redis + name: ${SESSION_NAME} + max_life_time: ${SESSION_MAX_LIFE_TIME} + save_handler: ${SESSION_SAVE_HANDLER} save_path: ${SESSION_SAVE_PATH} + cookie_lifetime: ${SESSION_COOKIE_LIFETIME} + cookie_path: ${SESSION_COOKIE_PATH} + cookie_secure: ${SESSION_COOKIE_SECURE} + cookie_httponly: ${SESSION_COOKIE_HTTPONLY} + cookie_domain: ${SESSION_COOKIE_DOMAIN} ``` `${SESSION_SAVE_PATH}` contains entire of `save_path` that encrypted with you secure key. diff --git a/manual/index.html b/manual/index.html index 431c64cb..8eca2dd3 100644 --- a/manual/index.html +++ b/manual/index.html @@ -1989,15 +1989,44 @@

Session

Session with File

Yaml File

session:
-  name: MUSICPRODUCTIONMANAGER
-  max_life_time: 86400
-  save_handler: files
-  save_path: /tmp/sessions
+ name: ${SESSION_NAME} + max_life_time: ${SESSION_MAX_LIFE_TIME} + save_handler: ${SESSION_SAVE_HANDLER} + save_path: ${SESSION_SAVE_PATH} + cookie_lifetime: ${SESSION_COOKIE_LIFETIME} + cookie_path: ${SESSION_COOKIE_PATH} + cookie_secure: ${SESSION_COOKIE_SECURE} + cookie_httponly: ${SESSION_COOKIE_HTTPONLY} + cookie_domain: ${SESSION_COOKIE_DOMAIN} +

Explanation:

    -
  • name: Name of the session.
  • -
  • max_life_time: Maximum lifetime of the session in seconds (e.g., 86400 seconds = 24 hours).
  • -
  • save_handler: Specifies the session storage mechanism (in this case, files).
  • -
  • save_path: Directory where session files are stored.
  • +
  • +

    name: Specifies the name of the session. The value ${SESSION_NAME} refers to an environment variable or a parameter that should be defined elsewhere in the application or server environment. It determines the session's name identifier.

    +
  • +
  • +

    max_life_time: Defines the maximum lifetime of the session in seconds. The value ${SESSION_MAX_LIFE_TIME} is expected to come from an environment variable or configuration setting, controlling how long the session will last before it expires.

    +
  • +
  • +

    save_handler: Specifies the handler used to save session data. In this case, ${SESSION_SAVE_HANDLER} likely refers to the session handler, such as redis, files, or database, depending on the server configuration.

    +
  • +
  • +

    save_path: Defines the path where session data will be stored. The value ${SESSION_SAVE_PATH} is an environment variable or configuration parameter, which could specify a directory or Redis connection string (if redis is used as the save handler).

    +
  • +
  • +

    cookie_lifetime: Specifies the lifetime of the session cookie in seconds, which controls how long the cookie will persist in the user's browser. ${SESSION_COOKIE_LIFETIME} is the environment variable or configuration value that sets this duration.

    +
  • +
  • +

    cookie_path: The path on the server where the session cookie is valid. ${SESSION_COOKIE_PATH} determines the directory or URL prefix for which the session cookie is set.

    +
  • +
  • +

    cookie_secure: A boolean flag that determines whether the session cookie should be transmitted over HTTPS only. If ${SESSION_COOKIE_SECURE} is set to true, the cookie will only be sent over secure connections.

    +
  • +
  • +

    cookie_httponly: A boolean flag that indicates whether the session cookie is accessible only through the HTTP protocol (i.e., not accessible via JavaScript). If ${SESSION_COOKIE_HTTPONLY} is true, it increases security by preventing client-side access to the cookie.

    +
  • +
  • +

    cookie_domain: Specifies the domain to which the session cookie is valid. ${SESSION_COOKIE_DOMAIN} can define the domain for which the session cookie should be sent, allowing cross-subdomain access to the session.

    +

PHP Script

<?php
@@ -2031,10 +2060,15 @@ 

Session with Redis

You can not encrypt the ${REDIS_AUTH} value. If you want to secure the config, encrypt entire save_path instead.

For example:

session:
-  name: MUSICPRODUCTIONMANAGER
-  max_life_time: 86400
-  save_handler: redis
-  save_path: ${SESSION_SAVE_PATH}
+ name: ${SESSION_NAME} + max_life_time: ${SESSION_MAX_LIFE_TIME} + save_handler: ${SESSION_SAVE_HANDLER} + save_path: ${SESSION_SAVE_PATH} + cookie_lifetime: ${SESSION_COOKIE_LIFETIME} + cookie_path: ${SESSION_COOKIE_PATH} + cookie_secure: ${SESSION_COOKIE_SECURE} + cookie_httponly: ${SESSION_COOKIE_HTTPONLY} + cookie_domain: ${SESSION_COOKIE_DOMAIN}

${SESSION_SAVE_PATH} contains entire of save_path that encrypted with you secure key.

PHP Script

<?php
diff --git a/tutorial.md b/tutorial.md
index d9f6186c..b3e1bb02 100644
--- a/tutorial.md
+++ b/tutorial.md
@@ -2294,16 +2294,37 @@ Session variables keep information about one single user, and are available to a
 
 ```yaml
 session:
-  name: MUSICPRODUCTIONMANAGER
-  max_life_time: 86400
-  save_handler: files
-  save_path: /tmp/sessions
+  name: ${SESSION_NAME}
+  max_life_time: ${SESSION_MAX_LIFE_TIME}
+  save_handler: ${SESSION_SAVE_HANDLER}
+  save_path: ${SESSION_SAVE_PATH}
+  cookie_lifetime: ${SESSION_COOKIE_LIFETIME}
+  cookie_path: ${SESSION_COOKIE_PATH}
+  cookie_secure: ${SESSION_COOKIE_SECURE}
+  cookie_httponly: ${SESSION_COOKIE_HTTPONLY}
+  cookie_domain: ${SESSION_COOKIE_DOMAIN}
 ```
 
-- `name`: Name of the session.
-- `max_life_time`: Maximum lifetime of the session in seconds (e.g., 86400 seconds = 24 hours).
-- `save_handler`: Specifies the session storage mechanism (in this case, files).
-- `save_path`: Directory where session files are stored.
+### Explanation:
+  
+-   **name**: Specifies the name of the session. The value `${SESSION_NAME}` refers to an environment variable or a parameter that should be defined elsewhere in the application or server environment. It determines the session's name identifier.
+    
+-   **max_life_time**: Defines the maximum lifetime of the session in seconds. The value `${SESSION_MAX_LIFE_TIME}` is expected to come from an environment variable or configuration setting, controlling how long the session will last before it expires.
+    
+-   **save_handler**: Specifies the handler used to save session data. In this case, `${SESSION_SAVE_HANDLER}` likely refers to the session handler, such as `redis`, `files`, or `database`, depending on the server configuration.
+    
+-   **save_path**: Defines the path where session data will be stored. The value `${SESSION_SAVE_PATH}` is an environment variable or configuration parameter, which could specify a directory or Redis connection string (if `redis` is used as the save handler).
+    
+-   **cookie_lifetime**: Specifies the lifetime of the session cookie in seconds, which controls how long the cookie will persist in the user's browser. `${SESSION_COOKIE_LIFETIME}` is the environment variable or configuration value that sets this duration.
+    
+-   **cookie_path**: The path on the server where the session cookie is valid. `${SESSION_COOKIE_PATH}` determines the directory or URL prefix for which the session cookie is set.
+    
+-   **cookie_secure**: A boolean flag that determines whether the session cookie should be transmitted over HTTPS only. If `${SESSION_COOKIE_SECURE}` is set to `true`, the cookie will only be sent over secure connections.
+    
+-   **cookie_httponly**: A boolean flag that indicates whether the session cookie is accessible only through the HTTP protocol (i.e., not accessible via JavaScript). If `${SESSION_COOKIE_HTTPONLY}` is `true`, it increases security by preventing client-side access to the cookie.
+    
+-   **cookie_domain**: Specifies the domain to which the session cookie is valid. `${SESSION_COOKIE_DOMAIN}` can define the domain for which the session cookie should be sent, allowing cross-subdomain access to the session.
+
 
 **PHP Script**
 
@@ -2354,10 +2375,15 @@ For example:
 
 ```yaml
 session:
-  name: MUSICPRODUCTIONMANAGER
-  max_life_time: 86400
-  save_handler: redis
+  name: ${SESSION_NAME}
+  max_life_time: ${SESSION_MAX_LIFE_TIME}
+  save_handler: ${SESSION_SAVE_HANDLER}
   save_path: ${SESSION_SAVE_PATH}
+  cookie_lifetime: ${SESSION_COOKIE_LIFETIME}
+  cookie_path: ${SESSION_COOKIE_PATH}
+  cookie_secure: ${SESSION_COOKIE_SECURE}
+  cookie_httponly: ${SESSION_COOKIE_HTTPONLY}
+  cookie_domain: ${SESSION_COOKIE_DOMAIN}
 ```
 
 `${SESSION_SAVE_PATH}` contains entire of `save_path` that encrypted with you secure key.