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);
}
/**