diff --git a/src/Database/PicoDataComparation.php b/src/Database/PicoDataComparation.php index afcab03..d660d84 100644 --- a/src/Database/PicoDataComparation.php +++ b/src/Database/PicoDataComparation.php @@ -6,12 +6,12 @@ * Class PicoDataComparation * * This class provides various comparison operations for use in database queries. - * It allows the creation of comparison objects that can be utilized to - * compare values against specified criteria, facilitating flexible and + * It allows the creation of comparison objects that can be utilized to + * compare values against specified criteria, facilitating flexible and * expressive database querying. * - * The class supports a variety of comparison operators such as equality, - * inequality, inclusion, and range comparisons. Each operator can be + * The class supports a variety of comparison operators such as equality, + * inequality, inclusion, and range comparisons. Each operator can be * applied to values of various types, including strings, booleans, and numbers. * * @author Kamshory @@ -214,7 +214,7 @@ public function __construct($value, $comparison = self::EQUALS) /** * Returns the appropriate equals operator based on the value's state. * - * If the value is null or of type null, returns the IS operator; + * If the value is null or of type null, returns the IS operator; * otherwise, returns the standard equals operator. * * @return string The equals operator. @@ -227,7 +227,7 @@ private function _equals() /** * Returns the appropriate not equals operator based on the value's state. * - * If the value is null or of type null, returns the IS NOT operator; + * If the value is null or of type null, returns the IS NOT operator; * otherwise, returns the standard not equals operator. * * @return string The not equals operator. diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php index 770b32d..69dad06 100644 --- a/src/Database/PicoDatabase.php +++ b/src/Database/PicoDatabase.php @@ -17,18 +17,18 @@ /** * PicoDatabase provides an interface for database interactions using PDO. - * + * * This class manages database connections, query execution, and transactions. - * It supports callbacks for query execution and debugging, allowing developers + * It supports callbacks for query execution and debugging, allowing developers * to handle SQL commands and responses effectively. - * + * * Features include: * - Establishing and managing a database connection. * - Executing various SQL commands (INSERT, UPDATE, DELETE, etc.). * - Transaction management with commit and rollback functionality. * - Fetching results in different formats (array, object, etc.). * - Generating unique IDs and retrieving the last inserted ID. - * + * * **Example:** * ```php * connect(); * $result = $db->fetch("SELECT * FROM users WHERE id = ?", 123); * ``` - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -102,21 +102,21 @@ class PicoDatabase // NOSONAR * Creates a PicoDatabase instance from an existing PDO connection. * * This static method accepts a PDO connection object and attempts to extract - * database credentials (such as host, port, schema, and timezone) directly - * from the connection. If certain details cannot be retrieved from the PDO - * object (for example, if the driver does not support a specific attribute), - * the method will use the optional $databaseCredentials parameter as a + * database credentials (such as host, port, schema, and timezone) directly + * from the connection. If certain details cannot be retrieved from the PDO + * object (for example, if the driver does not support a specific attribute), + * the method will use the optional $databaseCredentials parameter as a * fallback source. * - * The resulting PicoDatabase instance will contain the PDO connection, + * The resulting PicoDatabase instance will contain the PDO connection, * resolved database type, and credentials, and will be marked as connected. * - * @param PDO $pdo The PDO connection object representing an active + * @param PDO $pdo The PDO connection object representing an active * connection to the database. - * @param SecretObject|null $databaseCredentials Optional fallback credentials + * @param SecretObject|null $databaseCredentials Optional fallback credentials * to use if details cannot be extracted from the PDO object. - * @return PicoDatabase Returns a new instance of the PicoDatabase class, - * configured with the PDO connection, database type, + * @return PicoDatabase Returns a new instance of the PicoDatabase class, + * configured with the PDO connection, database type, * and credentials. */ public static function fromPdo($pdo, $databaseCredentials = null) @@ -296,8 +296,8 @@ private static function getTimeZoneOffset($pdo) * Converts a timezone offset string to a corresponding PHP timezone name. * * This method takes a timezone offset string (e.g., "+08:00" or "-05:30") and computes - * the total offset in seconds. It then attempts to map the offset to a standard PHP - * timezone name. If no matching timezone is found, it falls back to returning a + * the total offset in seconds. It then attempts to map the offset to a standard PHP + * timezone name. If no matching timezone is found, it falls back to returning a * UTC-based timezone string in the same offset format. * * Examples: @@ -312,12 +312,12 @@ private static function convertOffsetToTimeZone($offset) try { // Extract the sign ('+' or '-') from the offset $sign = substr($offset, 0, 1); // Get the first character ('+' or '-') - + // Split the offset into hours and minutes (e.g., "+08:00" -> [8, 0]) $parts = explode(':', substr($offset, 1)); // Remove the sign and split $hours = (int)$parts[0]; // Parse the hours $minutes = isset($parts[1]) ? (int)$parts[1] : 0; // Parse the minutes if available - + // Calculate the total offset in seconds $totalOffsetSeconds = ($hours * 3600) + ($minutes * 60); if ($sign === '-') { @@ -359,7 +359,7 @@ public function __construct($databaseCredentials, $callbackExecuteQuery = null, /** * Connect to the database. * - * Establishes a connection to the specified database type. Optionally selects a database if the + * Establishes a connection to the specified database type. Optionally selects a database if the * connection is to an RDMS and the flag is set. * * @param int $timeout Connection timeout in seconds. @@ -369,7 +369,7 @@ public function __construct($databaseCredentials, $callbackExecuteQuery = null, public function connect($withDatabase = true) { $timeout = $this->databaseCredentials->issetConnectionTimeout() ? $this->databaseCredentials->getConnectionTimeout() : 0; - $databaseTimeZone = $this->databaseCredentials->getTimeZone(); + $databaseTimeZone = $this->databaseCredentials->getTimeZone(); if ($databaseTimeZone !== null && !empty($databaseTimeZone)) { date_default_timezone_set($this->databaseCredentials->getTimeZone()); } @@ -383,7 +383,7 @@ public function connect($withDatabase = true) return $this->connectRDMS($withDatabase, $timeout); } } - + /** * Connect to SQLite database. * @@ -403,7 +403,11 @@ private function connectSqlite() throw new InvalidDatabaseConfiguration("Database path may not be empty. Please check your database configuration on {database_file_path}!"); } try { - $this->databaseConnection = new PDO("sqlite:" . $path); + if(stripos($path, 'sqlite:') === false) + { + $path = 'sqlite:' . $path; + } + $this->databaseConnection = new PDO($path); $this->databaseConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $connected = true; $this->connected = true; @@ -412,16 +416,16 @@ private function connectSqlite() } return $connected; } - + /** * Connect to the RDMS (Relational Database Management System). * - * Establishes a connection to an RDMS database using the provided credentials. Optionally, a specific - * database is selected based on the provided flag. This method also configures the time zone, character set, + * Establishes a connection to an RDMS database using the provided credentials. Optionally, a specific + * database is selected based on the provided flag. This method also configures the time zone, character set, * and schema settings (for PostgreSQL) after the connection is established. * * - The time zone is set based on the current offset (`date("P")`), or a configured value. - * - For PostgreSQL, the client encoding (charset) is set using `SET CLIENT_ENCODING`, and the schema is set + * - For PostgreSQL, the client encoding (charset) is set using `SET CLIENT_ENCODING`, and the schema is set * using `SET search_path`. * - For MySQL, the time zone and charset are set using `SET time_zone` and `SET NAMES`. * @@ -479,7 +483,7 @@ private function connectRDMS($withDatabase = true, $timeout = 0) * Establish a connection to a MySQL or MariaDB database. * * This method sets up a connection to a MySQL or MariaDB database, configuring the time zone - * and character set (charset) as needed. It runs initial queries to set the correct time zone + * and character set (charset) as needed. It runs initial queries to set the correct time zone * and charset, and then establishes a PDO connection to the database. * * @param string $connectionString The connection string used to connect to the database. @@ -496,7 +500,7 @@ private function connectMySql($connectionString, $timeZoneOffset, $charset, $tim $initialQueries = array(); // Set time zone for MySQL $initialQueries[] = "SET time_zone='$timeZoneOffset';"; - + // Add charset to the initial queries for MySQL if ($charset) { $initialQueries[] = "SET NAMES '$charset';"; // Set charset for MySQL @@ -530,8 +534,8 @@ private function connectMySql($connectionString, $timeZoneOffset, $charset, $tim * Establish a connection to a PostgreSQL database. * * This method sets up a connection to a PostgreSQL database, configuring the time zone, - * character set (charset), and schema (search path) as needed. It runs initial queries - * to set the correct time zone, charset, and schema for the session, and then establishes + * character set (charset), and schema (search path) as needed. It runs initial queries + * to set the correct time zone, charset, and schema for the session, and then establishes * a PDO connection to the database. * * @param string $connectionString The connection string used to connect to the PostgreSQL database. @@ -571,7 +575,7 @@ private function connectPostgreSql($connectionString, $timeZoneOffset, $charset, $connectionString, $this->databaseCredentials->getUsername(), $this->databaseCredentials->getPassword(), - $options + $options ); // Execute the initial queries (timezone, charset, schema) in PostgreSQL @@ -582,7 +586,7 @@ private function connectPostgreSql($connectionString, $timeZoneOffset, $charset, } } } - + /** * Establish a connection to a SQL Server database. * @@ -623,11 +627,11 @@ private function connectSqlServer($connectionString, $timeout = 0) } } - + /** * Determine the database type from a string. * - * This method evaluates the provided string to identify common database types such as SQLite, PostgreSQL, + * This method evaluates the provided string to identify common database types such as SQLite, PostgreSQL, * MariaDB, MySQL, and SQL Server. It returns the corresponding constant from the `PicoDatabaseType` class. * If the provided database type is not supported, it throws an `UnsupportedDatabaseException`. * @@ -671,12 +675,12 @@ public static function getDbType($databaseType) // NOSONAR /** * Determines the database driver based on the provided database type. * - * This function takes a string representing the database type and returns + * This function takes a string representing the database type and returns * the corresponding database driver constant from the `PicoDatabaseType` class. * It supports SQLite, PostgreSQL, MySQL/MariaDB, and SQL Server types. * * @param string $databaseType The type of the database (e.g., 'sqlite', 'postgres', 'pgsql', 'mysql', 'mariadb', 'sqlsrv'). - * + * * @return string The corresponding database driver constant, one of: * - `PicoDatabaseType::DATABASE_TYPE_SQLITE` * - `PicoDatabaseType::DATABASE_TYPE_PGSQL` @@ -735,7 +739,7 @@ private function constructConnectionString($withDatabase = true) // NOSONAR $this->databaseCredentials->getHost(), $this->databaseCredentials->getDatabaseName() ); - } + } else { return sprintf( @@ -803,14 +807,14 @@ public function setTimeZoneOffset($timeZoneOffset) $sql = "SET time_zone='$timeZoneOffset'"; $this->execute($sql); } - + return $this; } - + /** * Set the time zone offset for the database session. * - * This method sets the time zone offset for the current database session. This is useful for ensuring that + * This method sets the time zone offset for the current database session. This is useful for ensuring that * any time-related operations (such as querying and storing timestamps) are adjusted to the correct time zone. * The method generates the appropriate SQL command based on the type of the database (e.g., PostgreSQL, MySQL, etc.) * and executes it to apply the time zone setting. @@ -823,7 +827,7 @@ public function setTimeZone($timezone) { return $this->setTimeZoneOffset(self::getTimeZoneOffsetFromString($timezone)); } - + /** * Converts a timezone name (e.g., 'Asia/Jakarta') to its corresponding UTC offset (e.g., '+07:00' or '-03:00'). * @@ -836,10 +840,10 @@ public static function getTimeZoneOffsetFromString($timezone) { // Create a DateTimeZone object for the provided timezone $zone = new DateTimeZone($timezone); - + // Get the current time in the given timezone $now = new DateTime("now", $zone); - + // Get the offset in seconds from UTC $offsetInSeconds = $zone->getOffset($now); @@ -941,7 +945,7 @@ public function getDatabaseConnection() * * @param string $sql The SQL query to execute. * @param array|null $params Optional parameters to bind to the query. - * @return PDOStatement|false Returns a `PDOStatement` object if the query was executed successfully, + * @return PDOStatement|false Returns a `PDOStatement` object if the query was executed successfully, * or `false` if the execution failed. * @throws PDOException If an error occurs while executing the query. */ @@ -966,11 +970,11 @@ public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = n if ($this->databaseConnection == null) { throw new NullPointerException(self::DATABASE_NONECTION_IS_NULL); } - + $result = array(); $this->executeDebug($sql); $stmt = $this->databaseConnection->prepare($sql); - + try { $stmt->execute($params); if($this->getDatabaseType() == PicoDatabaseType::DATABASE_TYPE_SQLITE) @@ -988,7 +992,7 @@ public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = n } catch (PDOException $e) { $result = $defaultValue; } - + return $result; } @@ -1007,10 +1011,10 @@ public function isRecordExists($sql, $params = null) if ($this->databaseConnection == null) { throw new NullPointerException(self::DATABASE_NONECTION_IS_NULL); } - + $this->executeDebug($sql); $stmt = $this->databaseConnection->prepare($sql); - + try { $stmt->execute($params); if($this->getDatabaseType() == PicoDatabaseType::DATABASE_TYPE_SQLITE) @@ -1043,11 +1047,11 @@ public function fetchAll($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue if ($this->databaseConnection == null) { throw new NullPointerException(self::DATABASE_NONECTION_IS_NULL); } - + $result = array(); $this->executeDebug($sql); $stmt = $this->databaseConnection->prepare($sql); - + try { $stmt->execute($params); if($this->getDatabaseType() == PicoDatabaseType::DATABASE_TYPE_SQLITE) @@ -1065,7 +1069,7 @@ public function fetchAll($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue } catch (PDOException $e) { $result = $defaultValue; } - + return $result; } @@ -1101,16 +1105,16 @@ public function executeQuery($sql, $params = null) if ($this->databaseConnection == null) { throw new NullPointerException(self::DATABASE_NONECTION_IS_NULL); } - + $this->executeDebug($sql, $params); $stmt = $this->databaseConnection->prepare($sql); - + try { $stmt->execute($params); } catch (PDOException $e) { throw new PDOException($e->getMessage(), intval($e->getCode())); } - + return $stmt; } @@ -1230,7 +1234,7 @@ private function executeDebug($query, $params = null) else { call_user_func($this->callbackDebugQuery, $query); - } + } } } @@ -1341,7 +1345,7 @@ public function getDatabaseTimeZoneOffset() /** * Convert the object to a JSON string representation for debugging. * - * This method is intended for debugging purposes only and provides + * This method is intended for debugging purposes only and provides * a JSON representation of the object's state. * * @return string The JSON representation of the object. @@ -1359,7 +1363,7 @@ public function __toString() /** * Get the callback function to be executed when modifying data with queries. * - * This function returns the callback that is invoked when executing queries + * This function returns the callback that is invoked when executing queries * that modify data (e.g., `INSERT`, `UPDATE`, `DELETE`). * * @return callable|null The callback function, or null if no callback is set. @@ -1372,12 +1376,12 @@ public function getCallbackExecuteQuery() /** * Set the callback function to be executed when modifying data with queries. * - * This method sets the callback to be invoked when executing queries + * This method sets the callback to be invoked when executing queries * that modify data (e.g., `INSERT`, `UPDATE`, `DELETE`). * * @param callable|null $callbackExecuteQuery The callback function to set, or null to unset the callback. * @return self Returns the current instance for method chaining. - */ + */ public function setCallbackExecuteQuery($callbackExecuteQuery) { $this->callbackExecuteQuery = $callbackExecuteQuery; @@ -1388,7 +1392,7 @@ public function setCallbackExecuteQuery($callbackExecuteQuery) /** * Get the callback function to be executed when executing any query. * - * This function returns the callback that is invoked for any type of query, + * This function returns the callback that is invoked for any type of query, * whether it's a read (`SELECT`) or modify (`INSERT`, `UPDATE`, `DELETE`). * * @return callable|null The callback function, or null if no callback is set. @@ -1401,7 +1405,7 @@ public function getCallbackDebugQuery() /** * Set the callback function to be executed when executing any query. * - * This method sets the callback to be invoked for any type of query, + * This method sets the callback to be invoked for any type of query, * whether it's a read (`SELECT`) or modify (`INSERT`, `UPDATE`, `DELETE`). * * @param callable|null $callbackDebugQuery The callback function to set, or null to unset the callback. @@ -1417,9 +1421,9 @@ public function setCallbackDebugQuery($callbackDebugQuery) /** * Checks whether the PDO connection is fully active and able to execute queries. * - * This method not only verifies that a TCP connection to the database exists, - * but also ensures that PHP can successfully execute a simple SQL statement - * on the target database. By running `SELECT 1 + 1 AS two`, it validates + * This method not only verifies that a TCP connection to the database exists, + * but also ensures that PHP can successfully execute a simple SQL statement + * on the target database. By running `SELECT 1 + 1 AS two`, it validates * both the connection and query execution capability. * * @return bool True if the connection and query execution are successful, false otherwise. diff --git a/src/Database/PicoDatabaseCredentials.php b/src/Database/PicoDatabaseCredentials.php index 20d911c..fa63e5e 100644 --- a/src/Database/PicoDatabaseCredentials.php +++ b/src/Database/PicoDatabaseCredentials.php @@ -7,13 +7,13 @@ /** * PicoDatabaseCredentials class - * + * * This class encapsulates database credentials and utilizes the SecretObject to encrypt all attributes, * ensuring the security of database configuration details from unauthorized access. - * + * * It provides getter methods to retrieve database connection parameters such as driver, host, port, * username, password, database name, schema, and application time zone. - * + * * Example usage: * ```php * setPassword('password'); * $credentials->setDatabaseName('app'); * ``` - * + * * The attributes are automatically encrypted when set, providing a secure way to handle sensitive * information within your application. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -59,7 +59,7 @@ class PicoDatabaseCredentials extends SecretObject * @var string */ protected $host; - + /** * Database server port. * @@ -101,7 +101,7 @@ class PicoDatabaseCredentials extends SecretObject * @DecryptOut * @var string */ - protected $databaseSchema; + protected $databaseSchema; /** * Application time zone. @@ -131,7 +131,7 @@ class PicoDatabaseCredentials extends SecretObject public function importFromUrl($url, $username = null, $password = null) // NOSONAR { $parts = parse_url($url); - + if (!$parts) { throw new InvalidArgumentException("Invalid database URL"); } diff --git a/src/Database/PicoDatabaseEntity.php b/src/Database/PicoDatabaseEntity.php index 85517a8..d539b41 100644 --- a/src/Database/PicoDatabaseEntity.php +++ b/src/Database/PicoDatabaseEntity.php @@ -8,7 +8,7 @@ * Class PicoDatabaseEntity * * Represents a database entity that manages multiple database connections. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -17,23 +17,23 @@ class PicoDatabaseEntity { /** * An associative array of databases indexed by entity class name. - * - * @var PicoDatabase[] + * + * @var PicoDatabase[] */ private $databases = array(); - + /** * Default database connection * * @var PicoDatabase */ private $defaultDatabase; - + /** * Adds an entity to the database. * * @param MagicObject $entity The entity to add. - * @param PicoDatabase|null $database The database to associate with the entity. If null, + * @param PicoDatabase|null $database The database to associate with the entity. If null, * the current database of the entity will be used. * @return self Returns the current instance for method chaining. */ @@ -51,7 +51,7 @@ public function add($entity, $database = null) } return $this; } - + /** * Gets the database associated with an entity. * @@ -71,7 +71,7 @@ public function getDatabase($entity) * Get default database connection * * @return PicoDatabase Default database connection - */ + */ public function getDefaultDatabase() { return $this->defaultDatabase; @@ -83,7 +83,7 @@ public function getDefaultDatabase() * @param PicoDatabase $defaultDatabase Default database connection * * @return self Returns the current instance for method chaining. - */ + */ public function setDefaultDatabase($defaultDatabase) { $this->defaultDatabase = $defaultDatabase; diff --git a/src/Database/PicoDatabasePersistence.php b/src/Database/PicoDatabasePersistence.php index 998310b..30c5333 100644 --- a/src/Database/PicoDatabasePersistence.php +++ b/src/Database/PicoDatabasePersistence.php @@ -967,7 +967,7 @@ private function getWhereWithColumns($info, $queryBuilder) $result->whereClause = $this->whereStr; return $result; } - + $wheres = array(); $columns = array(); foreach($info->getPrimaryKeys() as $property=>$column) @@ -990,7 +990,7 @@ private function getWhereWithColumns($info, $queryBuilder) { throw new NoPrimaryKeyDefinedException("No primary key defined"); } - + $result->columns = $columns; $result->whereClause = implode(" and ", $wheres); return $result; diff --git a/src/Database/PicoDatabasePersistenceExtended.php b/src/Database/PicoDatabasePersistenceExtended.php index dffe62d..0f923fc 100644 --- a/src/Database/PicoDatabasePersistenceExtended.php +++ b/src/Database/PicoDatabasePersistenceExtended.php @@ -13,7 +13,7 @@ * This class extends the functionality of the PicoDatabasePersistence * by adding dynamic property setting through magic methods and enhanced * record selection capabilities. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -43,7 +43,7 @@ public function with() /** * Sets a property value and adds it to the internal map. * - * This method sets a value to a property of the associated object and adds the property name and value + * This method sets a value to a property of the associated object and adds the property name and value * to an internal map for further processing. * * @param string $propertyName The name of the property to set. @@ -69,7 +69,7 @@ public function set($propertyName, $propertyValue) private function addToMap($propertyName, $propertyValue) { $this->map[] = array( - 'property' => $propertyName, + 'property' => $propertyName, 'value' => $propertyValue ); return $this; @@ -155,16 +155,16 @@ public function validate( // Call the main validation utility only once ValidationUtil::getInstance($messageTemplate)->validate($objectToValidate, $parentPropertyName); } - + return $this; } - + /** * Get the current database for the specified entity. * - * This method retrieves the database connection associated with the - * provided entity. If the entity does not have an associated database - * or if the connection is not valid, it defaults to the object's + * This method retrieves the database connection associated with the + * provided entity. If the entity does not have an associated database + * or if the connection is not valid, it defaults to the object's * primary database connection. * * @param MagicObject $entity The entity for which to get the database. @@ -249,7 +249,7 @@ public function selectAll() public function __toString() { return json_encode(array( - 'where' => (string) $this->specification, + 'where' => (string) $this->specification, 'set' => $this->map ), JSON_PRETTY_PRINT); } diff --git a/src/Database/PicoDatabaseQueryBuilder.php b/src/Database/PicoDatabaseQueryBuilder.php index 6604088..ef433af 100644 --- a/src/Database/PicoDatabaseQueryBuilder.php +++ b/src/Database/PicoDatabaseQueryBuilder.php @@ -6,11 +6,11 @@ /** * Class PicoDatabaseQueryBuilder - * - * A query builder for constructing SQL statements programmatically. This class - * facilitates the creation of various SQL commands including SELECT, INSERT, + * + * A query builder for constructing SQL statements programmatically. This class + * facilitates the creation of various SQL commands including SELECT, INSERT, * UPDATE, and DELETE, while managing database-specific nuances. - * + * * **Example:** * ```php * fetch($query); * echo $data['client_id']."\r\n"; // Client ID - * echo $data['name']."\r\n"; // Client name + * echo $data['name']."\r\n"; // Client name * ``` * * @author Kamshory @@ -115,7 +115,7 @@ public function getDatabaseType() */ public function isMySql() { - return strcasecmp($this->databaseType, PicoDatabaseType::DATABASE_TYPE_MYSQL) == 0 || + return strcasecmp($this->databaseType, PicoDatabaseType::DATABASE_TYPE_MYSQL) == 0 || strcasecmp($this->databaseType, PicoDatabaseType::DATABASE_TYPE_MARIADB) == 0; } @@ -138,7 +138,7 @@ public function isSqlite() { return strcasecmp($this->databaseType, PicoDatabaseType::DATABASE_TYPE_SQLITE) == 0; } - + /** * Check if the database type is SQL Server. * @@ -438,8 +438,8 @@ public function where($query) /** * Binds SQL parameters by replacing placeholders with actual values. * - * This function accepts multiple arguments, where the first argument - * is expected to be a SQL string containing `?` placeholders, and + * This function accepts multiple arguments, where the first argument + * is expected to be a SQL string containing `?` placeholders, and * subsequent arguments are the values to replace them. * * @return string The formatted SQL query with values replaced. @@ -647,7 +647,7 @@ public function unlockTables() * * @return string|null The START TRANSACTION statement or null if not supported. */ - public function startTransaction() + public function startTransaction() { if ($this->isMySql() || $this->isPgSql()) { return "START TRANSACTION"; @@ -689,7 +689,7 @@ public function rollback() } return null; } - + /** * Escapes a raw SQL query string to be safely used in an SQL statement, * including handling of single quotes, backslashes, and line breaks, @@ -714,7 +714,7 @@ public function escapeSQL($query) // NOSONAR // Escape carriage return and newline for all $query = str_replace(["\r", "\n"], ["\\r", "\\n"], $query); - if (stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_MYSQL) !== false + if (stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_MYSQL) !== false || stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_MARIADB) !== false) { // MySQL/MariaDB: escape both backslash and single quote return str_replace( @@ -751,19 +751,19 @@ public function escapeSQL($query) // NOSONAR ); } - + /** * Escape a value for SQL queries. * - * This method safely escapes different types of values (null, strings, booleans, - * numeric values, arrays, and objects) to ensure that they can be safely used in - * SQL queries. It prevents SQL injection by escaping potentially dangerous - * characters in string values and converts arrays or objects to their JSON + * This method safely escapes different types of values (null, strings, booleans, + * numeric values, arrays, and objects) to ensure that they can be safely used in + * SQL queries. It prevents SQL injection by escaping potentially dangerous + * characters in string values and converts arrays or objects to their JSON * representation. * - * @param mixed $value The value to be escaped. Can be null, string, boolean, + * @param mixed $value The value to be escaped. Can be null, string, boolean, * numeric, array, or object. - * @return string The escaped value. This will be a string representation + * @return string The escaped value. This will be a string representation * of the value, properly formatted for SQL usage. */ public function escapeValue($value) @@ -805,7 +805,7 @@ public function escapedJSONValues($values) { return $this->escapeValue(json_encode($values)); } - + /** * Convert a value to its boolean representation for SQL. * @@ -1005,16 +1005,16 @@ public function addQueryParameters($query) /** * Adds pagination and sorting clauses to a native query string. - * + * * This function appends the appropriate `ORDER BY` and `LIMIT $limit OFFSET $offset` or `LIMIT $offset, $limit` * clauses to the provided SQL query string based on the given pagination and sorting parameters. - * It supports various database management systems (DBMS) and adjusts the query syntax + * It supports various database management systems (DBMS) and adjusts the query syntax * accordingly (e.g., for PostgreSQL, SQLite, MySQL, MariaDB, etc.). * * @param string $queryString The original SQL query string to which pagination and sorting will be added. * @param PicoPageable|null $pageable The pagination parameters, or `null` if pagination is not required. * @param PicoSortable|null $sortable The sorting parameters, or `null` if sorting is not required. - * + * * @return string The modified SQL query string with added pagination and sorting clauses. */ public function addPaginationAndSorting($queryString, $pageable, $sortable) @@ -1031,8 +1031,8 @@ public function addPaginationAndSorting($queryString, $pageable, $sortable) foreach($sortable->getSortable() as $sort) { $columnName = $sort->getSortBy(); - $sortType = $sort->getSortType(); - $sorts[] = $columnName . " " . $sortType; + $sortType = $sort->getSortType(); + $sorts[] = $columnName . " " . $sortType; } if(!empty($sorts)) { @@ -1060,10 +1060,10 @@ public function addPaginationAndSorting($queryString, $pageable, $sortable) $queryString .= "\r\nOFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY"; } } - + return $queryString; } - + /** * Converts the current object to a string representation. * diff --git a/src/Database/PicoDatabaseQueryTemplate.php b/src/Database/PicoDatabaseQueryTemplate.php index ebc85df..0b879c5 100644 --- a/src/Database/PicoDatabaseQueryTemplate.php +++ b/src/Database/PicoDatabaseQueryTemplate.php @@ -4,11 +4,11 @@ /** * Class PicoDatabaseQueryTemplate - * + * * This class represents a query template or builder that can either hold a * pre-defined query template (as a string) or an instance of a query builder * (PicoDatabaseQueryBuilder). It is designed to facilitate the construction - * and conversion of queries to a string format, either directly or through + * and conversion of queries to a string format, either directly or through * a query builder. * * @author Kamshory @@ -19,7 +19,7 @@ class PicoDatabaseQueryTemplate { /** * The query template as a string. - * + * * This property holds the template for a database query in string format, * or null if no template is provided. * @@ -29,7 +29,7 @@ class PicoDatabaseQueryTemplate /** * The query builder instance. - * + * * This property holds an instance of PicoDatabaseQueryBuilder, which is used * to build and manipulate database queries programmatically. * diff --git a/src/Database/PicoDatabaseStructure.php b/src/Database/PicoDatabaseStructure.php index 729551d..3fa97e8 100644 --- a/src/Database/PicoDatabaseStructure.php +++ b/src/Database/PicoDatabaseStructure.php @@ -12,7 +12,7 @@ * This class is used to manage and generate the structure of a database table based on annotations * defined in a MagicObject class. It provides functionality to create SQL `CREATE TABLE` statements * and retrieve metadata about the table, including its columns, primary keys, and nullable fields. - * + * * It uses annotations such as `@Table` to specify the table name and `@Column` to define column properties * in a class. The class can automatically generate SQL statements for creating a table and its columns * based on this metadata. @@ -95,7 +95,7 @@ private function showCreateTableByType($databaseType, $info) { $createStrArr = array(); $pk = array(); - + if ($databaseType == PicoDatabaseType::DATABASE_TYPE_MYSQL || $databaseType == PicoDatabaseType::DATABASE_TYPE_MARIADB) { foreach ($info->getColumns() as $column) { $createStrArr[] = $column[self::KEY_NAME] . " " . $column[self::KEY_TYPE] . " " . $this->nullable($column[self::KEY_NULLABLE]); diff --git a/src/Database/PicoDatabaseType.php b/src/Database/PicoDatabaseType.php index 3bef9ac..30fecf6 100644 --- a/src/Database/PicoDatabaseType.php +++ b/src/Database/PicoDatabaseType.php @@ -56,7 +56,7 @@ class PicoDatabaseType * @var string */ const DATABASE_TYPE_SQLITE = "sqlite"; - + /** * Constant for SQL Server database type. * diff --git a/src/Database/PicoEntityField.php b/src/Database/PicoEntityField.php index 69b7630..6b30fe9 100644 --- a/src/Database/PicoEntityField.php +++ b/src/Database/PicoEntityField.php @@ -7,7 +7,7 @@ * * This class encapsulates information about an entity field, including * its associated entity, field name, and any parent field relationships. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -58,7 +58,7 @@ class PicoEntityField public function __construct($fieldRaw, $info = null) { $field = $this->extractField($fieldRaw); - + if (strpos($field, ".") !== false) { $arr = explode(".", $field, 2); $this->field = $arr[1]; diff --git a/src/Database/PicoEntityLabel.php b/src/Database/PicoEntityLabel.php index 24328b1..5f652f5 100644 --- a/src/Database/PicoEntityLabel.php +++ b/src/Database/PicoEntityLabel.php @@ -9,9 +9,9 @@ /** * Class to manage entity labels and their annotations. - * + * * Provides methods to retrieve and filter entity metadata, including labels, columns, and other attributes. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -235,15 +235,15 @@ public function getObjectInfo() // NOSONAR // Ensure the column exists before proceeding // Parse the key-value pair for additional details $vals = $this->parseKeyValue($reflexProp, $val, $param); - + // Store the parsed values in the auto-increment keys array $autoIncrementKeys[$prop->name] = array( self::KEY_NAME => isset($columns[$prop->name][self::KEY_NAME]) ? $columns[$prop->name][self::KEY_NAME] : null, self::KEY_STRATEGY => isset($vals[self::KEY_STRATEGY]) ? $vals[self::KEY_STRATEGY] : null, self::KEY_GENERATOR => isset($vals[self::KEY_GENERATOR]) ? $vals[self::KEY_GENERATOR] : null, ); - } - } + } + } // Define default column values foreach ($parameters as $param => $val) { @@ -257,7 +257,7 @@ public function getObjectInfo() // NOSONAR ); } } - } + } // List not null columns foreach ($parameters as $param => $val) { diff --git a/src/Database/PicoJoinMap.php b/src/Database/PicoJoinMap.php index b100549..7b53e2d 100644 --- a/src/Database/PicoJoinMap.php +++ b/src/Database/PicoJoinMap.php @@ -6,7 +6,7 @@ * Class representing a join mapping in a database. * * Contains information about how an entity is joined with another table. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -119,7 +119,7 @@ public function getJoinTableAlias() /** * Convert the object to a JSON string representation for debugging. * - * This method is intended for debugging purposes only and provides + * This method is intended for debugging purposes only and provides * a JSON representation of the object's state. * * @return string The JSON representation of the object. diff --git a/src/Database/PicoLimit.php b/src/Database/PicoLimit.php index 3d87904..d737fe7 100644 --- a/src/Database/PicoLimit.php +++ b/src/Database/PicoLimit.php @@ -5,9 +5,9 @@ /** * Class PicoLimit * - * This class provides functionality to manage pagination in database queries + * This class provides functionality to manage pagination in database queries * by setting limits and offsets for record retrieval. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -43,7 +43,7 @@ public function __construct($offset = 0, $limit = 0) /** * Increment the offset to retrieve the next page of records. * - * This method adjusts the offset based on the current limit, allowing + * This method adjusts the offset based on the current limit, allowing * for the retrieval of the next set of records in a paginated result. * * @return self Returns the current instance for method chaining. @@ -57,7 +57,7 @@ public function nextPage() /** * Decrement the offset to retrieve the previous page of records. * - * This method adjusts the offset back, ensuring it does not fall below + * This method adjusts the offset back, ensuring it does not fall below * zero, thus allowing navigation to the previous set of records. * * @return self Returns the current instance for method chaining. @@ -119,7 +119,7 @@ public function setOffset($offset) /** * Get information about the current page based on the offset and limit. * - * This method calculates the current page number and returns a + * This method calculates the current page number and returns a * PicoPage object containing the page number and limit. * * @return PicoPage The current page information. @@ -128,14 +128,14 @@ public function getPage() { $limit = $this->limit > 0 ? $this->limit : 1; $pageNumber = max(1, round(($this->offset + $limit) / $limit)); - + return new PicoPage($pageNumber, $limit); } /** * Convert the object to a JSON string representation for debugging. * - * This method is intended for debugging purposes only and provides + * This method is intended for debugging purposes only and provides * a JSON representation of the object's state. * * @return string The JSON representation of the object. diff --git a/src/Database/PicoPage.php b/src/Database/PicoPage.php index 5508c75..8f46623 100644 --- a/src/Database/PicoPage.php +++ b/src/Database/PicoPage.php @@ -7,7 +7,7 @@ * * This class provides functionality to manage page numbers and sizes, * and to calculate offsets for database queries. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -109,11 +109,11 @@ public function setPageSize($pageSize) $this->pageSize = max(1, intval($pageSize)); return $this; } - + /** * Calculates and retrieves the offset for database queries. * - * The offset is used to determine the starting point for fetching data + * The offset is used to determine the starting point for fetching data * in paginated queries, based on the current page number and page size. * * @return int The calculated offset for database queries. @@ -134,14 +134,14 @@ public function getLimit() { $limit = $this->getPageSize(); $offset = ($this->getPageNumber() - 1) * $limit; - + return new PicoLimit(max(0, $offset), $limit); } /** * Convert the object to a JSON string representation for debugging. * - * This method is intended for debugging purposes only and provides + * This method is intended for debugging purposes only and provides * a JSON representation of the object's state. * * @return string The JSON representation of the object. diff --git a/src/Database/PicoPageControl.php b/src/Database/PicoPageControl.php index edf6d2b..a26446e 100644 --- a/src/Database/PicoPageControl.php +++ b/src/Database/PicoPageControl.php @@ -7,12 +7,12 @@ /** * Class PicoPageControl * - * This class manages pagination controls for displaying pages of data. - * It generates navigation elements such as previous, next, first, and last - * page buttons, allowing users to navigate through pages seamlessly. - * The pagination links are generated based on the provided page data and + * This class manages pagination controls for displaying pages of data. + * It generates navigation elements such as previous, next, first, and last + * page buttons, allowing users to navigate through pages seamlessly. + * The pagination links are generated based on the provided page data and * can be customized with parameter names and paths. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -112,7 +112,7 @@ public function __construct($pageData, $parameterName = 'page', $path = null) $this->prev = ''; $this->next = ''; $this->first = ''; - $this->last = ''; + $this->last = ''; } /** @@ -218,7 +218,7 @@ public function getFormatPageNumber() * * This format is used to generate the HTML for individual page numbers in the pagination. * It includes a span with the `page-selector-number` class and a link (``) to the page. - * + * * Placeholders: * - `%s`: Additional CSS classes, e.g., `page-selected`. * - `%d`: Page number for the `data-page-number` attribute. @@ -235,7 +235,7 @@ public function getFormatPageNumber() * 3 * * ``` - * + * * @param string $formatPageNumber The new format for rendering page numbers. * @return self Returns the current instance for method chaining. */ @@ -263,7 +263,7 @@ public function getFormatStepOne() * * This format generates the HTML for step navigation buttons, such as "previous" or "next." * It includes a span with the `page-selector-step-one` class and a link (``) to the target page. - * + * * Placeholders: * - `%s`: Additional CSS classes (e.g., active state). * - `%d`: Page number for the `data-page-number` attribute. @@ -309,7 +309,7 @@ public function getFormatStartEnd() * * This format generates the HTML for step navigation buttons, such as "previous" or "next." * It includes a span with the `page-selector-step-one` class and a link (``) to the target page. - * + * * Placeholders: * - `%s`: Additional CSS classes (e.g., active state). * - `%d`: Page number for the `data-page-number` attribute. @@ -342,23 +342,23 @@ public function setFormatStartEnd($formatStartEnd) /** * Sets the button format templates for pagination controls. * - * This method allows you to set custom templates for page numbers, step buttons + * This method allows you to set custom templates for page numbers, step buttons * and start/end buttons. * * @param string $pageNumberFormat The format template for rendering page numbers. - * + * * **Example:** * ```html * %s * ``` * @param string $stepOneFormat The format template for rendering step buttons. - * + * * **Example:** * ```html * %s * ``` * @param string $startEndFormat The format template for rendering start and end buttons. - * + * * **Example:** * ```html * %s @@ -376,7 +376,7 @@ public function setButtonFormat($pageNumberFormat, $stepOneFormat, $startEndForm /** * Applies the pagination configuration to the current instance. * - * This method accepts a configuration object, typically sourced from a Yaml file, + * This method accepts a configuration object, typically sourced from a Yaml file, * and applies its settings to the pagination control. The object contains the following properties: * * @param SecretObject $paginationConfig The configuration object containing pagination settings. diff --git a/src/Database/PicoPageData.php b/src/Database/PicoPageData.php index f4ef083..818365f 100644 --- a/src/Database/PicoPageData.php +++ b/src/Database/PicoPageData.php @@ -20,7 +20,7 @@ * - Supports execution time tracking for performance monitoring. * - Provides easy access to pagination controls and metadata. * - Facilitates fetching and processing of data with subquery mapping. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -305,50 +305,66 @@ public function getResultAsArray() */ protected function magicObjectToArray($data) { - // Null or scalar -> return directly + // Null or scalar if (is_null($data) || is_scalar($data)) { return $data; } - // Array -> recursive + // Array if (is_array($data)) { - return array_map(function ($item) { - return $this->magicObjectToArray($item); - }, $data); + $out = array(); + foreach ($data as $k => $v) { + $out[$k] = $this->magicObjectToArray($v); + } + return $out; } // Object if (is_object($data)) { - // If there is a toArray method in MagicObject + + // Prefer toArray() if exists if (method_exists($data, 'toArray')) { return $this->magicObjectToArray($data->toArray()); } - // Get public properties + // Public properties first $vars = get_object_vars($data); - // If empty, try using Reflection (for protected/private properties) + // Fallback: reflection (protected/private) if (empty($vars)) { $reflect = new \ReflectionClass($data); + $vars = array(); foreach ($reflect->getProperties() as $prop) { - $prop->setAccessible(true); + // Skip non-public if we cannot safely access + if (!$prop->isPublic()) { + // setAccessible may not exist or may be restricted + if (!method_exists($prop, 'setAccessible')) { + continue; + } + + try { + $prop->setAccessible(true); + } catch (\Exception $e) { + continue; + } + } $vars[$prop->getName()] = $prop->getValue($data); } } - // Convert property values recursively $obj = new \stdClass(); foreach ($vars as $k => $v) { $obj->$k = $this->magicObjectToArray($v); } + return $obj; } - // Default fallback return $data; } + /** * Get the current page number in the pagination context. * @@ -382,7 +398,7 @@ public function getPageSize() /** * Convert the object to a JSON string representation for debugging. * - * This method is intended for debugging purposes only and provides + * This method is intended for debugging purposes only and provides * a JSON representation of the object's state. * * @return string The JSON representation of the object. @@ -402,7 +418,7 @@ public function __toString() "executionTime", "pagination" ); - + foreach ($exposedProps as $key) { if (property_exists($this, $key)) { $obj->{$key} = $this->{$key}; @@ -504,7 +520,7 @@ public function fetch() if ($this->stmt === null) { throw new FindOptionException("Statement is null. See MagicObject::FIND_OPTION_NO_FETCH_DATA option."); } - + $result = $this->stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT); return $result !== false ? $this->applySubqueryResult($result) : false; } diff --git a/src/Database/PicoPageable.php b/src/Database/PicoPageable.php index 3b0e43b..e2a754d 100644 --- a/src/Database/PicoPageable.php +++ b/src/Database/PicoPageable.php @@ -7,7 +7,7 @@ /** * Pageable - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -37,55 +37,55 @@ class PicoPageable /** * Constructor for the Pageable class. - * + * * This constructor allows initializing a Pageable object with pagination and sorting options. * It supports different formats for both page and sortable parameters. - * + * * Example 1: Using `PicoPage` and `PicoSortable` - * + * * ```php * * ``` - * + * * Example 2: Using `PicoLimit` and `PicoSortable` - * + * * ```php * * ``` - * + * * Example 3: Using an array for page and `PicoSortable` - * + * * ```php * * ``` - * + * * Example 4: Using an array for both page and sortable - * + * * ```php * * ``` - * + * * Example 5: Using `PicoPage` and an array for sortable - * + * * ```php * * ``` - * + * * @param PicoPage|PicoLimit|array|null $page The page or limit configuration. Can be: * - `PicoPage` instance (for page number and size), * - `PicoLimit` instance (for offset and limit), * - array with two elements representing page and size (e.g., `[1, 100]`). - * + * * @param PicoSortable|array|null $sortable The sorting configuration. Can be: * - `PicoSortable` instance (for sorting by multiple fields), * - array of field-direction pairs (e.g., `['userName', 'asc', 'email', 'desc']`). @@ -237,7 +237,7 @@ public function setOffsetLimit($offsetLimit) /** * Convert the object to a JSON string representation for debugging. * - * This method is intended for debugging purposes only and provides + * This method is intended for debugging purposes only and provides * a JSON representation of the object's state. * * @return string The JSON representation of the object. diff --git a/src/Database/PicoPredicate.php b/src/Database/PicoPredicate.php index da260cc..a6fa232 100644 --- a/src/Database/PicoPredicate.php +++ b/src/Database/PicoPredicate.php @@ -6,10 +6,10 @@ * Class PicoPredicate * * A predicate for building query conditions in database queries. - * This class allows you to define various query conditions + * This class allows you to define various query conditions * (e.g., equality, inequality, inclusion, pattern matching, etc.) * to be used when constructing database queries. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -42,7 +42,7 @@ public static function alwaysTrue() /** * Constructor. Initializes the predicate with a field and value. * - * If a field is provided, it sets the equality condition or + * If a field is provided, it sets the equality condition or * an IN condition based on the value type. * * @param string|null $field The name of the field. @@ -373,7 +373,7 @@ public static function generateLikeContains($value) * - `set(value)`: Checks if the property value equals the provided value. * - For example, calling `$obj->setFoo($value)` checks if the property `foo` * is equal to `$value` using the `equals` method. - * + * * If the method name does not start with "set" or if no value is provided, * the method returns null. * @@ -451,7 +451,7 @@ public static function functionAndValue($function, $value) /** * Convert the object to a JSON string representation for debugging. * - * This method is intended for debugging purposes only and provides + * This method is intended for debugging purposes only and provides * a JSON representation of the object's state. * * @return string The JSON representation of the object. diff --git a/src/Database/PicoSort.php b/src/Database/PicoSort.php index bcc6c61..a3b0869 100644 --- a/src/Database/PicoSort.php +++ b/src/Database/PicoSort.php @@ -9,9 +9,9 @@ * Class PicoSort * * A class for defining sorting criteria for database queries. - * This class allows you to specify the field to sort by and the + * This class allows you to specify the field to sort by and the * direction of sorting (ascending or descending). - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -95,12 +95,12 @@ public function setSortType($sortType) /** * Handles dynamic method calls for setting sorting criteria. * - * This magic method allows dynamic sorting by intercepting method calls - * prefixed with "sortBy". It extracts the sorting field from the method name + * This magic method allows dynamic sorting by intercepting method calls + * prefixed with "sortBy". It extracts the sorting field from the method name * and applies the specified sorting type. * * ### Supported Dynamic Method: - * - `sortBy(sortType)`: + * - `sortBy(sortType)`: * - Example: `$obj->sortByName('asc')` * - Sets the sorting field to `name` * - Sets the sorting type to `'asc'` @@ -108,7 +108,7 @@ public function setSortType($sortType) * ### Behavior: * - The method name must start with "sortBy". * - The first parameter must be provided, defining the sorting type. - * - If these conditions are met, the sorting field and type are set, and the + * - If these conditions are met, the sorting field and type are set, and the * current instance is returned for method chaining. * - If the method does not match the expected format, `null` is returned. * @@ -161,7 +161,7 @@ public static function fixSortType($type) /** * Convert the object to a JSON string representation for debugging. * - * This method is intended for debugging purposes only and provides + * This method is intended for debugging purposes only and provides * a JSON representation of the object's state. * * @return string The JSON representation of the object. diff --git a/src/Database/PicoSortable.php b/src/Database/PicoSortable.php index 33491de..9e4c51b 100644 --- a/src/Database/PicoSortable.php +++ b/src/Database/PicoSortable.php @@ -11,7 +11,7 @@ * * This class provides functionality to manage sorting criteria, * allowing the specification of fields to sort by and their sort types. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -27,7 +27,7 @@ class PicoSortable /** * Constructor to initialize sortable criteria based on provided arguments. - * + * * **Example:** * ```php * createSortable($sort[0], $sort[1]); $this->sortable[count($this->sortable)] = $sortable; } - + } else if (is_string($sort)) { // No mapping $this->sortable[count($this->sortable)] = $sort; diff --git a/src/Database/PicoSpecification.php b/src/Database/PicoSpecification.php index 343cb25..406efa9 100644 --- a/src/Database/PicoSpecification.php +++ b/src/Database/PicoSpecification.php @@ -390,7 +390,7 @@ private function getWhere($specifications) */ private function getColumnName($field, $parentField) { - return ($parentField === null) ? $field : $parentField . "." . $field; + return ($parentField === null) ? $field : $parentField . "." . $field; } /** @@ -613,7 +613,7 @@ private static function fixInput($filterValue, $filter) // Result is boolean $filterValue = self::fixInputBoolean($filterValue); } - + return $filterValue; } diff --git a/src/Database/PicoSpecificationFilter.php b/src/Database/PicoSpecificationFilter.php index 7c16ada..36890fb 100644 --- a/src/Database/PicoSpecificationFilter.php +++ b/src/Database/PicoSpecificationFilter.php @@ -5,9 +5,9 @@ /** * Class representing a specification filter. * - * This class defines filters for columns, specifying the data type + * This class defines filters for columns, specifying the data type * and providing methods to convert values based on the defined type. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -54,7 +54,7 @@ public function __construct($columnName, $dataType) /** * Convert the object to a JSON string representation for debugging. * - * This method is intended for debugging purposes only and provides + * This method is intended for debugging purposes only and provides * a JSON representation of the object's state. * * @return string The JSON representation of the object. @@ -93,7 +93,7 @@ public function valueOf($stringValue) /** * Checks if the data type represents an array. * - * This method determines whether the data type contains + * This method determines whether the data type contains * an array-like structure based on the presence of square brackets (`[]`). * * @return bool true if the data type represents an array, false otherwise. @@ -239,7 +239,7 @@ public function isFulltext() { return $this->dataType === self::DATA_TYPE_FULLTEXT; } - + /** * Checks if the data type is a text equality match (exact match). * diff --git a/src/Database/PicoSqlJson.php b/src/Database/PicoSqlJson.php index 2bd6f8a..0c95d49 100644 --- a/src/Database/PicoSqlJson.php +++ b/src/Database/PicoSqlJson.php @@ -7,13 +7,13 @@ /** * Class PicoSqlJson - * - * This class handles the encoding and validation of JSON data. - * It accepts an object, array, or a valid JSON string and ensures that it is properly - * encoded as a JSON string. If a string is provided, it checks if the string is a - * valid JSON format before encoding it. If the string is not valid JSON, an exception + * + * This class handles the encoding and validation of JSON data. + * It accepts an object, array, or a valid JSON string and ensures that it is properly + * encoded as a JSON string. If a string is provided, it checks if the string is a + * valid JSON format before encoding it. If the string is not valid JSON, an exception * will be thrown. - * + * * @package MagicObject\Database */ class PicoSqlJson @@ -27,13 +27,13 @@ class PicoSqlJson /** * Constructor for PicoSqlJson class - * + * * Accepts an array, object, or a valid JSON string, and encodes it to JSON. - * If a string is provided, it checks whether it's a valid JSON string. + * If a string is provided, it checks whether it's a valid JSON string. * If valid, it is encoded to JSON; otherwise, an exception is thrown. * * @param mixed $value The value to encode. Can be an array, object, or JSON string. - * + * * @throws InvalidArgumentException If the string provided is not valid JSON. */ public function __construct($value = null) { @@ -41,12 +41,12 @@ public function __construct($value = null) { // If $value is an object or array, encode it to JSON if ($value instanceof stdClass || is_array($value)) { $this->value = json_encode($value); - } + } // If $value is a string, check if it's a valid JSON string else if (is_string($value)) { // Try to decode the JSON string $decoded = json_decode($value); - + // Check if the decoding was successful (not null) and there were no JSON errors if (json_last_error() === JSON_ERROR_NONE) { // If it's valid JSON, set $this->value @@ -58,7 +58,7 @@ public function __construct($value = null) { } } } - + /** * Converts the object to a string. * diff --git a/src/Database/PicoSqlite.php b/src/Database/PicoSqlite.php index 558fc4c..f23d3fb 100644 --- a/src/Database/PicoSqlite.php +++ b/src/Database/PicoSqlite.php @@ -9,15 +9,15 @@ * Class PicoSqlite * * A simple wrapper for SQLite database operations using PDO. - * + * * This class provides an easy-to-use interface for interacting with an SQLite database. * It supports basic CRUD (Create, Read, Update, Delete) operations such as creating tables, * inserting records, selecting records with optional conditions, updating records, and deleting records. - * + * * The class uses PDO (PHP Data Objects) for database connectivity and supports executing SQL queries * through various methods. The connection to the SQLite database is made through the provided file path, * and additional callbacks can be provided for query execution and debugging. - * + * * This class extends the `PicoDatabase` class, which provides additional database interaction capabilities. * * @author Kamshory @@ -27,7 +27,7 @@ class PicoSqlite extends PicoDatabase { const LOGIC_AND = " and "; - + /** * Database file path * @@ -197,6 +197,6 @@ public function delete($tableName, $conditions) { return $stmt->execute(); } - + } diff --git a/src/Database/PicoTableInfo.php b/src/Database/PicoTableInfo.php index 9d11c62..0dd0d2a 100644 --- a/src/Database/PicoTableInfo.php +++ b/src/Database/PicoTableInfo.php @@ -8,10 +8,10 @@ /** * Class representing information about a database table. * - * This class contains details such as the table name, columns, - * primary keys, and other related metadata necessary for managing + * This class contains details such as the table name, columns, + * primary keys, and other related metadata necessary for managing * database interactions. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject @@ -73,7 +73,7 @@ class PicoTableInfo // NOSONAR * @var string */ protected $columnType; - + /** * Flag to disable cache when any entities join with this entity * @@ -358,7 +358,7 @@ public function setNotNullColumns($notNullColumns) * Get flag to disable cache when any entities join with this entity * * @return boolean Flag to disable cache when any entities join with this entity - */ + */ public function getNoCache() { return $this->noCache; @@ -370,7 +370,7 @@ public function getNoCache() * @param boolean $noCache Flag to disable cache when any entities join with this entity * * @return self Returns the current instance for method chaining. - */ + */ public function setNoCache($noCache) { $this->noCache = $noCache; @@ -382,7 +382,7 @@ public function setNoCache($noCache) * Get the package name or namespace. * * @return string The package name or namespace. - */ + */ public function getPackage() { return $this->package; @@ -394,7 +394,7 @@ public function getPackage() * @param string $package The package name or namespace. * * @return self Returns the current instance for method chaining. - */ + */ public function setPackage($package) { $this->package = $package; diff --git a/src/Database/PicoTableInfoExtended.php b/src/Database/PicoTableInfoExtended.php index b3831ee..7946c03 100644 --- a/src/Database/PicoTableInfoExtended.php +++ b/src/Database/PicoTableInfoExtended.php @@ -8,7 +8,7 @@ * This class extends the functionality of PicoTableInfo by providing methods * for managing unique columns, join columns, primary keys, auto-increment keys, * default values, and not-null columns. - * + * * @author Kamshory * @package MagicObject\Database * @link https://github.com/Planetbiru/MagicObject diff --git a/src/Generator/PicoDatabaseDump.php b/src/Generator/PicoDatabaseDump.php index 34ba36d..0a660c8 100644 --- a/src/Generator/PicoDatabaseDump.php +++ b/src/Generator/PicoDatabaseDump.php @@ -14,6 +14,7 @@ use MagicObject\Util\Database\PicoDatabaseUtilPostgreSql; use MagicObject\Util\Database\PicoDatabaseUtilSqlite; use MagicObject\Util\Database\PicoDatabaseUtilSqlServer; +use stdClass; /** * Database dump class for managing and generating SQL statements @@ -45,6 +46,222 @@ class PicoDatabaseDump // NOSONAR * @var array */ protected $columns = array(); + + /** + * Generates a SQL CREATE TABLE statement based on the provided entity schema. + * * This method detects the database type and utilizes the appropriate utility + * class to format columns, primary keys, and auto-increment constraints. + * It supports MySQL, MariaDB, PostgreSQL, SQLite, and SQL Server. + * + * @param array $entity The entity schema containing 'name' and 'columns' (an array of column definitions). + * @param string $databaseType The type of database (e.g., PicoDatabaseType::DATABASE_TYPE_MARIADB). + * @param bool $createIfNotExists Whether to add the "IF NOT EXISTS" clause to the CREATE statement. + * @param bool $dropIfExists Whether to prepend a commented-out "DROP TABLE IF EXISTS" statement. + * @param string $engine The storage engine to use (default is 'InnoDB', primarily for MySQL/MariaDB). + * @param string $charset The character set for the table (default is 'utf8mb4'). + * * @return string The generated SQL DDL statement or an empty string if the database type is unsupported. + */ + public function dumpStructureFromSchema($entity, $databaseType, $createIfNotExists = false, $dropIfExists = false, $engine = 'InnoDB', $charset = 'utf8mb4') + { + $tableName = $entity['name']; + + // 1. Initialize Tool based on Database Type + switch ($databaseType) { + case PicoDatabaseType::DATABASE_TYPE_MARIADB: + case PicoDatabaseType::DATABASE_TYPE_MYSQL: + $tool = new PicoDatabaseUtilMySql(); + break; + case PicoDatabaseType::DATABASE_TYPE_PGSQL: + case PicoDatabaseType::DATABASE_TYPE_POSTGRESQL: + $tool = new PicoDatabaseUtilPostgreSql(); + break; + case PicoDatabaseType::DATABASE_TYPE_SQLITE: + $tool = new PicoDatabaseUtilSqlite(); + break; + case PicoDatabaseType::DATABASE_TYPE_SQLSERVER: + $tool = new PicoDatabaseUtilSqlServer(); + break; + default: + return ""; + } + + $columns = array(); + $primaryKeys = array(); + $autoIncrementKeys = array(); + + // 2. Mapping Key Constraints + foreach($entity['columns'] as $col) { + if($col['primaryKey']) { + $primaryKeys[] = $col['name']; + } + if($col['autoIncrement']) { + $autoIncrementKeys[] = $col['name']; + } + } + + // 3. Generate Column Definitions + foreach($entity['columns'] as $col) { + if(!empty($col['length'])) + { + $col['type'] = $col['type']."(".$col['length'].")"; + } + else if(stripos($col['type'], 'enum') === 0 && !empty($col['values'])) + { + $col['type'] = $col['type']."(".$col['values'].")"; + } + $columns[] = $tool->createColumn($col, $autoIncrementKeys, $primaryKeys); + } + + // 4. Only append table-level PRIMARY KEY if it's a composite key (more than 1) + if(count($primaryKeys) > 1) + { + $columns[] = "\tPRIMARY KEY (" . implode(", ", $primaryKeys) . ")"; + } + + $query = array(); + + // 5. Add DROP TABLE with comment + if ($dropIfExists) { + $query[] = "-- DROP TABLE IF EXISTS $tableName;"; + $query[] = ""; + } + + // 6. Create Statement + $createStatement = "CREATE TABLE" . ($createIfNotExists ? " IF NOT EXISTS" : ""); + $query[] = "$createStatement $tableName ("; + $query[] = implode(",\r\n", $columns); + + // 7. Handle Engine & Charset only for MySQL/MariaDB + $tableOptions = ""; + if ($databaseType == PicoDatabaseType::DATABASE_TYPE_MARIADB || $databaseType == PicoDatabaseType::DATABASE_TYPE_MYSQL) { + $tableOptions = " ENGINE=$engine DEFAULT CHARSET=$charset"; + } + + $query[] = ")" . $tableOptions . ";"; + + return implode("\r\n", $query)."\r\n\r\n"; + } + + /** + * Dumps data from a schema entity into batched SQL INSERT statements. + * + * @param array $entity The entity containing table name, columns, and data. + * @param string $databaseType The database type (e.g., MySQL, PostgreSQL). + * @param int $batchSize Number of records per INSERT statement. + * @return string The generated SQL script. + */ + public function dumpDataFromSchema($entity, $databaseType, $batchSize = 100) + { + // Check if the target database is PostgreSQL + $isPgSql = $databaseType == PicoDatabaseType::DATABASE_TYPE_PGSQL || $databaseType == PicoDatabaseType::DATABASE_TYPE_POSTGRESQL; + $columnInfo = array(); + $tableName = $entity['name']; + + // 1. Prepare Column Information for type-casting + if (isset($entity['columns']) && is_array($entity['columns'])) { + foreach ($entity['columns'] as $column) { + $columnInfo[$column['name']] = $this->getColumnInfo($column); + } + } + + $validColumnNames = array_keys($columnInfo); + + $allSql = ""; + + // 2. Process Data with Batching + if (isset($entity['data']) && is_array($entity['data']) && !empty($entity['data'])) { + // Split the data into smaller chunks based on the batch size + $batches = array_chunk($entity['data'], $batchSize); + + foreach ($batches as $batch) { + $sqlInsert = array(); + + // 1. Filter setiap row agar hanya kolom valid + $filteredBatch = array(); + foreach ($batch as $data) { + $filteredBatch[] = array_intersect_key( + $data, + array_flip($validColumnNames) + ); + } + + if (empty($filteredBatch)) { + continue; + } + + // 2. Ambil column names dari hasil filter + $columnNames = implode(", ", array_keys($filteredBatch[0])); + $sqlInsert[] = "INSERT INTO $tableName ($columnNames) VALUES"; + + $rows = array(); + foreach ($filteredBatch as $data) { + $rows[] = "(" . implode(", ", $this->fixData($data, $columnInfo, $isPgSql)) . ")"; + } + + $allSql .= implode("\r\n", $sqlInsert) + . "\r\n" + . implode(",\r\n", $rows) + . ";\r\n\r\n"; + } + + } + + return $allSql; + } + + /** + * Formats raw data values based on column metadata and database requirements. + * + * @param array $data Associative array of column => value. + * @param array $columnInfo Metadata for each column. + * @param bool $isPgSql Whether the target database is PostgreSQL. + * @return array The formatted data array. + */ + public function fixData($data, $columnInfo, $isPgSql) + { + foreach ($data as $key => $value) { + if ($value === null) { + // Handle NULL values + $data[$key] = 'null'; + } else if (isset($columnInfo[$key]) && in_array($columnInfo[$key]->normalizedType, ['integer', 'float'])) { + // Keep numeric values as they are (no quotes) + $data[$key] = $value; + } else if (isset($columnInfo[$key]) && in_array($columnInfo[$key]->normalizedType, ['boolean', 'bool']) && $isPgSql) { + // Handle PostgreSQL boolean conversion + $data[$key] = $this->toBoolean($value) ? 'true' : 'false'; + } else { + // Treat as string: escape single quotes and wrap in quotes + $data[$key] = "'" . str_replace("'", "''", $value) . "'"; + } + } + return $data; + } + + /** + * Converts a value to a boolean representation. + * + * @param mixed $value The value to check. + * @return bool + */ + public function toBoolean($value) + { + return $value === 1 || $value === 'true' || $value === 'TRUE' || $value === '1' || $value === true; + } + + /** + * Normalizes column metadata into a standard object. + * + * @param array $column The column definition from schema. + * @return stdClass + */ + public function getColumnInfo($column) + { + $ret = new stdClass; + $ret->type = $column['type']; + $ret->length = isset($column['length']) ? $column['length'] : null; + $ret->normalizedType = $this->normalizeDbType($column['type'], $ret->length); + return $ret; + } /** * Dump the structure of a table for the specified entity. @@ -119,6 +336,90 @@ public function dumpStructureTable($tableInfo, $databaseType, $createIfNotExists } return $result; } + + /** + * Normalize a database-specific column type into a generic type. + * + * Supported DBMS: MySQL, MariaDB, PostgreSQL, SQLite, SQL Server + * + * Possible return values: + * - string + * - integer + * - float + * - boolean + * - date + * - time + * - datetime + * - binary + * - json + * - uuid + * - enum + * - geometry + * - unknown + * + * @param string $dbType The raw column type from the database (e.g., VARCHAR(255), INT, NUMERIC(10,2), TEXT, etc.) + * @return string One of the normalized type names listed above. + */ + public function normalizeDbType($dbType, $length = null) + { + $normalized = 'string'; // default fallback + $rawType = strtolower(trim($dbType)); + + // Special case: MySQL TINYINT(1) → boolean + if ($rawType === 'tinyint' && isset($length) && (string) $length === '1') { + return 'boolean'; // acceptable early return for explicit edge-case + } + + // Remove size & precision: varchar(255) → varchar + $type = preg_replace('/\(.+\)/', '', $rawType); + + $map = [ + 'integer' => [ + 'int', 'integer', 'smallint', 'mediumint', + 'bigint', 'serial', 'bigserial', 'tinyint' + ], + 'float' => [ + 'float', 'double', 'decimal', 'numeric', + 'real', 'money', 'smallmoney' + ], + 'boolean' => [ + 'boolean', 'bool', 'bit' + ], + 'string' => [ + 'char', 'varchar', 'text', 'tinytext', + 'mediumtext', 'longtext', 'nchar', + 'nvarchar', 'citext' + ], + 'uuid' => [ + 'uuid' + ], + 'json' => [ + 'json', 'jsonb' + ], + 'binary' => [ + 'blob', 'binary', 'varbinary', + 'image', 'bytea' + ], + 'date' => [ + 'date' + ], + 'time' => [ + 'time' + ], + 'datetime' => [ + 'datetime', 'timestamp', 'year' + ] + ]; + + foreach ($map as $result => $types) { + if (in_array($type, $types, true)) { + $normalized = $result; + break; + } + } + + return $normalized; + } /** * Get the table information for the specified entity. diff --git a/src/Util/Database/PicoDatabaseUtilBase.php b/src/Util/Database/PicoDatabaseUtilBase.php index 4323e04..92004c1 100644 --- a/src/Util/Database/PicoDatabaseUtilBase.php +++ b/src/Util/Database/PicoDatabaseUtilBase.php @@ -15,6 +15,26 @@ class PicoDatabaseUtilBase // NOSONAR const KEY_PRIMARY_KEY = "primary_key"; const KEY_NULLABLE = "nullable"; const KEY_AUTO_INCREMENT = "auto_increment"; + + /** + * Extracts column names from an array of primary key definitions. + * + * This helper method iterates through a list of primary key attribute arrays + * and retrieves only the 'name' identifier for each column. It is typically + * used to determine if a table has a composite primary key. + * + * @param array[] $primaryKeys An array of associative arrays, where each element + * must contain a 'name' key representing the column name. + * @return string[] An array containing only the names of the primary key columns. + */ + public function getPrimaryKeyNames($primaryKeys) + { + $pkCols = array(); + foreach ($primaryKeys as $col) { + $pkCols[] = $col['name']; + } + return $pkCols; + } /** * Gets the auto-increment keys from the provided table information. @@ -963,6 +983,22 @@ public static function isTypeFloat($type) return stripos($type, 'decimal') !== false || stripos($type, 'float') !== false || stripos($type, 'double') !== false || stripos($type, 'real') !== false; } + /** + * Determine whether a given column definition is marked as nullable. + * + * This method checks the column metadata array for the `KEY_NULLABLE` flag. + * It returns true if the flag is explicitly set to boolean `true` or + * the string value `"true"` (case-insensitive, with surrounding whitespace trimmed). + * + * @param array $column The column definition array containing metadata keys. + * + * @return bool Returns true if the column is nullable, false otherwise. + */ + public static function isNullable($column) + { + return isset($column[self::KEY_NULLABLE]) && ($column[self::KEY_NULLABLE] === true || strtolower(trim($column[self::KEY_NULLABLE])) == 'true'); + } + /** * Checks if the given value is an array. * diff --git a/src/Util/Database/PicoDatabaseUtilMySql.php b/src/Util/Database/PicoDatabaseUtilMySql.php index 1a88a70..a8e0d4c 100644 --- a/src/Util/Database/PicoDatabaseUtilMySql.php +++ b/src/Util/Database/PicoDatabaseUtilMySql.php @@ -78,6 +78,8 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false $createStatement .= " IF NOT EXISTS"; } $autoIncrementKeys = $this->getAutoIncrementKey($tableInfo); + $primaryKeys = $this->getPrimaryKeyNames($tableInfo->getPrimaryKeys()); + $multiplePk = count($primaryKeys) > 1; $query[] = "$createStatement `$tableName` ("; @@ -87,9 +89,13 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false { if(isset($cols[$columnName])) { - $columns[] = $this->createColumn($cols[$columnName], $autoIncrementKeys, $tableInfo->getPrimaryKeys()); + $columns[] = $this->createColumn($cols[$columnName], $autoIncrementKeys, $primaryKeys); } } + if($multiplePk) + { + $columns[] = "\tPRIMARY KEY(".implode(", ", $primaryKeys).")"; + } $query[] = implode(",\r\n", $columns); $query[] = ") ENGINE=$engine DEFAULT CHARSET=$charset;"; @@ -117,13 +123,9 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false */ public function createColumn($column, $autoIncrementKeys, $primaryKeys) { - $pkCols = array(); - foreach ($primaryKeys as $col) { - $pkCols[] = $col['name']; - } + $multiplePk = count($primaryKeys) > 1; $col = array(); - $col[] = "\t"; // Adding indentation for readability in SQL statements $columnName = $column[MagicObject::KEY_NAME]; $columnType = $column[MagicObject::KEY_TYPE]; @@ -131,7 +133,7 @@ public function createColumn($column, $autoIncrementKeys, $primaryKeys) $col[] = strtoupper($columnType); // Add the column type (e.g., INT, VARCHAR) // Check if the column is part of primary keys - if (in_array($columnName, $pkCols)) { + if (in_array($columnName, $primaryKeys) && !$multiplePk) { $col[] = 'PRIMARY KEY'; } @@ -141,7 +143,7 @@ public function createColumn($column, $autoIncrementKeys, $primaryKeys) } // Determine if the column allows NULL values - if (isset($column[self::KEY_NULLABLE]) && strtolower(trim($column[self::KEY_NULLABLE])) == 'true') { + if (parent::isNullable($column)) { $col[] = "NULL"; } else { $col[] = "NOT NULL"; @@ -154,7 +156,7 @@ public function createColumn($column, $autoIncrementKeys, $primaryKeys) $col[] = "DEFAULT $defaultValue"; } - return implode(" ", $col); + return "\t".implode(" ", $col); } diff --git a/src/Util/Database/PicoDatabaseUtilPostgreSql.php b/src/Util/Database/PicoDatabaseUtilPostgreSql.php index 5b2d084..03b35f0 100644 --- a/src/Util/Database/PicoDatabaseUtilPostgreSql.php +++ b/src/Util/Database/PicoDatabaseUtilPostgreSql.php @@ -124,6 +124,8 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false } $autoIncrementKeys = $this->getAutoIncrementKey($tableInfo); + $primaryKeys = $this->getPrimaryKeyNames($tableInfo->getPrimaryKeys()); + $multiplePk = count($primaryKeys) > 1; $query[] = "$createStatement \"$tableName\" ("; @@ -134,56 +136,19 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false { if(isset($cols[$columnName])) { - $columns[] = $this->createColumnPostgre($cols[$columnName], $autoIncrementKeys, $tableInfo->getPrimaryKeys()); + $columns[] = $this->createColumn($cols[$columnName], $autoIncrementKeys, $primaryKeys); } } - + if($multiplePk) + { + $columns[] = "\tPRIMARY KEY(".implode(", ", $primaryKeys).")"; + } $query[] = implode(",\r\n", $columns); $query[] = ");"; return implode("\r\n", $query); } - /** - * Creates a column definition for a SQL statement. - * - * This method constructs a SQL column definition based on the provided column details, - * including the column name, data type, nullability, and default value. The resulting - * definition is formatted for use in a CREATE TABLE statement. - * - * @param array $column An associative array containing details about the column: - * - string name: The name of the column. - * - string type: The data type of the column (e.g., VARCHAR, INT). - * - bool|string nullable: Indicates if the column allows NULL values (true or 'true' for NULL; otherwise, NOT NULL). - * - mixed defaultValue: The default value for the column (optional). - * - * @return string The SQL column definition formatted as a string, suitable for inclusion in a CREATE TABLE statement. - */ - public function createColumn($column, $autoIncrementKeys = null) - { - $col = array(); - $col[] = "\t"; - $col[] = "\"" . $column[MagicObject::KEY_NAME] . "\""; - - $type = $this->fixAutoIncrementType($column, $column[MagicObject::KEY_TYPE], $autoIncrementKeys); - - $col[] = strtoupper($type); - - if (isset($column[self::KEY_NULLABLE]) && strtolower(trim($column[self::KEY_NULLABLE])) == 'true') { - $col[] = "NULL"; - } else { - $col[] = "NOT NULL"; - } - - if (isset($column[MagicObject::KEY_DEFAULT_VALUE])) { - $defaultValue = $column[MagicObject::KEY_DEFAULT_VALUE]; - $defaultValue = $this->fixDefaultValue($defaultValue, $column[MagicObject::KEY_TYPE]); - $col[] = "DEFAULT $defaultValue"; - } - - return implode(" ", $col); - } - /** * Adjusts the SQL data type for auto-increment columns. * @@ -248,12 +213,10 @@ private function fixAutoIncrementType($column, $type, $autoIncrementKeys) * @return string The SQL column definition formatted as a string, suitable for inclusion * in a CREATE TABLE statement. */ - public function createColumnPostgre($column, $autoIncrementKeys, $primaryKeys) + public function createColumn($column, $autoIncrementKeys, $primaryKeys) { - $pkCols = array(); - foreach ($primaryKeys as $col) { - $pkCols[] = $col['name']; // Collect primary key column names. - } + + $multiplePk = count($primaryKeys) > 1; $col = array(); $columnName = $column[MagicObject::KEY_NAME]; // Get the column name. @@ -270,17 +233,16 @@ public function createColumnPostgre($column, $autoIncrementKeys, $primaryKeys) $columnType = $this->getColumnType($column[MagicObject::KEY_TYPE]); // Use the specified type if not auto-incrementing. } - $col[] = "\t"; // Add tab indentation for readability. $col[] = $columnName; // Add the column name. $col[] = strtoupper($columnType); // Add the column type (SERIAL or BIGSERIAL, or custom type). // Add PRIMARY KEY constraint if the column is part of the primary keys. - if (in_array($columnName, $pkCols)) { + if (in_array($columnName, $primaryKeys) && !$multiplePk) { $col[] = 'PRIMARY KEY'; } // Determine nullability and add it to the definition. - if (isset($column[self::KEY_NULLABLE]) && strtolower(trim($column[self::KEY_NULLABLE])) == 'true') { + if (parent::isNullable($column)) { $col[] = "NULL"; // Allow NULL values. } else { $col[] = "NOT NULL"; // Disallow NULL values. @@ -294,7 +256,7 @@ public function createColumnPostgre($column, $autoIncrementKeys, $primaryKeys) } // Join all parts into a single string to form the complete column definition. - return implode(" ", $col); + return "\t".implode(" ", $col); } /** diff --git a/src/Util/Database/PicoDatabaseUtilSqlServer.php b/src/Util/Database/PicoDatabaseUtilSqlServer.php index f760360..ed9f927 100644 --- a/src/Util/Database/PicoDatabaseUtilSqlServer.php +++ b/src/Util/Database/PicoDatabaseUtilSqlServer.php @@ -113,6 +113,8 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false $createStatement .= " IF NOT EXISTS"; } $autoIncrementKeys = $this->getAutoIncrementKey($tableInfo); + $primaryKeys = $this->getPrimaryKeyNames($tableInfo->getPrimaryKeys()); + $multiplePk = count($primaryKeys) > 1; $query[] = "$createStatement [$tableName] ("; @@ -122,9 +124,13 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false { if(isset($cols[$columnName])) { - $columns[] = $this->createColumn($cols[$columnName], $autoIncrementKeys, $tableInfo->getPrimaryKeys()); + $columns[] = $this->createColumn($cols[$columnName], $autoIncrementKeys, $primaryKeys); } } + if($multiplePk) + { + $columns[] = "\tPRIMARY KEY(".implode(", ", $primaryKeys).")"; + } $query[] = implode(",\r\n", $columns); $query[] = ");"; @@ -152,13 +158,9 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false */ public function createColumn($column, $autoIncrementKeys, $primaryKeys) { - $pkCols = array(); - foreach ($primaryKeys as $col) { - $pkCols[] = $col['name']; - } + $multiplePk = count($primaryKeys) > 1; $col = array(); - $col[] = "\t"; // Adding indentation for readability in SQL statements $columnName = $column[MagicObject::KEY_NAME]; $columnType = $column[MagicObject::KEY_TYPE]; @@ -171,12 +173,12 @@ public function createColumn($column, $autoIncrementKeys, $primaryKeys) } // Check if the column is part of primary keys - if (in_array($columnName, $pkCols)) { + if (in_array($columnName, $primaryKeys) && !$multiplePk) { $col[] = 'PRIMARY KEY'; } // Determine if the column allows NULL values - if (isset($column[self::KEY_NULLABLE]) && strtolower(trim($column[self::KEY_NULLABLE])) == 'true') { + if (parent::isNullable($column)) { $col[] = "NULL"; } else { $col[] = "NOT NULL"; @@ -189,7 +191,7 @@ public function createColumn($column, $autoIncrementKeys, $primaryKeys) $col[] = "DEFAULT $defaultValue"; } - return implode(" ", $col); + return "\t".implode(" ", $col); } /** diff --git a/src/Util/Database/PicoDatabaseUtilSqlite.php b/src/Util/Database/PicoDatabaseUtilSqlite.php index 746fbd9..bb925e6 100644 --- a/src/Util/Database/PicoDatabaseUtilSqlite.php +++ b/src/Util/Database/PicoDatabaseUtilSqlite.php @@ -87,7 +87,7 @@ public function showCreateTable($entity, $createIfNotExists = false, $dropIfExis $columnName = $column[MagicObject::KEY_NAME]; $columnType = $column[MagicObject::KEY_TYPE]; $length = isset($column[MagicObject::KEY_LENGTH]) ? $column[MagicObject::KEY_LENGTH] : null; - $nullable = (isset($column[self::KEY_NULLABLE]) && $column[self::KEY_NULLABLE] === 'true') ? ' NULL' : ' NOT NULL'; + $nullable = parent::isNullable($column) ? ' NULL' : ' NOT NULL'; $defaultValue = isset($column[MagicObject::KEY_DEFAULT_VALUE]) ? " DEFAULT ".$column[MagicObject::KEY_DEFAULT_VALUE] : ''; // Convert column type for SQL @@ -257,7 +257,7 @@ public function getColumnList($database, $tableName) * @param string|null $charset The character set to use for the table (optional, default is null). * @return string The SQL statement to create the table, including column definitions and primary keys. */ - public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false, $dropIfExists = false, $engine = 'InnoDB', $charset = 'utf8mb4') + public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false, $dropIfExists = false, $engine = 'InnoDB', $charset = 'utf8mb4') // NOSONAR { $query = array(); $columns = array(); @@ -288,22 +288,31 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false } $autoIncrementKeys = $this->getAutoIncrementKey($tableInfo); + $primaryKeys = array(); foreach($tableInfo->getColumns() as $k=>$column) { if(self::isArray($autoIncrementKeys) && in_array($column[MagicObject::KEY_NAME], $autoIncrementKeys)) { $cols[$k]['auto_increment'] = true; } + if(isset($column[self::KEY_PRIMARY_KEY]) && $column[self::KEY_PRIMARY_KEY]) + { + $primaryKeys[] = $column['name']; + } } + $multiplePk = count($primaryKeys) > 1; foreach($tableInfo->getSortedColumnName() as $columnName) { if(isset($cols[$columnName])) { - $columns[] = $this->createColumn($cols[$columnName]); + $columns[] = $this->createColumn($cols[$columnName], $autoIncrementKeys, $primaryKeys); } } - + if($multiplePk) + { + $columns[] = "\tPRIMARY KEY(".implode(", ", $primaryKeys).")"; + } $query[] = implode(",\r\n", $columns); $query[] = "); "; @@ -457,41 +466,52 @@ public function sqliteToMysqlType($type) // NOSONAR * Creates a column definition for a SQL statement (SQLite). * * This method constructs a SQL column definition based on the provided column details, - * including the column name, data type, nullability, default value, and primary key constraints. - * The resulting definition is formatted for use in a CREATE TABLE statement, suitable for SQLite. - * - * If the column is specified as a primary key with auto-increment, the column type is set to INTEGER, - * and the PRIMARY KEY constraint is added with auto-increment behavior (SQLite uses INTEGER PRIMARY KEY AUTOINCREMENT). - * - * @param array $column An associative array containing details about the column: - * - string 'name': The name of the column. - * - string 'type': The data type of the column (e.g., VARCHAR, INT). - * - bool|string 'nullable': Indicates if the column allows NULL values - * ('true' or true for NULL; otherwise, NOT NULL). - * - mixed 'defaultValue': The default value for the column (optional). - * - bool 'primary_key': Whether the column is a primary key (optional). - * - bool 'auto_increment': Whether the column is auto-incrementing (optional). - * - * @return string The SQL column definition formatted as a string, suitable for inclusion - * in a CREATE TABLE statement. + * including the name, data type, nullability, default value, and primary key constraints. + * The resulting string is formatted specifically for SQLite CREATE TABLE statements. + * * Special SQLite handling: + * - If the column is a primary key with auto-increment, the type is forced to INTEGER + * to trigger SQLite's internal rowid behavior. + * - If the table uses composite primary keys (multiple columns), the PRIMARY KEY + * constraint is omitted from the individual column definition and should be + * defined at the table level instead. + * + * @param array $column An associative array containing column details: + * - string 'name': The column identifier. + * - string 'type': The source data type (e.g., from MySQL). + * - bool|string 'nullable': 'true' or true for NULL; otherwise NOT NULL. + * - mixed 'defaultValue': The default value (optional). + * - bool 'primary_key': Primary key flag. + * - bool 'auto_increment': Auto-increment flag. + * @param array $autoIncrementKeys List of keys designated as auto-incrementing. + * @param array $primaryKeys List of all primary key columns to determine if a + * composite key (multiple PKs) exists. + * * @return string The formatted SQL column definition string. */ - public function createColumn($column) + public function createColumn($column, $autoIncrementKeys, $primaryKeys) { + + $multiplePk = count($primaryKeys) > 1; + $columnType = $this->mysqlToSqliteType($column[MagicObject::KEY_TYPE]); // Convert MySQL type to SQLite type $col = array(); - $col[] = "\t"; // Indentation for readability $col[] = "" . $column[MagicObject::KEY_NAME] . ""; // Column name // Handle primary key and auto-increment columns if (isset($column[self::KEY_PRIMARY_KEY]) && isset($column[self::KEY_AUTO_INCREMENT]) && $column[self::KEY_PRIMARY_KEY] && $column[self::KEY_AUTO_INCREMENT]) { $columnType = 'INTEGER'; // Use INTEGER for auto-incrementing primary keys in SQLite $col[] = $columnType; - $col[] = 'PRIMARY KEY'; + if(!$multiplePk) + { + $col[] = 'PRIMARY KEY'; + } } // Handle primary key only else if (isset($column[self::KEY_PRIMARY_KEY]) && $column[self::KEY_PRIMARY_KEY]) { $col[] = $columnType; - $col[] = 'PRIMARY KEY'; + if(!$multiplePk) + { + $col[] = 'PRIMARY KEY'; + } } // Handle regular column (non-primary key) else { @@ -499,7 +519,7 @@ public function createColumn($column) } // Handle nullability - if (isset($column[self::KEY_NULLABLE]) && strtolower(trim($column[self::KEY_NULLABLE])) == 'true') { + if (parent::isNullable($column)) { $col[] = "NULL"; // Allow NULL values } else { $col[] = "NOT NULL"; // Disallow NULL values @@ -513,7 +533,7 @@ public function createColumn($column) } // Join all parts into a single string for the final SQL column definition - return implode(" ", $col); + return "\t".implode(" ", $col); } /**