diff --git a/.gitignore b/.gitignore index 8454fa30..931f259b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor/ tests/import.yml tests/db.sql +tests/db.sqlite diff --git a/manual/includes/_database.md b/manual/includes/_database.md new file mode 100644 index 00000000..8f25933e --- /dev/null +++ b/manual/includes/_database.md @@ -0,0 +1,296 @@ +## Database + + +### Overview + +`PicoDatabase` is a PHP class designed for simplified database interactions using PDO (PHP Data Objects). It provides methods to connect to a database, execute SQL commands, manage transactions, and fetch results in various formats. This manual outlines how to use the class, its features, and provides examples for reference. + +### Features + +- **Connection Management**: Establish and manage database connections. +- **SQL Execution**: Execute various SQL commands such as INSERT, UPDATE, DELETE, and SELECT. +- **Transaction Handling**: Support for committing and rolling back transactions. +- **Result Fetching**: Fetch results in different formats (array, object, etc.). +- **Callbacks**: Support for custom callback functions for query execution and debugging. +- **Unique ID Generation**: Generate unique identifiers for database records. + +### Installation + +To use the `PicoDatabase` class, ensure you have PHP with PDO support. Include the class file in your project, and you can instantiate it with your database credentials. + +```php +use MagicObject\Database\PicoDatabase; + +// Example credentials setup +$credentials = new SecretObject(); +$db = new PicoDatabase($credentials); +``` + +**Credentials** + +To create database credentials, please see the `SecretObject` section. + +```php +setHost('localhost'); + * $credentials->setUsername('user'); + * $credentials->setPassword('password'); + * ``` + * + * 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 + */ +class PicoDatabaseCredentials extends SecretObject +{ + /** + * Database driver (e.g., 'mysql', 'pgsql'). + * + * @var string + */ + protected $driver = 'mysql'; + + /** + * Database server host. + * + * @EncryptIn + * @DecryptOut + * @var string + */ + protected $host = 'localhost'; + + /** + * Database server port. + * + * @var int + */ + protected $port = 3306; + + /** + * Database username. + * + * @EncryptIn + * @DecryptOut + * @var string + */ + protected $username = ""; + + /** + * Database user password. + * + * @EncryptIn + * @DecryptOut + * @var string + */ + protected $password = ""; + + /** + * Database name. + * + * @EncryptIn + * @DecryptOut + * @var string + */ + protected $databaseName = ""; + + /** + * Database schema (default: 'public'). + * + * @EncryptIn + * @DecryptOut + * @var string + */ + protected $databaseSchema = "public"; + + /** + * Application time zone. + * + * @var string + */ + protected $timeZone = "Asia/Jakarta"; +} +``` + +### Class Methods + +#### Constructor + +```php +public function __construct($databaseCredentials, $callbackExecuteQuery = null, $callbackDebugQuery = null) +``` + + +**Parameters:** + +- `SecretObject $databaseCredentials`: Database credentials object. +- `callable|null $callbackExecuteQuery`: Optional callback for executing modifying queries. + - If the callback has **3 parameters**, it will be: + - `$sqlQuery`: The SQL query being executed. + - `$params`: The parameters used in the SQL query. + - `$type`: The type of query (e.g., `PicoDatabase::QUERY_INSERT`). + - If the callback has **2 parameters**, it will be: + - `$sqlQuery`: The SQL query being executed. + - `$type`: The type of query. +- `callable|null $callbackDebugQuery`: Optional callback for debugging queries. + - If the callback has **2 parameters**, it will be: + - `$sqlQuery`: The SQL query being debugged. + - `$params`: The parameters used in the SQL query. + - If the callback has **1 parameter**, it will be: + - `$sqlQuery`: The SQL query being debugged. + +#### Connecting to the Database + +```php +public function connect($withDatabase = true): bool +``` + + +**Parameters**: + +- `bool $withDatabase`: Whether to select the database upon connection. + +**Returns**: `true` if connection is successful, `false` otherwise. + +#### Disconnecting from the Database + +```php +public function disconnect(): self +``` + +**Returns**: Current instance for method chaining. + +#### Query Execution + +```php +public function query($sql, $params = null) +``` + + +**Parameters**: +- `string $sql`: SQL command to be executed. +- `array|null $params`: Optional parameters for the SQL query. +**Returns**: PDOStatement object or `false` on failure. + +##### Fetch a Single Result + +```php +public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null) +``` + +**Parameters**: +- `string $sql`: SQL command. +- `int $tentativeType`: Fetch mode (default is `PDO::FETCH_ASSOC`). +- `mixed $defaultValue`: Default value if no results found. +- `array|null $params`: Optional parameters. +**Returns**: Fetched result or default value. + +#### Fetch All Results + +```php +public function fetchAll($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null) +``` + +Similar to fetch, but returns all matching results as an array. + +### Transaction Management + +#### Commit Transaction + +```php +public function commit(): bool +``` + +**Returns:** true if successful. + +#### Rollback Transaction + +```php +public function rollback(): bool +``` + +**Returns:** true if successful. + +#### Unique ID Generation + +```php +public function generateNewId(): string +``` + +**Returns:** A unique 20-byte ID. + +#### Last Inserted ID + +```php +public function lastInsertId($name = null): string|false +``` + +**Parameters:** + +- string|null $name: Sequence name (for PostgreSQL). + +**Returns:** The last inserted ID or false on error. + +### Connection Status + +#### Check Connection + +```php +public function isConnected(): bool +``` + +### Example Usage + +#### Connecting and Fetching Data + +```php +// Instantiate PicoDatabase +$db = new PicoDatabase($credentials); + +// Connect to the database +if ($db->connect()) { + // Fetch a user by ID + $user = $db->fetch("SELECT * FROM users WHERE id = ?", [1]); + print_r($user); + + // Disconnect + $db->disconnect(); +} +``` + +#### Executing a Transaction + +```php +$db->connect(); +$db->setAudoCommit(false); // Disable autocommit + +try { + $db->executeInsert("INSERT INTO users (name) VALUES (?)", ['John Doe']); + $db->commit(); // Commit the transaction +} catch (Exception $e) { + $db->rollback(); // Rollback on error +} +``` + +### Conclusion + +`PicoDatabase` is a robust class for managing database operations in PHP applications. By following the examples and method descriptions provided in this manual, you can effectively utilize its features for your database interactions. For further assistance, refer to the source code and documentation available at [MagicObject GitHub](https://github.com/Planetbiru/MagicObject). \ No newline at end of file diff --git a/manual/includes/_sqlite.md b/manual/includes/_sqlite.md new file mode 100644 index 00000000..badf8518 --- /dev/null +++ b/manual/includes/_sqlite.md @@ -0,0 +1,435 @@ +## PicoSqlite + +### Overview + +`PicoSqlite` is a PHP class designed for simplified interactions with SQLite databases using PDO (PHP Data Objects). This class extends `PicoDatabase` and provides methods for connecting to the database, creating tables, and performing basic CRUD (Create, Read, Update, Delete) operations. + +Here are some advantages of using SQLite: + +1. **Lightweight**: SQLite is a serverless, self-contained database engine that requires minimal setup and uses a single file to store the entire database, making it easy to manage and deploy. + +2. **Easy to Use**: Its simple API allows for straightforward integration with PHP, enabling quick database operations without the overhead of complex configurations. + +3. **No Server Required**: Unlike other database systems, SQLite does not require a separate server process, which simplifies the development process and reduces resource usage. + +4. **Cross-Platform**: SQLite databases are cross-platform and can be used on various operating systems without compatibility issues. + +5. **Fast Performance**: For smaller databases and applications, SQLite often outperforms more complex database systems, thanks to its lightweight architecture. + +6. **ACID Compliance**: SQLite provides full ACID (Atomicity, Consistency, Isolation, Durability) compliance, ensuring reliable transactions and data integrity. + +7. **Rich Feature Set**: Despite being lightweight, SQLite supports many advanced features like transactions, triggers, views, and complex queries. + +8. **No Configuration Required**: SQLite is easy to set up and requires no configuration, allowing developers to focus on building applications rather than managing the database server. + +9. **Great for Prototyping**: Its simplicity makes it ideal for prototyping applications before moving to a more complex database system. + +10. **Good for Read-Heavy Workloads**: SQLite performs well in read-heavy scenarios, making it suitable for applications where data is frequently read but rarely modified. + + +These features make SQLite a popular choice for many PHP applications, especially for smaller projects or for applications that need a lightweight database solution. + +SQLite has a slightly different method for determining whether a SELECT query returns matching rows. While other databases often utilize the rowCount() method to get this information, SQLite does not support this functionality in the same way. To address this limitation, MagicObject has implemented a solution that seamlessly handles row checking for users. With MagicObject, developers can interact with SQLite without needing to worry about the intricacies of row counting. This allows for a more intuitive and efficient experience when working with SQLite, enabling users to focus on their application logic rather than the underlying database mechanics. + +### Requirements + +- PHP 7.0 or higher +- PDO extension enabled + +### Installation + +To use the `PicoSqlite` class, include it in your PHP project. Ensure that your project structure allows for proper namespace loading. + +```php +use MagicObject\Database\PicoSqlite; + +// Example usage: +$db = new PicoSqlite('path/to/database.sqlite'); +``` + +### Class Methods + +#### Constructor + +```php +public function __construct($databaseFilePath) +``` + +**Parameters:** + + string $databaseFilePath: The path to the SQLite database file. + +**Throws:** PDOException if the connection fails. + +**Usage Example:** + +```php +$sqlite = new PicoSqlite('path/to/database.sqlite'); +``` + +#### Connecting to the Database + +```php +public function connect($withDatabase = true) +``` + +**Parameters:** +- bool $withDatabase: Optional. Default is true. Indicates whether to select the database when connecting. + +**Returns:** `bool` - True if the connection is successful, false otherwise. + +**Usage Example:** + +```php +if ($sqlite->connect()) { + echo "Connected to database successfully."; +} else { + echo "Failed to connect."; +} +``` + +#### Check Table + +```php +public function tableExists($tableName) : bool +``` + +**Parameters:** + +- string $tableName: The name of the table to check. + +**Returns:** `bool` - True if the table exists, false otherwise. + +**Usage Example:** + +```php +if ($sqlite->tableExists('users')) { + echo "Table exists."; +} else { + echo "Table does not exist."; +} +``` + +#### Create Table + +```php +public function createTable($tableName, $columns) : int|false +``` + +**Parameters:** + +- string $tableName: The name of the table to create. +- string[] $columns: An array of columns in the format 'column_name TYPE'. + +**Returns:** `int|false` - Number of rows affected or false on failure. + +**Usage Example:** + +```php +$columns = ['id INTEGER PRIMARY KEY', 'name TEXT', 'email TEXT']; +$sqlite->createTable('users', $columns); +``` + +#### Insert + +```php +public function insert($tableName, $data) : array +``` + +**Parameters:** + +- string $tableName: The name of the table to insert into. +- array $data: An associative array of column names and values to insert. + +**Returns:** `bool` - True on success, false on failure. + +**Usage Example:** + +```php +$data = ['name' => 'John Doe', 'email' => 'john@example.com']; +$sqlite->insert('users', $data); +``` + +```php +public function update($tableName, $data, $conditions) : bool +``` + +**Parameters:** + +- string $tableName: The name of the table to update. + array $data: An associative array of column names and new values. +- array $conditions: An associative array of conditions for the WHERE clause. + +**Returns:** `bool` - True on success, false on failure. + +**Usage Example:** + +```php +$data = ['name' => 'John Smith']; +$conditions = ['id' => 1]; +$sqlite->update('users', $data, $conditions); +``` + +#### Delete + +```php +public function delete($tableName, $conditions) : bool +``` + +**Parameters:** + +- string $tableName: The name of the table to delete from. +- array $conditions: An associative array of conditions for the WHERE clause. + +**Returns:** `bool` - True on success, false on failure. + +**Usage Example:** + +```php +$conditions = ['id' => 1]; +$sqlite->delete('users', $conditions); +``` + +### Entity with PicoSqlite + +```php +connect(); + + $album = new Album(null, $database); + + // create table if not exists + $util = new PicoDatabaseUtilSqlite(); + $tableStructure = $util->showCreateTable($album, true); + $database->query($tableStructure); + + $album->setAlbumId("1235"); + $album->setName("Meraih Mimpi 2 "); + $album->setTitle("Meraih Mimpi 2"); + $album->setDescription("Album pertama dengan judul Meraih Mimpi 2"); + $album->setProducerId("5678"); + $album->setReleaseDate("2024-09-09"); + $album->setNumberOfSong(10); + $album->duration(185*60); + $album->setSortOrder(1); + $album->setIpCreate("::1"); + $album->setIpEdit("::1"); + $album->setTimeCreate(date("Y-m-d H:i:s")); + $album->setTimeEdit(date("Y-m-d H:i:s")); + $album->setAdminCreate("1"); + $album->setAdminEdit("1"); + $album->setIpCreate("::1"); + $album->setActive(true); + $album->setAsDraft(false); + echo $album."\r\n--------\r\n"; + $album->save(); + + $album2 = new Album(null, $database); + + $res = $album2->findAll(); + foreach($res->getResult() as $row) + { + echo $row."\r\n"; + } +} +catch(Exception $e) +{ + echo $e->getMessage(); +} +``` + +### Error Handling + +If an operation fails, `PicoSqlite` may throw exceptions or return false. It is recommended to implement error handling using try-catch blocks to catch `PDOException` for connection-related issues. + +### Conclusion + +`PicoSqlite` provides an efficient way to interact with SQLite databases. Its straightforward API allows developers to perform common database operations with minimal code. For more advanced database operations, consider extending the class or using additional PDO features. \ No newline at end of file diff --git a/manual/index.html b/manual/index.html index 742f13a8..8bd9f2c3 100644 --- a/manual/index.html +++ b/manual/index.html @@ -1992,6 +1992,238 @@

Conclusion

+

Database

+

Overview

+

PicoDatabase is a PHP class designed for simplified database interactions using PDO (PHP Data Objects). It provides methods to connect to a database, execute SQL commands, manage transactions, and fetch results in various formats. This manual outlines how to use the class, its features, and provides examples for reference.

+

Features

+ +

Installation

+

To use the PicoDatabase class, ensure you have PHP with PDO support. Include the class file in your project, and you can instantiate it with your database credentials.

+
use MagicObject\Database\PicoDatabase;
+
+// Example credentials setup
+$credentials = new SecretObject();
+$db = new PicoDatabase($credentials);
+

Credentials

+

To create database credentials, please see the SecretObject section.

+
<?php
+
+namespace MagicObject\Database;
+
+use MagicObject\SecretObject;
+
+/**
+ * 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
+ * $credentials = new PicoDatabaseCredentials();
+ * $credentials->setHost('localhost');
+ * $credentials->setUsername('user');
+ * $credentials->setPassword('password');
+ * ```
+ * 
+ * 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
+ */
+class PicoDatabaseCredentials extends SecretObject
+{
+    /**
+     * Database driver (e.g., 'mysql', 'pgsql').
+     *
+     * @var string
+     */
+    protected $driver = 'mysql';
+
+    /**
+     * Database server host.
+     *
+     * @EncryptIn
+     * @DecryptOut
+     * @var string
+     */
+    protected $host = 'localhost';
+
+    /**
+     * Database server port.
+     *
+     * @var int
+     */
+    protected $port = 3306;
+
+    /**
+     * Database username.
+     *
+     * @EncryptIn
+     * @DecryptOut
+     * @var string
+     */
+    protected $username = "";
+
+    /**
+     * Database user password.
+     *
+     * @EncryptIn
+     * @DecryptOut
+     * @var string
+     */
+    protected $password = "";
+
+    /**
+     * Database name.
+     *
+     * @EncryptIn
+     * @DecryptOut
+     * @var string
+     */
+    protected $databaseName = "";
+
+    /**
+     * Database schema (default: 'public').
+     *
+     * @EncryptIn
+     * @DecryptOut
+     * @var string
+     */
+    protected $databaseSchema = "public"; 
+
+    /**
+     * Application time zone.
+     *
+     * @var string
+     */
+    protected $timeZone = "Asia/Jakarta";
+}
+

Class Methods

+

Constructor

+
public function __construct($databaseCredentials, $callbackExecuteQuery = null, $callbackDebugQuery = null)
+

Parameters:

+ +

Connecting to the Database

+
public function connect($withDatabase = true): bool
+

Parameters:

+ +

Returns: true if connection is successful, false otherwise.

+

Disconnecting from the Database

+
public function disconnect(): self
+

Returns: Current instance for method chaining.

+

Query Execution

+
public function query($sql, $params = null)
+

Parameters:

+ +
Fetch a Single Result
+
public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null)
+

Parameters:

+ +

Fetch All Results

+
public function fetchAll($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null)
+

Similar to fetch, but returns all matching results as an array.

+

Transaction Management

+

Commit Transaction

+
public function commit(): bool
+

Returns: true if successful.

+

Rollback Transaction

+
public function rollback(): bool
+

Returns: true if successful.

+

Unique ID Generation

+
public function generateNewId(): string
+

Returns: A unique 20-byte ID.

+

Last Inserted ID

+
public function lastInsertId($name = null): string|false
+

Parameters:

+ +

Returns: The last inserted ID or false on error.

+

Connection Status

+

Check Connection

+
public function isConnected(): bool
+

Example Usage

+

Connecting and Fetching Data

+
// Instantiate PicoDatabase
+$db = new PicoDatabase($credentials);
+
+// Connect to the database
+if ($db->connect()) {
+    // Fetch a user by ID
+    $user = $db->fetch("SELECT * FROM users WHERE id = ?", [1]);
+    print_r($user);
+
+    // Disconnect
+    $db->disconnect();
+}
+

Executing a Transaction

+
$db->connect();
+$db->setAudoCommit(false); // Disable autocommit
+
+try {
+    $db->executeInsert("INSERT INTO users (name) VALUES (?)", ['John Doe']);
+    $db->commit(); // Commit the transaction
+} catch (Exception $e) {
+    $db->rollback(); // Rollback on error
+}
+

Conclusion

+

PicoDatabase is a robust class for managing database operations in PHP applications. By following the examples and method descriptions provided in this manual, you can effectively utilize its features for your database interactions. For further assistance, refer to the source code and documentation available at MagicObject GitHub.

+
+ +

Entity

Entity is class to access database. Entity is derived from MagicObject. Some annotations required to activated all entity features.

Constructor

@@ -6479,7 +6711,7 @@

Method

}
-
+

Specification

Specifications are implemented in the PicoSpecification and PicoPredicate classes. PicoSpecification is a framework that can contain one or more PicoPredicate.

For example, we have the following query:

@@ -6972,7 +7204,7 @@

Specification

}
-
+

Pageable and Sortable

In MagicObject, pageable is used to divide data rows into several pages. This is required by the application to display a lot of data per page. While sortable is used to sort data before the data is divided per page.

Pageable can stand alone without sortable. However, this method is not recommended because the data sequence is not as expected. If new data is entered, users will have difficulty finding where it is located in the list and on which page the data will appear. The solution is to add a sortable that will sort the data based on certain columns. For example, the time of data creation is descending, then the new data will be on the first page. Conversely, if sorted based on the time of data creation is ascending, then the new data will be on the last page.

@@ -7264,7 +7496,7 @@

Pageable and Sortable

ORDER BY user_name ASC, email DESC, phone ASC LIMIT 200 OFFSET 400

-
+

Filtering, Ordering and Pagination

MagicObject will filter data according to the given criteria. On the other hand, MagicObject will only retrieve data on the specified page by specifying limit and offset data in the select query.

Example parameters:

@@ -8147,7 +8379,7 @@

Filtering, Ordering and Pagination

?>
-
+

Native Query

In MagicObject version 2, native queries have been introduced as an efficient way to interact with the database.

Native queries offer significant performance improvements when handling large volumes of data, allowing users to craft highly efficient queries that meet diverse requirements.

@@ -8777,7 +9009,7 @@

Best Practices

By leveraging the native query feature in MagicObject, you can create efficient and maintainable database interactions, enhancing your application's performance and security.

-
+

Multiple Database Connections

MagicObject version 2 introduces support for multiple database connections, enabling users to manage entities stored across different databases seamlessly. When performing operations such as JOINs with entities from multiple databases, it is essential to define a database connection for each entity involved.

Example Scenario

@@ -8942,7 +9174,7 @@

Conclusion

With MagicObject version 2, managing entities across multiple database connections is straightforward. By defining the correct associations and utilizing the provided methods, users can effectively work with complex data structures that span multiple databases. Make sure to handle exceptions properly to ensure robustness in your application.

-
+

Dump Database

We can dump database to another database type. We do not need any database converter. Just define the target database type when we dump the database.

Database Dump Overview

@@ -9456,7 +9688,7 @@

Summary

This approach allows developers to quickly switch between database types and manage their database schemas and data efficiently. The use of dedicated instances of PicoDatabaseDump for multiple tables ensures clarity and organization in your database operations.

-
+

Object Label

<?php
 
@@ -9581,7 +9813,7 @@ 

Object Label

// it will print "Admin Create"
-
+

Database Query Builder

Database Query Builder is a feature for creating object-based database queries. The output of the Database Query Builder is a query that can be directly executed by the database used.

Database Query Builder is actually designed for all relational databases but is currently only available in two languages, namely MySQL and PostgreSQL. MagicObject internally uses the Database Query Builder to create queries based on given methods and parameters.

@@ -9848,7 +10080,374 @@

Methods

This way, $active will be escaped before being executed by the database. You don't need to escape it first.

-
+
+

PicoSqlite

+

Overview

+

PicoSqlite is a PHP class designed for simplified interactions with SQLite databases using PDO (PHP Data Objects). This class extends PicoDatabase and provides methods for connecting to the database, creating tables, and performing basic CRUD (Create, Read, Update, Delete) operations.

+

Here are some advantages of using SQLite:

+
    +
  1. +

    Lightweight: SQLite is a serverless, self-contained database engine that requires minimal setup and uses a single file to store the entire database, making it easy to manage and deploy.

    +
  2. +
  3. +

    Easy to Use: Its simple API allows for straightforward integration with PHP, enabling quick database operations without the overhead of complex configurations.

    +
  4. +
  5. +

    No Server Required: Unlike other database systems, SQLite does not require a separate server process, which simplifies the development process and reduces resource usage.

    +
  6. +
  7. +

    Cross-Platform: SQLite databases are cross-platform and can be used on various operating systems without compatibility issues.

    +
  8. +
  9. +

    Fast Performance: For smaller databases and applications, SQLite often outperforms more complex database systems, thanks to its lightweight architecture.

    +
  10. +
  11. +

    ACID Compliance: SQLite provides full ACID (Atomicity, Consistency, Isolation, Durability) compliance, ensuring reliable transactions and data integrity.

    +
  12. +
  13. +

    Rich Feature Set: Despite being lightweight, SQLite supports many advanced features like transactions, triggers, views, and complex queries.

    +
  14. +
  15. +

    No Configuration Required: SQLite is easy to set up and requires no configuration, allowing developers to focus on building applications rather than managing the database server.

    +
  16. +
  17. +

    Great for Prototyping: Its simplicity makes it ideal for prototyping applications before moving to a more complex database system.

    +
  18. +
  19. +

    Good for Read-Heavy Workloads: SQLite performs well in read-heavy scenarios, making it suitable for applications where data is frequently read but rarely modified.

    +
  20. +
+

These features make SQLite a popular choice for many PHP applications, especially for smaller projects or for applications that need a lightweight database solution.

+

SQLite has a slightly different method for determining whether a SELECT query returns matching rows. While other databases often utilize the rowCount() method to get this information, SQLite does not support this functionality in the same way. To address this limitation, MagicObject has implemented a solution that seamlessly handles row checking for users. With MagicObject, developers can interact with SQLite without needing to worry about the intricacies of row counting. This allows for a more intuitive and efficient experience when working with SQLite, enabling users to focus on their application logic rather than the underlying database mechanics.

+

Requirements

+
    +
  • PHP 7.0 or higher
  • +
  • PDO extension enabled
  • +
+

Installation

+

To use the PicoSqlite class, include it in your PHP project. Ensure that your project structure allows for proper namespace loading.

+
use MagicObject\Database\PicoSqlite;
+
+// Example usage:
+$db = new PicoSqlite('path/to/database.sqlite');
+

Class Methods

+

Constructor

+
public function __construct($databaseFilePath)
+

Parameters:

+
string $databaseFilePath: The path to the SQLite database file.
+

Throws: PDOException if the connection fails.

+

Usage Example:

+
$sqlite = new PicoSqlite('path/to/database.sqlite');
+

Connecting to the Database

+
public function connect($withDatabase = true)
+

Parameters:

+
    +
  • bool $withDatabase: Optional. Default is true. Indicates whether to select the database when connecting.
  • +
+

Returns: bool - True if the connection is successful, false otherwise.

+

Usage Example:

+
if ($sqlite->connect()) {
+    echo "Connected to database successfully.";
+} else {
+    echo "Failed to connect.";
+}
+

Check Table

+
public function tableExists($tableName) : bool
+

Parameters:

+
    +
  • string $tableName: The name of the table to check.
  • +
+

Returns: bool - True if the table exists, false otherwise.

+

Usage Example:

+
if ($sqlite->tableExists('users')) {
+    echo "Table exists.";
+} else {
+    echo "Table does not exist.";
+}
+

Create Table

+
public function createTable($tableName, $columns) : int|false
+

Parameters:

+
    +
  • string $tableName: The name of the table to create.
  • +
  • string[] $columns: An array of columns in the format 'column_name TYPE'.
  • +
+

Returns: int|false - Number of rows affected or false on failure.

+

Usage Example:

+
$columns = ['id INTEGER PRIMARY KEY', 'name TEXT', 'email TEXT'];
+$sqlite->createTable('users', $columns);
+

Insert

+
public function insert($tableName, $data) : array 
+

Parameters:

+
    +
  • string $tableName: The name of the table to insert into.
  • +
  • array $data: An associative array of column names and values to insert.
  • +
+

Returns: bool - True on success, false on failure.

+

Usage Example:

+
$data = ['name' => 'John Doe', 'email' => 'john@example.com'];
+$sqlite->insert('users', $data);
+
public function update($tableName, $data, $conditions) : bool
+

Parameters:

+
    +
  • string $tableName: The name of the table to update. +array $data: An associative array of column names and new values.
  • +
  • array $conditions: An associative array of conditions for the WHERE clause.
  • +
+

Returns: bool - True on success, false on failure.

+

Usage Example:

+
$data = ['name' => 'John Smith'];
+$conditions = ['id' => 1];
+$sqlite->update('users', $data, $conditions);
+

Delete

+
public function delete($tableName, $conditions) : bool 
+

Parameters:

+
    +
  • string $tableName: The name of the table to delete from.
  • +
  • array $conditions: An associative array of conditions for the WHERE clause.
  • +
+

Returns: bool - True on success, false on failure.

+

Usage Example:

+
$conditions = ['id' => 1];
+$sqlite->delete('users', $conditions);
+

Entity with PicoSqlite

+
<?php
+
+use MagicObject\Database\PicoSqlite;
+use MagicObject\MagicObject;
+use MagicObject\Util\Database\PicoDatabaseUtilSqlite;
+
+require_once dirname(__DIR__) . "/vendor/autoload.php";
+
+/**
+ * @Entity
+ * @JSON(property-naming-strategy=SNAKE_CASE, prettify=false)
+ * @Table(name="album")
+ * @Cache(enable="true")
+ * @package MusicProductionManager\Data\Entity
+ */
+class Album extends MagicObject
+{
+    /**
+     * Album ID
+     * 
+     * @Id
+     * @GeneratedValue(strategy=GenerationType.UUID)
+     * @NotNull
+     * @Column(name="album_id", type="varchar(50)", length=50, nullable=false)
+     * @Label(content="Album ID")
+     * @var string
+     */
+    protected $albumId;
+
+    /**
+     * Name
+     * 
+     * @Column(name="name", type="varchar(50)", length=50, nullable=true)
+     * @Label(content="Name")
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * Title
+     * 
+     * @Column(name="title", type="text", nullable=true)
+     * @Label(content="Title")
+     * @var string
+     */
+    protected $title;
+
+    /**
+     * Description
+     * 
+     * @Column(name="description", type="longtext", nullable=true)
+     * @Label(content="Description")
+     * @var string
+     */
+    protected $description;
+
+    /**
+     * Producer ID
+     * 
+     * @Column(name="producer_id", type="varchar(40)", length=40, nullable=true)
+     * @Label(content="Producer ID")
+     * @var string
+     */
+    protected $producerId;
+
+    /**
+     * Release Date
+     * 
+     * @Column(name="release_date", type="date", nullable=true)
+     * @Label(content="Release Date")
+     * @var string
+     */
+    protected $releaseDate;
+
+    /**
+     * Number Of Song
+     * 
+     * @Column(name="number_of_song", type="int(11)", length=11, nullable=true)
+     * @Label(content="Number Of Song")
+     * @var integer
+     */
+    protected $numberOfSong;
+
+    /**
+     * Duration
+     * 
+     * @Column(name="duration", type="float", nullable=true)
+     * @Label(content="Duration")
+     * @var double
+     */
+    protected $duration;
+
+    /**
+     * Image Path
+     * 
+     * @Column(name="image_path", type="text", nullable=true)
+     * @Label(content="Image Path")
+     * @var string
+     */
+    protected $imagePath;
+
+    /**
+     * Sort Order
+     * 
+     * @Column(name="sort_order", type="int(11)", length=11, nullable=true)
+     * @Label(content="Sort Order")
+     * @var integer
+     */
+    protected $sortOrder;
+
+    /**
+     * Time Create
+     * 
+     * @Column(name="time_create", type="timestamp", length=19, nullable=true, updatable=false)
+     * @Label(content="Time Create")
+     * @var string
+     */
+    protected $timeCreate;
+
+    /**
+     * Time Edit
+     * 
+     * @Column(name="time_edit", type="timestamp", length=19, nullable=true)
+     * @Label(content="Time Edit")
+     * @var string
+     */
+    protected $timeEdit;
+
+    /**
+     * Admin Create
+     * 
+     * @Column(name="admin_create", type="varchar(40)", length=40, nullable=true, updatable=false)
+     * @Label(content="Admin Create")
+     * @var string
+     */
+    protected $adminCreate;
+
+    /**
+     * Admin Edit
+     * 
+     * @Column(name="admin_edit", type="varchar(40)", length=40, nullable=true)
+     * @Label(content="Admin Edit")
+     * @var string
+     */
+    protected $adminEdit;
+
+    /**
+     * IP Create
+     * 
+     * @Column(name="ip_create", type="varchar(50)", length=50, nullable=true, updatable=false)
+     * @Label(content="IP Create")
+     * @var string
+     */
+    protected $ipCreate;
+
+    /**
+     * IP Edit
+     * 
+     * @Column(name="ip_edit", type="varchar(50)", length=50, nullable=true)
+     * @Label(content="IP Edit")
+     * @var string
+     */
+    protected $ipEdit;
+
+    /**
+     * Active
+     * 
+     * @Column(name="active", type="tinyint(1)", length=1, default_value="1", nullable=true)
+     * @DefaultColumn(value="1")
+     * @var boolean
+     */
+    protected $active;
+
+    /**
+     * As Draft
+     * 
+     * @Column(name="as_draft", type="tinyint(1)", length=1, default_value="1", nullable=true)
+     * @DefaultColumn(value="1")
+     * @var boolean
+     */
+    protected $asDraft;
+
+}
+
+$database = new PicoSqlite(__DIR__ . "/db.sqlite", null, function($sql){
+    //echo $sql."\r\n";
+});
+try
+{
+    $database->connect();
+
+    $album = new Album(null, $database);
+
+    // create table if not exists
+    $util = new PicoDatabaseUtilSqlite();
+    $tableStructure = $util->showCreateTable($album, true);
+    $database->query($tableStructure);
+
+    $album->setAlbumId("1235");
+    $album->setName("Meraih Mimpi 2 ");
+    $album->setTitle("Meraih Mimpi 2");
+    $album->setDescription("Album pertama dengan judul Meraih Mimpi 2");
+    $album->setProducerId("5678");
+    $album->setReleaseDate("2024-09-09");
+    $album->setNumberOfSong(10);
+    $album->duration(185*60);
+    $album->setSortOrder(1);
+    $album->setIpCreate("::1");
+    $album->setIpEdit("::1");
+    $album->setTimeCreate(date("Y-m-d H:i:s"));
+    $album->setTimeEdit(date("Y-m-d H:i:s"));
+    $album->setAdminCreate("1");
+    $album->setAdminEdit("1");
+    $album->setIpCreate("::1");
+    $album->setActive(true);
+    $album->setAsDraft(false);
+    echo $album."\r\n--------\r\n";
+    $album->save();
+
+    $album2 = new Album(null, $database);
+
+    $res = $album2->findAll();
+    foreach($res->getResult() as $row)
+    {
+        echo $row."\r\n";
+    }
+}
+catch(Exception $e)
+{
+    echo $e->getMessage();
+}
+

Error Handling

+

If an operation fails, PicoSqlite may throw exceptions or return false. It is recommended to implement error handling using try-catch blocks to catch PDOException for connection-related issues.

+

Conclusion

+

PicoSqlite provides an efficient way to interact with SQLite databases. Its straightforward API allows developers to perform common database operations with minimal code. For more advanced database operations, consider extending the class or using additional PDO features.

+
+ +

Upload File

Uploading lots of files with arrays is difficult for some developers, especially novice developers. There is a significant difference between uploading a single file and multiple files.

When the developer decides to change the form from single file to multiple files or vice versa, the backend developer must change the code to handle the uploaded files.

@@ -9908,7 +10507,7 @@

Summary

This implementation offers a straightforward way to manage file uploads in PHP, abstracting complexities for developers. By using methods like getAll() and isMultiple(), developers can seamlessly handle both types of uploads without needing to write separate logic for each scenario. This approach not only improves code maintainability but also enhances the developer experience.

-
+

Language

MagicObject supports multilingual applications. MagicObject allows developers to create entities that support a wide variety of languages that users can choose from. At the same time, different users can use different languages.

To create table with multiple language, create new class from DataTable object. We can copy data from aother object to DataTable easly.

@@ -10128,7 +10727,7 @@

Language

echo $apa;
-
+

Database Migration

MagicObject allows users to import data from a database with different table names and column names between the source database and the destination database. This feature is used by developers who develop applications that are already used in production environments.

On the one hand, the application requires a new database structure according to what is defined by the developer. On the other hand, users want to use existing data.

diff --git a/manual/index.html.md b/manual/index.html.md index 58ead7c3..c4528002 100644 --- a/manual/index.html.md +++ b/manual/index.html.md @@ -25,6 +25,7 @@ includes: - magic-dto - input - session + - database - entity - specification - pagable @@ -34,6 +35,7 @@ includes: - database-dump - object-label - database-query-builder + - sqlite - upload-file - data-table - database-migration diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php index 984949f6..5a72fb48 100644 --- a/src/Database/PicoDatabase.php +++ b/src/Database/PicoDatabase.php @@ -9,6 +9,7 @@ use MagicObject\Exceptions\InvalidDatabaseConfiguration; use MagicObject\Exceptions\NullPointerException; use MagicObject\SecretObject; +use ReflectionFunction; use stdClass; /** @@ -49,49 +50,49 @@ class PicoDatabase //NOSONAR * * @var SecretObject */ - private $databaseCredentials; + protected $databaseCredentials; /** * Indicates whether the database is connected or not. * * @var bool */ - private $connected = false; + protected $connected = false; /** * Autocommit setting. * * @var bool */ - private $autocommit = true; + protected $autocommit = true; /** * Database connection. * * @var PDO */ - private $databaseConnection; + protected $databaseConnection; /** * Database type. * * @var string */ - private $databaseType = ""; + protected $databaseType = ""; /** * Callback function when executing queries that modify data. * * @var callable|null */ - private $callbackExecuteQuery = null; + protected $callbackExecuteQuery = null; /** * Callback function when executing any query. * * @var callable|null */ - private $callbackDebugQuery = null; + protected $callbackDebugQuery = null; /** * Constructor to initialize the PicoDatabase object. @@ -282,23 +283,25 @@ public function getDatabaseConnection() * Execute a query. * * @param string $sql SQL to be executed. + * @param array|null $params Optional parameters for the SQL query. * @return PDOStatement|false Returns the PDOStatement object if successful, or false on failure. - * @throws PDOException + * @throws PDOException If an error occurs while executing the query. */ - public function query($sql) + public function query($sql, $params = null) { - return $this->executeQuery($sql); + return $this->executeQuery($sql, $params); } /** * Fetch a result. * * @param string $sql SQL to be executed. - * @param int $tentativeType Tentative type for fetch mode. + * @param int $tentativeType Tentative type for fetch mode (e.g., PDO::FETCH_ASSOC). * @param mixed $defaultValue Default value to return if no results found. + * @param array|null $params Optional parameters for the SQL query. * @return array|object|stdClass|null Returns the fetched result as an array, object, or stdClass, or the default value if no results are found. */ - public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null) + public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null) { if ($this->databaseConnection == null) { throw new NullPointerException(self::DATABASE_NONECTION_IS_NULL); @@ -309,8 +312,19 @@ public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = n $stmt = $this->databaseConnection->prepare($sql); try { - $stmt->execute(); - $result = $stmt->rowCount() > 0 ? $stmt->fetch($tentativeType) : $defaultValue; + $stmt->execute($params); + if($this->getDatabaseType() == PicoDatabaseType::DATABASE_TYPE_SQLITE) + { + $result = $stmt->fetch($tentativeType); + if($result === false) + { + $result = $defaultValue; + } + } + else + { + $result = $stmt->rowCount() > 0 ? $stmt->fetch($tentativeType) : $defaultValue; + } } catch (PDOException $e) { $result = $defaultValue; } @@ -322,9 +336,11 @@ public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = n * Check if a record exists. * * @param string $sql SQL to be executed. + * @param array|null $params Optional parameters for the SQL query. * @return bool True if the record exists, false otherwise. + * @throws NullPointerException If the database connection is null. */ - public function isRecordExists($sql) + public function isRecordExists($sql, $params = null) { if ($this->databaseConnection == null) { throw new NullPointerException(self::DATABASE_NONECTION_IS_NULL); @@ -334,8 +350,16 @@ public function isRecordExists($sql) $stmt = $this->databaseConnection->prepare($sql); try { - $stmt->execute(); - return $stmt->rowCount() > 0; + $stmt->execute($params); + if($this->getDatabaseType() == PicoDatabaseType::DATABASE_TYPE_SQLITE) + { + $result = $stmt->fetch(); + return $result !== false; + } + else + { + return $stmt->rowCount() > 0; + } } catch (PDOException $e) { throw new PDOException($e->getMessage(), intval($e->getCode())); } @@ -345,11 +369,12 @@ public function isRecordExists($sql) * Fetch all results. * * @param string $sql SQL to be executed. - * @param int $tentativeType Tentative type for fetch mode. + * @param int $tentativeType Tentative type for fetch mode (e.g., PDO::FETCH_ASSOC). * @param mixed $defaultValue Default value to return if no results found. + * @param array|null $params Optional parameters for the SQL query. * @return array|null Returns an array of results or the default value if no results are found. */ - public function fetchAll($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null) + public function fetchAll($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null) { if ($this->databaseConnection == null) { throw new NullPointerException(self::DATABASE_NONECTION_IS_NULL); @@ -360,8 +385,19 @@ public function fetchAll($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue $stmt = $this->databaseConnection->prepare($sql); try { - $stmt->execute(); - $result = $stmt->rowCount() > 0 ? $stmt->fetchAll($tentativeType) : $defaultValue; + $stmt->execute($params); + if($this->getDatabaseType() == PicoDatabaseType::DATABASE_TYPE_SQLITE) + { + $result = $stmt->fetch($tentativeType); + if($result === false) + { + $result = $defaultValue; + } + } + else + { + $result = $stmt->rowCount() > 0 ? $stmt->fetchAll($tentativeType) : $defaultValue; + } } catch (PDOException $e) { $result = $defaultValue; } @@ -373,18 +409,20 @@ public function fetchAll($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue * Execute a query without returning anything. * * @param string $sql Query string to be executed. + * @param array|null $params Optional parameters for the SQL query. + * @throws NullPointerException If the database connection is null. */ - public function execute($sql) + public function execute($sql, $params = null) { if ($this->databaseConnection == null) { throw new NullPointerException(self::DATABASE_NONECTION_IS_NULL); } - $this->executeDebug($sql); + $this->executeDebug($sql, $params); $stmt = $this->databaseConnection->prepare($sql); try { - $stmt->execute(); + $stmt->execute($params); } catch (PDOException $e) { // Handle exception as needed } @@ -394,20 +432,22 @@ public function execute($sql) * Execute a query and return the statement. * * @param string $sql Query string to be executed. - * @return PDOStatement|bool Returns the PDOStatement object if successful, or false on failure. - * @throws PDOException + * @param array|null $params Optional parameters for the SQL query. + * @return PDOStatement|false Returns the PDOStatement object if successful, or false on failure. + * @throws NullPointerException If the database connection is null. + * @throws PDOException If an error occurs while executing the query. */ - public function executeQuery($sql) + public function executeQuery($sql, $params = null) { if ($this->databaseConnection == null) { throw new NullPointerException(self::DATABASE_NONECTION_IS_NULL); } - $this->executeDebug($sql); + $this->executeDebug($sql, $params); $stmt = $this->databaseConnection->prepare($sql); try { - $stmt->execute(); + $stmt->execute($params); } catch (PDOException $e) { throw new PDOException($e->getMessage(), intval($e->getCode())); } @@ -419,12 +459,13 @@ public function executeQuery($sql) * Execute an insert query. * * @param string $sql Query string to be executed. - * @return PDOStatement|bool Returns the PDOStatement object if successful, or false on failure. + * @param array|null $params Optional parameters for the SQL query. + * @return PDOStatement|false Returns the PDOStatement object if successful, or false on failure. */ - public function executeInsert($sql) + public function executeInsert($sql, $params = null) { - $stmt = $this->executeQuery($sql); - $this->executeCallback($sql, self::QUERY_INSERT); + $stmt = $this->executeQuery($sql, $params); + $this->executeCallback($sql, $params, self::QUERY_INSERT); return $stmt; } @@ -432,12 +473,13 @@ public function executeInsert($sql) * Execute an update query. * * @param string $sql Query string to be executed. - * @return PDOStatement|bool Returns the PDOStatement object if successful, or false on failure. + * @param array|null $params Optional parameters for the SQL query. + * @return PDOStatement|false Returns the PDOStatement object if successful, or false on failure. */ - public function executeUpdate($sql) + public function executeUpdate($sql, $params = null) { - $stmt = $this->executeQuery($sql); - $this->executeCallback($sql, self::QUERY_UPDATE); + $stmt = $this->executeQuery($sql, $params); + $this->executeCallback($sql, $params, self::QUERY_UPDATE); return $stmt; } @@ -445,12 +487,13 @@ public function executeUpdate($sql) * Execute a delete query. * * @param string $sql Query string to be executed. - * @return PDOStatement|bool Returns the PDOStatement object if successful, or false on failure. + * @param array|null $params Optional parameters for the SQL query. + * @return PDOStatement|false Returns the PDOStatement object if successful, or false on failure. */ - public function executeDelete($sql) + public function executeDelete($sql, $params = null) { - $stmt = $this->executeQuery($sql); - $this->executeCallback($sql, self::QUERY_DELETE); + $stmt = $this->executeQuery($sql, $params); + $this->executeCallback($sql, $params, self::QUERY_DELETE); return $stmt; } @@ -458,12 +501,13 @@ public function executeDelete($sql) * Execute a transaction query. * * @param string $sql Query string to be executed. - * @return PDOStatement|bool Returns the PDOStatement object if successful, or false on failure. + * @param array|null $params Optional parameters for the SQL query. + * @return PDOStatement|false Returns the PDOStatement object if successful, or false on failure. */ - public function executeTransaction($sql) + public function executeTransaction($sql, $params = null) { - $stmt = $this->executeQuery($sql); - $this->executeCallback($sql, self::QUERY_TRANSACTION); + $stmt = $this->executeQuery($sql, $params); + $this->executeCallback($sql, $params, self::QUERY_TRANSACTION); return $stmt; } @@ -471,11 +515,25 @@ public function executeTransaction($sql) * Execute a callback query function. * * @param string $query SQL to be executed. - * @param string $type Query type. + * @param array|null $params Optional parameters for the SQL query. + * @param string|null $type Query type. */ - private function executeCallback($query, $type) + private function executeCallback($query, $params = null, $type = null) { if ($this->callbackExecuteQuery !== null && is_callable($this->callbackExecuteQuery)) { + $reflection = new ReflectionFunction($this->callbackDebugQuery); + + // Get number of parameters + $numberOfParams = $reflection->getNumberOfParameters(); + $numberOfRequiredParams = $reflection->getNumberOfRequiredParameters(); + if($numberOfParams == 3) + { + call_user_func($this->callbackDebugQuery, $query, $params, $type); + } + else + { + call_user_func($this->callbackDebugQuery, $query); + } call_user_func($this->callbackExecuteQuery, $query, $type); } } @@ -484,11 +542,27 @@ private function executeCallback($query, $type) * Execute a debug query function. * * @param string $query SQL to be executed. + * @param array|null $params Optional parameters for the SQL query. */ - private function executeDebug($query) + private function executeDebug($query, $params = null) { if ($this->callbackDebugQuery !== null && is_callable($this->callbackDebugQuery)) { - call_user_func($this->callbackDebugQuery, $query); + + $reflection = new ReflectionFunction($this->callbackDebugQuery); + + // Get number of parameters + $numberOfParams = $reflection->getNumberOfParameters(); + $numberOfRequiredParams = $reflection->getNumberOfRequiredParameters(); + + if($numberOfParams == 2) + { + call_user_func($this->callbackDebugQuery, $query, $params); + } + else + { + call_user_func($this->callbackDebugQuery, $query); + } + } } diff --git a/src/Database/PicoDatabasePersistence.php b/src/Database/PicoDatabasePersistence.php index 56cab896..c1b891e5 100644 --- a/src/Database/PicoDatabasePersistence.php +++ b/src/Database/PicoDatabasePersistence.php @@ -529,7 +529,7 @@ public function getTableInfo() if(isset($cache)) { - $noCache = self::VALUE_FALSE == strtolower($cache[self::KEY_ENABLE]); + $noCache = isset($cache[self::KEY_ENABLE]) && self::VALUE_FALSE == strtolower($cache[self::KEY_ENABLE]); } if(empty($package)) { @@ -595,10 +595,15 @@ public function getTableInfo() * Get match row * * @param PDOStatement $stmt PDO statement + * @param string $databaseType Database type * @return bool */ - public function matchRow($stmt) + public function matchRow($stmt, $databaseType = null) { + if(isset($databaseType) && $databaseType == PicoDatabaseType::DATABASE_TYPE_SQLITE) + { + return true; + } if($stmt == null) { return false; @@ -1913,11 +1918,19 @@ public function find($propertyValues) try { $stmt = $this->database->executeQuery($sqlQuery); - if($this->matchRow($stmt)) + if($this->matchRow($stmt, $this->database->getDatabaseType())) { $row = $stmt->fetch(PDO::FETCH_ASSOC); - $data = $this->fixDataType($row, $info); - $data = $this->join($data, $row, $info); + if($row === false) + { + // SQLite database + $data = null; + } + else + { + $data = $this->fixDataType($row, $info); + $data = $this->join($data, $row, $info); + } return $data; } else @@ -2336,11 +2349,19 @@ public function findOneWithPrimaryKeyValue($primaryKeyVal, $subqueryMap) ->limit(1) ->offset(0); $stmt = $this->database->executeQuery($sqlQuery); - if($this->matchRow($stmt)) + if($this->matchRow($stmt, $this->database->getDatabaseType())) { $row = $stmt->fetch(PDO::FETCH_ASSOC); - $data = $this->fixDataType($row, $info); - $data = self::applySubqueryResult($data, $row, $subqueryMap); + if($row === false) + { + // SQLite database + $data = null; + } + else + { + $data = $this->fixDataType($row, $info); + $data = self::applySubqueryResult($data, $row, $subqueryMap); + } } else { @@ -2378,7 +2399,7 @@ public function findSpecificWithSubquery($selected, $specification, $pageable = try { $stmt = $this->database->executeQuery($sqlQuery); - if($this->matchRow($stmt)) + if($this->matchRow($stmt, $this->database->getDatabaseType())) { while ($row = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT)) { @@ -2485,7 +2506,7 @@ public function findSpecific($selected, $specification, $pageable = null, $sorta try { $stmt = $this->database->executeQuery($sqlQuery); - if($this->matchRow($stmt)) + if($this->matchRow($stmt, $this->database->getDatabaseType())) { while ($row = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT)) { @@ -2567,7 +2588,7 @@ public function findBy($propertyName, $propertyValue, $pageable = null, $sortabl try { $stmt = $this->database->executeQuery($sqlQuery); - if($this->matchRow($stmt)) + if($this->matchRow($stmt, $this->database->getDatabaseType())) { while ($row = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT)) { @@ -2774,11 +2795,19 @@ public function findOneBy($propertyName, $propertyValue, $sortable = null) try { $stmt = $this->database->executeQuery($sqlQuery); - if($this->matchRow($stmt)) + if($this->matchRow($stmt, $this->database->getDatabaseType())) { $row = $stmt->fetch(PDO::FETCH_ASSOC); - $data = $this->fixDataType($row, $info); - $data = $this->join($data, $row, $info); + if($row === false) + { + // SQLite database + $data = null; + } + else + { + $data = $this->fixDataType($row, $info); + $data = $this->join($data, $row, $info); + } } else { @@ -3325,11 +3354,19 @@ private function _select($info = null, $queryBuilder = null, $where = null, $spe try { $stmt = $this->database->executeQuery($sqlQuery); - if($this->matchRow($stmt)) + if($this->matchRow($stmt, $this->database->getDatabaseType())) { $row = $stmt->fetch(PDO::FETCH_ASSOC); - $data = $this->fixDataType($row, $info); - $data = $this->join($data, $row, $info); + if($row === false) + { + // SQLite database + $data = null; + } + else + { + $data = $this->fixDataType($row, $info); + $data = $this->join($data, $row, $info); + } } else { @@ -3385,7 +3422,7 @@ private function _selectAll($info = null, $queryBuilder = null, $where = null, $ try { $stmt = $this->database->executeQuery($sqlQuery); - if($this->matchRow($stmt)) + if($this->matchRow($stmt, $this->database->getDatabaseType())) { while ($row = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT)) { diff --git a/src/Database/PicoDatabaseType.php b/src/Database/PicoDatabaseType.php index 1bfbb934..4eba0946 100644 --- a/src/Database/PicoDatabaseType.php +++ b/src/Database/PicoDatabaseType.php @@ -27,4 +27,9 @@ class PicoDatabaseType * PostgreSQL database type. */ const DATABASE_TYPE_POSTGRESQL = "postgresql"; + + /** + * SQLite database type. + */ + const DATABASE_TYPE_SQLITE = "sqlite"; } diff --git a/src/Database/PicoSqlite.php b/src/Database/PicoSqlite.php new file mode 100644 index 00000000..68c58d3e --- /dev/null +++ b/src/Database/PicoSqlite.php @@ -0,0 +1,186 @@ +databaseFilePath = $databaseFilePath; + + if ($callbackExecuteQuery !== null && is_callable($callbackExecuteQuery)) { + $this->callbackExecuteQuery = $callbackExecuteQuery; + } + + if ($callbackDebugQuery !== null && is_callable($callbackDebugQuery)) { + $this->callbackDebugQuery = $callbackDebugQuery; + } + + $this->databaseType = PicoDatabaseType::DATABASE_TYPE_SQLITE; + } + + /** + * Connect to the database. + * + * @param bool $withDatabase Flag to select the database when connected. + * @return bool True if the connection is successful, false if it fails. + */ + public function connect($withDatabase = true) + { + $connected = false; + try { + $this->databaseConnection = new PDO("sqlite:" . $this->databaseFilePath); + $this->databaseConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $connected = true; + $this->connected = true; + } catch (PDOException $e) { + throw new PDOException($e->getMessage(), intval($e->getCode())); + } + return $connected; + } + + /** + * Check if a table exists in the database. + * + * @param string $tableName The name of the table to check. + * @return bool True if the table exists, false otherwise. + */ + public function tableExists($tableName) + { + $query = "SELECT name FROM sqlite_master WHERE type='table' AND name=:tableName"; + $stmt = $this->databaseConnection->prepare($query); + $stmt->bindValue(':tableName', $tableName); + $stmt->execute(); + return $stmt->fetch() !== false; + } + + /** + * Create a new table in the database. + * + * @param string $tableName The name of the table to create. + * @param string[] $columns An array of columns in the format 'column_name TYPE'. + * @return int|false Returns the number of rows affected or false on failure. + */ + public function createTable($tableName, $columns) { + $columnsStr = implode(", ", $columns); + $sql = "CREATE TABLE IF NOT EXISTS $tableName ($columnsStr)"; + return $this->databaseConnection->exec($sql); + } + + /** + * Insert a new record into the specified table. + * + * @param string $tableName The name of the table to insert into. + * @param array $data An associative array of column names and values to insert. + * @return bool Returns true on success or false on failure. + */ + public function insert($tableName, $data) { + $columns = implode(", ", array_keys($data)); + $placeholders = ":" . implode(", :", array_keys($data)); + $sql = "INSERT INTO $tableName ($columns) VALUES ($placeholders)"; + $stmt = $this->databaseConnection->prepare($sql); + + foreach ($data as $key => $value) { + $stmt->bindValue(':' . $key, $value); + } + + return $stmt->execute(); + } + + /** + * Select records from the specified table with optional conditions. + * + * @param string $tableName The name of the table to select from. + * @param array $conditions An associative array of conditions for the WHERE clause. + * @return array Returns an array of fetched records as associative arrays. + */ + public function select($tableName, $conditions = []) { + $sql = "SELECT * FROM $tableName"; + if (!empty($conditions)) { + $conditionStr = implode(" AND ", array_map(function($key) { + return "$key = :$key"; + }, array_keys($conditions))); + $sql .= " WHERE $conditionStr"; + } + + $stmt = $this->databaseConnection->prepare($sql); + foreach ($conditions as $key => $value) { + $stmt->bindValue(':' . $key, $value); + } + $stmt->execute(); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Update existing records in the specified table based on conditions. + * + * @param string $tableName The name of the table to update. + * @param array $data An associative array of column names and new values. + * @param array $conditions An associative array of conditions for the WHERE clause. + * @return bool Returns true on success or false on failure. + */ + public function update($tableName, $data, $conditions) { + $dataStr = implode(", ", array_map(function($key) { + return "$key = :$key"; + }, array_keys($data))); + + $conditionStr = implode(" AND ", array_map(function($key) { + return "$key = :$key"; + }, array_keys($conditions))); + + $sql = "UPDATE $tableName SET $dataStr WHERE $conditionStr"; + $stmt = $this->databaseConnection->prepare($sql); + + foreach (array_merge($data, $conditions) as $key => $value) { + $stmt->bindValue(':' . $key, $value); + } + + return $stmt->execute(); + } + + /** + * Delete records from the specified table based on conditions. + * + * @param string $tableName The name of the table to delete from. + * @param array $conditions An associative array of conditions for the WHERE clause. + * @return bool Returns true on success or false on failure. + */ + public function delete($tableName, $conditions) { + $conditionStr = implode(" AND ", array_map(function($key) { + return "$key = :$key"; + }, array_keys($conditions))); + + $sql = "DELETE FROM $tableName WHERE $conditionStr"; + $stmt = $this->databaseConnection->prepare($sql); + + foreach ($conditions as $key => $value) { + $stmt->bindValue(':' . $key, $value); + } + + return $stmt->execute(); + } + + +} diff --git a/src/Geometry/SphericalGeometry.php b/src/Geometry/SphericalGeometry.php index 62ab40e3..cea56032 100644 --- a/src/Geometry/SphericalGeometry.php +++ b/src/Geometry/SphericalGeometry.php @@ -228,7 +228,10 @@ public static function computeArea($latLngsArray) */ public static function computeSignedArea($latLngsArray, $signed = true) { - if (empty($latLngsArray) || count($latLngsArray) < 3) return 0; + if (empty($latLngsArray) || count($latLngsArray) < 3) + { + return 0; + } $e = 0; $r2 = pow(self::EARTH_RADIUS, 2); diff --git a/src/MagicDto.php b/src/MagicDto.php index f69e8d47..ea6ff69b 100644 --- a/src/MagicDto.php +++ b/src/MagicDto.php @@ -47,7 +47,7 @@ class MagicDto extends stdClass // NOSONAR * * @var mixed */ - private $_dataSource = null; + private $_dataSource = null; //NOSONAR /** * Constructor. @@ -96,20 +96,18 @@ public function __construct($data = null) */ public function loadData($data) { - if (isset($data)) { - // Check if data is not a scalar value - if (is_object($data) || is_array($data)) { - // Check if the data is one of the allowed object types - if ($data instanceof self || $data instanceof MagicObject || - $data instanceof SetterGetter || $data instanceof SecretObject || - $data instanceof PicoGenericObject) { - // Directly assign the data source if it is an allowed object type - $this->_dataSource = $data; - } else { - // Parse the object or array recursively - $this->_dataSource = PicoObjectParser::parseRecursiveObject($data); - } - } + // Check if data is not a scalar value + if (isset($data) && (is_object($data) || is_array($data))) { + // Check if the data is one of the allowed object types + if ($data instanceof self || $data instanceof MagicObject || + $data instanceof SetterGetter || $data instanceof SecretObject || + $data instanceof PicoGenericObject) { + // Directly assign the data source if it is an allowed object type + $this->_dataSource = $data; + } else { + // Parse the object or array recursively + $this->_dataSource = PicoObjectParser::parseRecursiveObject($data); + } } return $this; } @@ -134,9 +132,16 @@ public function loadXml($xmlString) } /** - * Get the object values + * Retrieves an object containing the values of the properties of the current instance. + * + * This method iterates through the properties of the instance, excluding inherited properties, + * and constructs an object where each property is mapped to its corresponding value. + * The method handles various property types including self-references, magic objects, + * DateTime instances, and standard class objects. * - * @return stdClass An object containing the values of the properties + * @return stdClass An object containing the values of the properties, where each property + * name corresponds to the JSON property or the original key if no + * JSON property is defined. */ public function value() { @@ -151,7 +156,7 @@ public function value() $var = $this->extractVar($doc); $propertyName = $jsonProperty ? $jsonProperty : $key; - $objectTest = class_exists($var) ? new $var() : null; + $objectTest = $this->createTestObject($var); if ($this->isSelfInstance($objectTest)) { $returnValue->$propertyName = $this->handleSelfInstance($source, $var, $propertyName); @@ -160,15 +165,42 @@ public function value() } elseif ($this->isDateTimeInstance($objectTest)) { $returnValue->$propertyName = $this->formatDateTime($this->handleDateTimeObject($source, $propertyName), $this, $key); } else if($this->_dataSource instanceof stdClass || is_object($this->_dataSource)) { - $returnValue->$propertyName = $this->handleStdClass($source, $key, $propertyName); + $returnValue->$propertyName = $this->handleStdClass($source, $key); } else if(isset($this->_dataSource)) { - $returnValue->$propertyName = $this->handleDefaultCase($source, $key, $propertyName); + $returnValue->$propertyName = $this->handleDefaultCase($source, $key); } } } return $returnValue; } + + /** + * Creates an instance of a class based on the provided variable name. + * + * This method checks if the class corresponding to the given variable name exists. + * If it does, an instance of that class is created and returned; otherwise, null is returned. + * + * @param string $var The name of the class to instantiate. + * @return mixed|null An instance of the class if it exists, or null if the class does not exist. + */ + private function createTestObject($var) + { + return isset($var) && !empty($var) && class_exists($var) ? new $var() : null; + } + /** + * Formats a DateTime object according to specified JSON format parameters. + * + * This method checks if the provided DateTime object is set and, if so, retrieves + * formatting parameters from the property's annotations. If a 'JsonFormat' + * parameter is present, its pattern is used; otherwise, a default format + * of 'Y-m-d H:i:s' is applied. + * + * @param DateTime|null $dateTime The DateTime object to format. + * @param object $class The class instance from which the property originates. + * @param string $property The name of the property being processed. + * @return string|null The formatted date as a string, or null if the DateTime is not set. + */ private function formatDateTime($dateTime, $class, $property) { if(!isset($dateTime)) @@ -243,23 +275,12 @@ private function extractVar($doc) * @param string $doc The documentation comment containing the label. * @return string|null The extracted label or null if not found. */ - private function extractLabel($doc) + private function extractLabel($doc) // NOSONAR { preg_match('/@Label\("([^"]+)"\)/', $doc, $matches); return !empty($matches[1]) ? $matches[1] : null; } - /** - * Checks if the given variable is a self-instance. - * - * @param mixed $objectTest The object to test against. - * @return bool True if it's a self-instance, otherwise false. - */ - private function isSelfInstance($objectTest) - { - return $objectTest instanceof self; - } - /** * Handles the case where the property is a self-instance. * @@ -278,6 +299,17 @@ private function handleSelfInstance($source, $var, $propertyName) return $this->getNestedValue($source); } } + + /** + * Checks if the given variable is a self-instance. + * + * @param mixed $objectTest The object to test against. + * @return bool True if it's a self-instance, otherwise false. + */ + private function isSelfInstance($objectTest) + { + return isset($objectTest) && $objectTest instanceof self; + } /** * Checks if the given object is an instance of MagicObject or its derivatives. @@ -287,10 +319,10 @@ private function handleSelfInstance($source, $var, $propertyName) */ private function isMagicObjectInstance($objectTest) { - return $objectTest instanceof MagicObject || + return isset($objectTest) && ($objectTest instanceof MagicObject || $objectTest instanceof SetterGetter || $objectTest instanceof SecretObject || - $objectTest instanceof PicoGenericObject; + $objectTest instanceof PicoGenericObject); } /** @@ -301,7 +333,7 @@ private function isMagicObjectInstance($objectTest) */ private function isDateTimeInstance($objectTest) { - return $objectTest instanceof DateTime; + return isset($objectTest) && $objectTest instanceof DateTime; } /** @@ -346,16 +378,11 @@ private function handleDateTimeObject($source, $propertyName) * * @param string|null $source The source to extract the value from. * @param string $key The key of the property. - * @param string $propertyName The name of the property. * @return mixed The handled default value. */ - private function handleDefaultCase($source, $key, $propertyName) + private function handleDefaultCase($source, $key) { - if (strpos($source, "->") === false) { - return isset($source) ? $this->_dataSource->get($source) : $this->_dataSource->get($key); - } else { - return $this->getNestedValue($source); - } + return $this->handleStdClass($source, $key); } /** @@ -363,16 +390,29 @@ private function handleDefaultCase($source, $key, $propertyName) * * @param string|null $source The source to extract the value from. * @param string $key The key of the property. - * @param string $propertyName The name of the property. * @return mixed The handled default value. */ - private function handleStdClass($source, $key, $propertyName) + private function handleStdClass($source, $key) { + // Check if the source does not contain a nested property indicator if (strpos($source, "->") === false) { - return isset($source) && isset($this->_dataSource->{$source}) ? $this->_dataSource->{$source} : $this->_dataSource->{$key}; + // If the source is set and exists in the data source, retrieve its value + if (isset($source) && isset($this->_dataSource->{$source})) { + $value = $this->_dataSource->{$source}; + // If the source is not available, check for the key in the data source + } elseif (isset($this->_dataSource->{$key})) { + $value = $this->_dataSource->{$key}; + // If neither is available, set value to null + } else { + $value = null; + } + // If the source indicates a nested property, retrieve its value using a different method } else { - return $this->getNestedValue($source); + $value = $this->getNestedValue($source); } + + // Return the retrieved value + return $value; } /** @@ -694,7 +734,7 @@ public function toXml($root = "root") { * @param array $dataArray The array to convert * @param SimpleXMLElement $xml XML element to append to */ - function arrayToXml($dataArray, $xml) { + public function arrayToXml($dataArray, $xml) { foreach ($dataArray as $key => $value) { // Replace spaces and special characters in key names $key = preg_replace('/[^a-z0-9_]/i', '_', $key); diff --git a/src/MagicObject.php b/src/MagicObject.php index 0f963eb5..d69a66a0 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -2402,7 +2402,7 @@ public function __call($method, $params) // NOSONAR $var = lcfirst(substr($method, 3)); return isset($this->$var) ? $this->$var : null; } - else if (strncasecmp($method, "set", 3) === 0 && isset($params) && isset($params[0]) && !$this->_readonly) { + else if (strncasecmp($method, "set", 3) === 0 && isset($params) && is_array($params) && !empty($params) && !$this->_readonly) { $var = lcfirst(substr($method, 3)); $this->$var = $params[0]; $this->modifyNullProperties($var, $params[0]); @@ -2410,7 +2410,7 @@ public function __call($method, $params) // NOSONAR } else if (strncasecmp($method, "unset", 5) === 0 && !$this->_readonly) { $var = lcfirst(substr($method, 5)); - $this->removeValue($var, $params[0]); + $this->removeValue($var); return $this; } else if (strncasecmp($method, "push", 4) === 0 && isset($params) && is_array($params) && !$this->_readonly) { diff --git a/src/SecretObject.php b/src/SecretObject.php index fddacead..17fb9d28 100644 --- a/src/SecretObject.php +++ b/src/SecretObject.php @@ -257,7 +257,7 @@ public function __call($method, $params) // NOSONAR $var = lcfirst(substr($method, 3)); return $this->_get($var); } - else if (strncasecmp($method, "set", 3) === 0 && isset($params) && isset($params[0]) && !$this->_readonly) { + else if (strncasecmp($method, "set", 3) === 0 && isset($params) && is_array($params) && !empty($params) && !$this->_readonly) { $var = lcfirst(substr($method, 3)); $this->_set($var, $params[0]); $this->modifyNullProperties($var, $params[0]); diff --git a/src/Util/Database/PicoDatabaseUtilMySql.php b/src/Util/Database/PicoDatabaseUtilMySql.php index ac045a55..0ddbd5f4 100644 --- a/src/Util/Database/PicoDatabaseUtilMySql.php +++ b/src/Util/Database/PicoDatabaseUtilMySql.php @@ -50,12 +50,12 @@ class PicoDatabaseUtilMySql extends PicoDatabaseUtilBase implements PicoDatabase * Retrieves a list of columns for a specified table. * * @param PicoDatabase $database Database connection. - * @param string $picoTableName Table name. + * @param string $tableName Table name. * @return array An array of column details. */ - public function getColumnList($database, $picoTableName) + public function getColumnList($database, $tableName) { - $sql = "SHOW COLUMNS FROM $picoTableName"; + $sql = "SHOW COLUMNS FROM $tableName"; return $database->fetchAll($sql); } @@ -67,20 +67,20 @@ public function getColumnList($database, $picoTableName) * "DROP TABLE IF EXISTS". It also handles the definition of primary keys if present. * * @param PicoTableInfo $tableInfo The information about the table, including column details and primary keys. - * @param string $picoTableName The name of the table for which the structure is being generated. + * @param string $tableName The name of the table for which the structure is being generated. * @param bool $createIfNotExists Whether to add "IF NOT EXISTS" in the CREATE statement (default is false). * @param bool $dropIfExists Whether to add "DROP TABLE IF EXISTS" before the CREATE statement (default is false). * @param string|null $engine The storage engine to use for the table (optional, default is null). * @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, $picoTableName, $createIfNotExists = false, $dropIfExists = false, $engine = 'InnoDB', $charset = 'utf8mb4') + public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false, $dropIfExists = false, $engine = 'InnoDB', $charset = 'utf8mb4') { $query = array(); $columns = array(); if($dropIfExists) { - $query[] = "-- DROP TABLE IF EXISTS `$picoTableName`;"; + $query[] = "-- DROP TABLE IF EXISTS `$tableName`;"; $query[] = ""; } $createStatement = ""; @@ -93,7 +93,7 @@ public function dumpStructure($tableInfo, $picoTableName, $createIfNotExists = f $autoIncrementKeys = $this->getAutoIncrementKey($tableInfo); - $query[] = "$createStatement `$picoTableName` ("; + $query[] = "$createStatement `$tableName` ("; foreach($tableInfo->getColumns() as $column) { @@ -106,7 +106,7 @@ public function dumpStructure($tableInfo, $picoTableName, $createIfNotExists = f if(isset($pk) && is_array($pk) && !empty($pk)) { $query[] = ""; - $query[] = "ALTER TABLE `$picoTableName`"; + $query[] = "ALTER TABLE `$tableName`"; foreach($pk as $primaryKey) { $query[] = "\tADD PRIMARY KEY (`$primaryKey[name]`)"; @@ -119,7 +119,7 @@ public function dumpStructure($tableInfo, $picoTableName, $createIfNotExists = f if(isset($autoIncrementKeys) && is_array($autoIncrementKeys) && in_array($column[parent::KEY_NAME], $autoIncrementKeys)) { $query[] = ""; - $query[] = "ALTER TABLE `$picoTableName` \r\n\tMODIFY ".trim($this->createColumn($column), " \r\n\t ")." AUTO_INCREMENT"; + $query[] = "ALTER TABLE `$tableName` \r\n\tMODIFY ".trim($this->createColumn($column), " \r\n\t ")." AUTO_INCREMENT"; $query[] = ";"; } } @@ -199,13 +199,13 @@ public function fixDefaultValue($defaultValue, $type) * columns based on the provided column definitions. * * @param array $columns An associative array where keys are column names and values are column details. - * @param string $picoTableName The name of the table where the record will be inserted. + * @param string $tableName The name of the table where the record will be inserted. * @param MagicObject $record The data record to be inserted, which provides a method to retrieve values. * * @return string The generated SQL INSERT statement. * @throws Exception If the record cannot be processed or if there are no values to insert. */ - public function dumpRecord($columns, $picoTableName, $record) + public function dumpRecord($columns, $tableName, $record) { $value = $record->valueArray(); $rec = array(); @@ -219,7 +219,7 @@ public function dumpRecord($columns, $picoTableName, $record) $queryBuilder = new PicoDatabaseQueryBuilder(PicoDatabaseType::DATABASE_TYPE_MYSQL); $queryBuilder->newQuery() ->insert() - ->into($picoTableName) + ->into($tableName) ->fields(array_keys($rec)) ->values(array_values($rec)); diff --git a/src/Util/Database/PicoDatabaseUtilPostgreSql.php b/src/Util/Database/PicoDatabaseUtilPostgreSql.php index e282fa6e..1824f345 100644 --- a/src/Util/Database/PicoDatabaseUtilPostgreSql.php +++ b/src/Util/Database/PicoDatabaseUtilPostgreSql.php @@ -54,13 +54,13 @@ class PicoDatabaseUtilPostgreSql extends PicoDatabaseUtilBase implements PicoDat * and default values. * * @param PicoDatabase $database The database connection instance. - * @param string $picoTableName The name of the table to retrieve column information from. + * @param string $tableName The name of the table to retrieve column information from. * @return array An array of associative arrays containing details about each column, * where each associative array includes 'column_name', 'data_type', * 'is_nullable', and 'column_default'. * @throws Exception If the database connection fails or the query cannot be executed. */ - public function getColumnList($database, $picoTableName) + public function getColumnList($database, $tableName) { $schema = $database->getDatabaseCredentials()->getDatabaseSchema(); if(!isset($schema) || empty($schema)) @@ -69,7 +69,7 @@ public function getColumnList($database, $picoTableName) } $sql = "SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns - WHERE table_schema = '$schema' AND table_name = '$picoTableName'"; + WHERE table_schema = '$schema' AND table_name = '$tableName'"; return $database->fetchAll($sql); } @@ -81,18 +81,18 @@ public function getColumnList($database, $picoTableName) * "DROP TABLE IF EXISTS". It also handles the definition of primary keys if present. * * @param PicoTableInfo $tableInfo The information about the table, including column details and primary keys. - * @param string $picoTableName The name of the table for which the structure is being generated. + * @param string $tableName The name of the table for which the structure is being generated. * @param bool $createIfNotExists Whether to add "IF NOT EXISTS" in the CREATE statement (default is false). * @param bool $dropIfExists Whether to add "DROP TABLE IF EXISTS" before the CREATE statement (default is false). * @param string|null $engine The storage engine to use for the table (optional, default is null). * @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, $picoTableName, $createIfNotExists = false, $dropIfExists = false, $engine = null, $charset = null) + public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false, $dropIfExists = false, $engine = null, $charset = null) { $query = []; if ($dropIfExists) { - $query[] = "-- DROP TABLE IF EXISTS \"$picoTableName\";"; + $query[] = "-- DROP TABLE IF EXISTS \"$tableName\";"; $query[] = ""; } @@ -103,10 +103,10 @@ public function dumpStructure($tableInfo, $picoTableName, $createIfNotExists = f $autoIncrementKeys = $this->getAutoIncrementKey($tableInfo); - $query[] = "$createStatement \"$picoTableName\" ("; + $query[] = "$createStatement \"$tableName\" ("; foreach ($tableInfo->getColumns() as $column) { - $query[] = $this->createColumn($column); + $query[] = $this->createColumnPostgre($column, $autoIncrementKeys); } $query[] = implode(",\r\n", $query); $query[] = ");"; @@ -114,7 +114,7 @@ public function dumpStructure($tableInfo, $picoTableName, $createIfNotExists = f $pk = $tableInfo->getPrimaryKeys(); if (isset($pk) && is_array($pk) && !empty($pk)) { $query[] = ""; - $query[] = "ALTER TABLE \"$picoTableName\""; + $query[] = "ALTER TABLE \"$tableName\""; foreach ($pk as $primaryKey) { $query[] = "\tADD PRIMARY KEY (\"$primaryKey[name]\")"; } @@ -161,6 +161,69 @@ public function createColumn($column) return implode(" ", $col); } + /** + * Creates a column definition for a PostgreSQL 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. If the column is specified + * as auto-increment, it will use SERIAL or BIGSERIAL data types as appropriate. + * + * @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 default_value: The default value for the column (optional). + * + * @param array|null $autoIncrementKeys An optional array of column names that should + * be treated as auto-incrementing. + * + * @return string The SQL column definition formatted as a string, suitable for + * inclusion in a CREATE TABLE statement. + */ + public function createColumnPostgre($column, $autoIncrementKeys = null) + { + $col = []; + $col[] = "\t"; + $col[] = "\"" . $column[parent::KEY_NAME] . "\""; + + // Check if the column should be auto-incrementing. + if(isset($autoIncrementKeys) && is_array($autoIncrementKeys) && in_array($column[parent::KEY_NAME], $autoIncrementKeys)) + { + // Determine the appropriate serial type based on the column's type. + if(stripos($column['type'], 'big')) + { + $col[] = "BIGSERIAL"; // Use BIGSERIAL for large integers. + } + else + { + $col[] = "SERIAL"; // Use SERIAL for standard integers. + } + } + else + { + $col[] = $column['type']; // Use the specified type if not auto-incrementing. + } + + // Determine nullability and add it to the definition. + if (isset($column['nullable']) && strtolower(trim($column['nullable'])) == 'true') { + $col[] = "NULL"; // Allow NULL values. + } else { + $col[] = "NOT NULL"; // Disallow NULL values. + } + + // Handle default value if provided, using a helper method to format it. + if (isset($column['default_value'])) { + $defaultValue = $column['default_value']; + $defaultValue = $this->fixDefaultValue($defaultValue, $column['type']); + $col[] = "DEFAULT $defaultValue"; + } + + return implode(" ", $col); // Join all parts into a single string. + } + + /** * Fixes the default value for SQL insertion based on its type. * @@ -195,13 +258,13 @@ public function fixDefaultValue($defaultValue, $type) * columns based on the provided column definitions. * * @param array $columns An associative array where keys are column names and values are column details. - * @param string $picoTableName The name of the table where the record will be inserted. + * @param string $tableName The name of the table where the record will be inserted. * @param MagicObject $record The data record to be inserted, which provides a method to retrieve values. * * @return string The generated SQL INSERT statement. * @throws Exception If the record cannot be processed or if there are no values to insert. */ - public function dumpRecord($columns, $picoTableName, $record) + public function dumpRecord($columns, $tableName, $record) { $value = $record->valueArray(); $rec = []; @@ -214,7 +277,7 @@ public function dumpRecord($columns, $picoTableName, $record) $queryBuilder = new PicoDatabaseQueryBuilder(PicoDatabaseType::DATABASE_TYPE_POSTGRESQL); $queryBuilder->newQuery() ->insert() - ->into($picoTableName) + ->into($tableName) ->fields(array_keys($rec)) ->values(array_values($rec)); diff --git a/src/Util/Database/PicoDatabaseUtilSqlite.php b/src/Util/Database/PicoDatabaseUtilSqlite.php new file mode 100644 index 00000000..45339459 --- /dev/null +++ b/src/Util/Database/PicoDatabaseUtilSqlite.php @@ -0,0 +1,435 @@ +tableInfo(); + $tableName = $tableInfo->getTableName(); + + // Start building the CREATE TABLE query + if($createIfNotExists) + { + $condition = " IF NOT EXISTS"; + } + else + { + $condition = ""; + } + + $autoIncrementKeys = $this->getAutoIncrementKey($tableInfo); + + $query = ""; + if($dropIfExists) + { + $query .= "-- DROP TABLE IF EXISTS `$tableName`;\r\n\r\n"; + } + $query .= "CREATE TABLE$condition $tableName (\n"; + + // Define primary key + $primaryKey = null; + + $pKeys = $tableInfo->getPrimaryKeys(); + + $pKeyArr = []; + $pKeyArrUsed = []; + if(isset($pKeys) && is_array($pKeys) && !empty($pKeys)) + { + $pkVals = array_values($pKeys); + foreach($pkVals as $pk) + { + $pKeyArr[] = $pk['name']; + } + } + + foreach ($tableInfo->getColumns() as $column) { + + $columnName = $column['name']; + $columnType = $column['type']; + $length = isset($column['length']) ? $column['length'] : null; + $nullable = (isset($column['nullable']) && $column['nullable'] === 'true') ? ' NULL' : ' NOT NULL'; + $defaultValue = isset($column['default_value']) ? " DEFAULT '{$column['default_value']}'" : ''; + + // Convert column type for SQL + $columnType = strtolower($columnType); // Convert to lowercase for case-insensitive comparison + + if(isset($autoIncrementKeys) && is_array($autoIncrementKeys) && in_array($column[parent::KEY_NAME], $autoIncrementKeys)) { + $sqlType = 'INTEGER PRIMARY KEY AUTOINCREMENT'; + $pKeyArrUsed[] = $columnName; + } elseif (strpos($columnType, 'varchar') !== false) { + $sqlType = "VARCHAR($length)"; + } elseif ($columnType === 'tinyint(1)') { + $sqlType = 'TINYINT(1)'; + } elseif (stripos($columnType, 'tinyint') !== false) { + $sqlType = strtoupper($columnType); + } elseif (stripos($columnType, 'smallint') !== false) { + $sqlType = strtoupper($columnType); + } elseif (stripos($columnType, 'bigint') !== false) { + $sqlType = strtoupper($columnType); + } elseif (stripos($columnType, 'integer') !== false) { + $sqlType = strtoupper($columnType); + } elseif (stripos($columnType, 'int') !== false) { + $sqlType = strtoupper($columnType); + } elseif ($columnType === 'int') { + $sqlType = 'INT'; + } elseif ($columnType === 'float') { + $sqlType = 'FLOAT'; + } elseif ($columnType === 'text') { + $sqlType = 'TEXT'; + } elseif ($columnType === 'longtext') { + $sqlType = 'LONGTEXT'; + } elseif ($columnType === 'date') { + $sqlType = 'DATE'; + } elseif ($columnType === 'timestamp') { + $sqlType = 'TIMESTAMP'; + } elseif ($columnType === 'blob') { + $sqlType = 'BLOB'; + } else { + $sqlType = 'VARCHAR(255)'; // Fallback type + } + + // Add to query + $query .= "\t$columnName $sqlType$nullable$defaultValue,\n"; + + } + + // Remove the last comma and add primary key constraint + $query = rtrim($query, ",\n") . "\n"; + + $pKeyArrFinal = []; + foreach($pKeyArr as $k=>$v) + { + if(!in_array($v, $pKeyArrUsed)) + { + $pKeyArrFinal[] = $v; + } + } + + if (!empty($pKeyArrFinal)) { + $primaryKey = implode(", ", $pKeyArrFinal); + $query = rtrim($query, ",\n"); + $query .= ",\n\tPRIMARY KEY ($primaryKey)\n"; + } + + $query .= ");"; + + return str_replace("\n", "\r\n", $query); + } + + /** + * Retrieves a list of columns for a specified table in the database. + * + * This method queries the information schema to obtain details about the columns + * of the specified table, including their names, data types, nullability, + * default values, and any additional attributes such as primary keys and auto-increment. + * + * @param PicoDatabase $database The database connection instance. + * @param string $tableName The name of the table to retrieve column information from. + * @return array An array of associative arrays containing details about each column, + * where each associative array includes: + * - 'Field': The name of the column. + * - 'Type': The data type of the column. + * - 'Null': Indicates if the column allows NULL values ('YES' or 'NO'). + * - 'Key': Indicates if the column is a primary key ('PRI' or null). + * - 'Default': The default value of the column, or 'None' if not set. + * - 'Extra': Additional attributes of the column, such as 'auto_increment'. + * @throws Exception If the database connection fails or the query cannot be executed. + */ + public function getColumnList($database, $tableName) + { + $stmt = $database->query("PRAGMA table_info($tableName)"); + + // Fetch and display the column details + $rows = array(); + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $rows[] = array( + "Field" => $row['name'], + "Type" => $row['type'], + "Null" => $row['notnull'] ? 'YES' : 'NO', + "Key" => $row['pk'] ? 'PRI' : null, + "Default" => $row['dflt_value'] ? $row['dflt_value'] : 'None', + "Extra" => ($row['pk'] == 1 && $row['type'] === 'INTEGER') ? 'auto_increment' : null + ); + } + return $rows; + } + + /** + * Dumps the structure of a table as a SQL statement. + * + * This method generates a SQL CREATE TABLE statement based on the provided table information, + * including the option to include or exclude specific clauses such as "IF NOT EXISTS" and + * "DROP TABLE IF EXISTS". It also handles the definition of primary keys if present. + * + * @param PicoTableInfo $tableInfo The information about the table, including column details and primary keys. + * @param string $tableName The name of the table for which the structure is being generated. + * @param bool $createIfNotExists Whether to add "IF NOT EXISTS" in the CREATE statement (default is false). + * @param bool $dropIfExists Whether to add "DROP TABLE IF EXISTS" before the CREATE statement (default is false). + * @param string|null $engine The storage engine to use for the table (optional, default is null). + * @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') + { + $query = array(); + $columns = array(); + if($dropIfExists) + { + $query[] = "-- DROP TABLE IF EXISTS `$tableName`;"; + $query[] = ""; + } + $createStatement = ""; + + $createStatement = "CREATE TABLE"; + if($createIfNotExists) + { + $createStatement .= " IF NOT EXISTS"; + } + + $autoIncrementKeys = $this->getAutoIncrementKey($tableInfo); + + $query[] = "$createStatement `$tableName` ("; + + foreach($tableInfo->getColumns() as $column) + { + $columns[] = $this->createColumn($column); + } + $query[] = implode(",\r\n", $columns); + $query[] = ") ENGINE=$engine DEFAULT CHARSET=$charset;"; + + $pk = $tableInfo->getPrimaryKeys(); + if(isset($pk) && is_array($pk) && !empty($pk)) + { + $query[] = ""; + $query[] = "ALTER TABLE `$tableName`"; + foreach($pk as $primaryKey) + { + $query[] = "\tADD PRIMARY KEY (`$primaryKey[name]`)"; + } + $query[] = ";"; + } + + foreach($tableInfo->getColumns() as $column) + { + if(isset($autoIncrementKeys) && is_array($autoIncrementKeys) && in_array($column[parent::KEY_NAME], $autoIncrementKeys)) + { + $query[] = ""; + $query[] = "ALTER TABLE `$tableName` \r\n\tMODIFY ".trim($this->createColumn($column), " \r\n\t ")." AUTO_INCREMENT"; + $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 default_value: 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) + { + $col = array(); + $col[] = "\t"; + $col[] = "`".$column[parent::KEY_NAME]."`"; + $col[] = $column['type']; + if(isset($column['nullable']) && strtolower(trim($column['nullable'])) == 'true') + { + $col[] = "NULL"; + } + else + { + $col[] = "NOT NULL"; + } + if(isset($column['default_value'])) + { + $defaultValue = $column['default_value']; + $defaultValue = $this->fixDefaultValue($defaultValue, $column['type']); + $col[] = "DEFAULT $defaultValue"; + } + return implode(" ", $col); + } + + /** + * Fixes the default value for SQL insertion based on its type. + * + * This method processes the given default value according to the specified data type, + * ensuring that it is correctly formatted for SQL insertion. For string-like types, + * the value is enclosed in single quotes, while boolean and null values are returned + * as is. + * + * @param mixed $defaultValue The default value to fix, which can be a string, boolean, or null. + * @param string $type The data type of the column (e.g., ENUM, CHAR, TEXT, INT, FLOAT, DOUBLE). + * + * @return mixed The fixed default value formatted appropriately for SQL insertion. + */ + public function fixDefaultValue($defaultValue, $type) + { + if(strtolower($defaultValue) == 'true' || strtolower($defaultValue) == 'false' || strtolower($defaultValue) == 'null') + { + return $defaultValue; + } + if(stripos($type, 'enum') !== false || stripos($type, 'char') !== false || stripos($type, 'text') !== false || stripos($type, 'int') !== false || stripos($type, 'float') !== false || stripos($type, 'double') !== false) + { + return "'".$defaultValue."'"; + } + return $defaultValue; + } + + /** + * Fixes imported data based on specified column types. + * + * This method processes the input data array and adjusts the values + * according to the expected types defined in the columns array. It + * supports boolean, integer, and float types. + * + * @param mixed[] $data The input data to be processed. + * @param string[] $columns An associative array mapping column names to their types. + * @return mixed[] The updated data array with fixed types. + */ + public function fixImportData($data, $columns) + { + // Iterate through each item in the data array + foreach($data as $name=>$value) + { + // Check if the column exists in the columns array + if(isset($columns[$name])) + { + $type = $columns[$name]; + + if(strtolower($type) == 'tinyint(1)' || strtolower($type) == 'boolean' || strtolower($type) == 'bool') + { + // Process boolean types + $data = $this->fixBooleanData($data, $name, $value); + } + else if(stripos($type, 'integer') !== false || stripos($type, 'int(') !== false) + { + // Process integer types + $data = $this->fixIntegerData($data, $name, $value); + } + else if(stripos($type, 'float') !== false || stripos($type, 'double') !== false || stripos($type, 'decimal') !== false) + { + // Process float types + $data = $this->fixFloatData($data, $name, $value); + } + } + } + return $data; + } + + /** + * Automatically configures the import data settings based on the source and target databases. + * + * This method connects to the source and target databases, retrieves the list of existing + * tables, and updates the configuration for each target table by checking its presence in the + * source database. It handles exceptions and logs any errors encountered during the process. + * + * @param SecretObject $config The configuration object containing database and table information. + * @return SecretObject The updated configuration object with modified table settings. + */ + public function autoConfigureImportData($config) + { + $databaseConfigSource = $config->getDatabaseSource(); + $databaseConfigTarget = $config->getDatabaseTarget(); + + $databaseSource = new PicoDatabase($databaseConfigSource); + $databaseTarget = new PicoDatabase($databaseConfigTarget); + try + { + $databaseSource->connect(); + $databaseTarget->connect(); + $tables = $config->getTable(); + + $existingTables = array(); + foreach($tables as $tb) + { + $existingTables[] = $tb->getTarget(); + } + + $sourceTableList = $databaseSource->fetchAll("SELECT name FROM sqlite_master WHERE type='table'", PDO::FETCH_NUM); + $targetTableList = $databaseTarget->fetchAll("SELECT name FROM sqlite_master WHERE type='table'", PDO::FETCH_NUM); + + $sourceTables = call_user_func_array('array_merge', $sourceTableList); + $targetTables = call_user_func_array('array_merge', $targetTableList); + + foreach($targetTables as $target) + { + $tables = $this->updateConfigTable($databaseSource, $databaseTarget, $tables, $sourceTables, $target, $existingTables); + } + $config->setTable($tables); + } + catch(Exception $e) + { + error_log($e->getMessage()); + } + return $config; + } + +} \ No newline at end of file diff --git a/src/Util/Database/PicoSqlParser.php b/src/Util/Database/PicoSqlParser.php index c215807b..348314ed 100644 --- a/src/Util/Database/PicoSqlParser.php +++ b/src/Util/Database/PicoSqlParser.php @@ -19,6 +19,12 @@ */ class PicoSqlParser { + const KEY_COLUMN_NAME = 'Column Name'; + const KEY_PRIMARY_KEY = 'Primary Key'; + const KEY_TYPE = 'Type'; + const KEY_LENGTH = 'Length'; + const KEY_NULLABLE = 'Nullable'; + const KEY_DEFAULT = 'Default'; /** * Type list * @@ -67,18 +73,18 @@ private function inArray($haystack, $needle) * @return array Information about the table, columns, and primary key. * @throws InvalidArgumentException if the SQL statement is not a valid CREATE TABLE statement. */ - public function parseTable($sql) + public function parseTable($sql) //NOSONAR { $arr = explode(";", $sql); $sql = $arr[0]; $rg_tb = '/(create\s+table\s+if\s+not\s+exists|create\s+table)\s+(?.*)\s+\(/i'; - $rg_fld = '/(\w+\s+key.*|\w+\s+bigserial|\w+\s+serial4|\w+\s+tinyint.*|\w+\s+bigint.*|\w+\s+text.*|\w+\s+varchar.*|\w+\s+char.*|\w+\s+real.*|\w+\s+float.*|\w+\s+integer.*|\w+\s+int.*|\w+\s+datetime.*|\w+\s+date.*|\w+\s+double.*|\w+\s+bigserial.*|\w+\s+serial.*|\w+\s+timestamp .*)/i'; + $rg_fld = '/(\w+\s+key.*|\w+\s+bigserial|\w+\s+serial4|\w+\s+tinyint.*|\w+\s+bigint.*|\w+\s+text.*|\w+\s+varchar.*|\w+\s+char.*|\w+\s+real.*|\w+\s+float.*|\w+\s+integer.*|\w+\s+int.*|\w+\s+datetime.*|\w+\s+date.*|\w+\s+double.*|\w+\s+bigserial.*|\w+\s+serial.*|\w+\s+timestamp .*)/i'; //NOSONAR $rg_fld2 = '/(?\w+)\s+(?\w+)(?.*)/i'; $rg_not_null = '/not\s+null/i'; $rg_pk = '/primary\s+key/i'; $rg_fld_def = '/default\s+(.+)/i'; - $rg_pk2 = '/(PRIMARY|UNIQUE) KEY\s+[a-zA-Z_0-9\s]+\(([a-zA-Z_0-9,\s]+)\)/i'; + $rg_pk2 = '/(PRIMARY|UNIQUE) KEY\s+[a-zA-Z_0-9\s]+\(([a-zA-Z_0-9,\s]+)\)/i'; //NOSONAR preg_match($rg_tb, $sql, $result); $tableName = $result['tb']; @@ -102,7 +108,7 @@ public function parseTable($sql) $def = null; preg_match($rg_fld_def, $attr2, $def); - $comment = null; + $comment = null; //NOSONAR if ($def) { $def = trim($def[1]); @@ -120,12 +126,12 @@ public function parseTable($sql) $def = null; } $fld_list[] = [ - 'Column Name' => $columnName, - 'Type' => trim($rg_fld2_result['ftype']), - 'Length' => $length, - 'Primary Key' => $is_pk, - 'Nullable' => $nullable, - 'Default' => $def + self::KEY_COLUMN_NAME => $columnName, + self::KEY_TYPE => trim($rg_fld2_result['ftype']), + self::KEY_LENGTH => $length, + self::KEY_PRIMARY_KEY => $is_pk, + self::KEY_NULLABLE => $nullable, + self::KEY_DEFAULT => $def ]; $columnList[] = $columnName; } @@ -135,9 +141,10 @@ public function parseTable($sql) } if ($primaryKey !== null) { - foreach ($fld_list as &$column) { - if ($column['Column Name'] === $primaryKey) { - $column['Primary Key'] = true; + foreach ($fld_list as &$column) //NOSONAR + { + if ($column[self::KEY_COLUMN_NAME] === $primaryKey) { + $column[self::KEY_PRIMARY_KEY] = true; } } } @@ -147,8 +154,8 @@ public function parseTable($sql) $x = str_replace(['(', ')'], '', $x); $pkeys = array_map('trim', explode(',', $x)); foreach ($fld_list as &$column) { - if ($this->inArray($pkeys, $column['Column Name'])) { - $column['Primary Key'] = true; + if ($this->inArray($pkeys, $column[self::KEY_COLUMN_NAME])) { + $column[self::KEY_PRIMARY_KEY] = true; } } } @@ -193,7 +200,7 @@ private function isValidType($dataType) */ public function getResult() { - return $this->tableInfo; + return $this->getTableInfo(); } /** diff --git a/src/Util/PicoIniUtil.php b/src/Util/PicoIniUtil.php index 35fec43f..9fea2293 100644 --- a/src/Util/PicoIniUtil.php +++ b/src/Util/PicoIniUtil.php @@ -129,50 +129,61 @@ public static function parseIniFile($path) */ public static function parseIniString($str) { + // Split the input string into lines $lines = explode("\n", $str); - $ret = array(); - $inside_section = false; + $ret = array(); // Initialize the result array + $insideSection = false; // Track if we are inside a section + // Iterate through each line of the INI string foreach ($lines as $line) { - $line = trim($line); + $line = trim($line); // Remove whitespace from the beginning and end + // Skip invalid lines if (self::invalidLine($line)) { continue; } + // Check for a section header if ($line[0] == "[" && $endIdx = strpos($line, "]")) { - $inside_section = substr($line, 1, $endIdx - 1); + $insideSection = substr($line, 1, $endIdx - 1); continue; } + // Skip lines without an equals sign if (!strpos($line, '=')) { continue; } + // Split the line into key and value $tmp = explode("=", $line, 2); - if ($inside_section) { + if ($insideSection) { + // Process key-value pairs inside a section + $key = rtrim($tmp[0]); // Trim the key + $value = ltrim($tmp[1]); // Trim the value + $value = self::removeSurroundingQuotes($value); // Apply any necessary value fixes + $value = self::removeSurroundingQuotesRegex($value); // Apply additional fixes - $key = rtrim($tmp[0]); - $value = ltrim($tmp[1]); - $value = self::fixValue1($value); - $value = self::fixValue2($value); + // Match the key format to determine if it's a sub-array preg_match("^\[(.*?)\]^", $key, $matches); if (self::matchValue($matches)) { - $arr_name = preg_replace('#\[(.*?)\]#is', '', $key); - $ret = self::fixValue3($ret, $inside_section, $arr_name, $matches, $value); + // Handle array-like keys + $arrName = preg_replace('#\[(.*?)\]#is', '', $key); + $ret = self::organizeValue($ret, $insideSection, $arrName, $matches, $value); } else { - $ret[$inside_section][trim($tmp[0])] = $value; + // Standard key-value assignment + $ret[$insideSection][trim($tmp[0])] = $value; } } else { - $value = ltrim($tmp[1]); - $value = self::fixValue1($value); - $ret[trim($tmp[0])] = $value; + // Process key-value pairs outside of any section + $value = ltrim($tmp[1]); // Trim the value + $value = self::removeSurroundingQuotes($value); // Apply value fixes + $ret[trim($tmp[0])] = $value; // Assign to the result array } } - return $ret; + return $ret; // Return the final parsed array } /** @@ -202,13 +213,13 @@ public static function invalidLine($line) /** * Remove surrounding quotes from a value. * - * This method checks if the value is surrounded by double or single quotes - * and removes them if present. + * This method checks if the given value is surrounded by either double or single quotes + * and removes those quotes if they are present. * * @param string $value The value to fix. * @return string The cleaned value without surrounding quotes. */ - public static function fixValue1($value) + public static function removeSurroundingQuotes($value) { if ( PicoStringUtil::startsWith($value, '"') && PicoStringUtil::endsWith($value, '"') @@ -222,13 +233,13 @@ public static function fixValue1($value) /** * Remove surrounding quotes from a value using regex. * - * This method checks if the value matches the pattern of being surrounded by + * This method checks if the given value matches the pattern of being surrounded by * double or single quotes and removes them if so. * * @param string $value The value to fix. * @return string The cleaned value without surrounding quotes. */ - public static function fixValue2($value) + public static function removeSurroundingQuotesRegex($value) { if (preg_match("/^\".*\"$/", $value) || preg_match("/^'.*'$/", $value)) { $value = mb_substr($value, 1, mb_strlen($value) - 2); @@ -239,26 +250,26 @@ public static function fixValue2($value) /** * Fix and organize the value in the parsed result. * - * This method ensures that the given array is correctly formatted - * based on the provided parameters, handling nested structures. + * This method ensures that the provided array is correctly formatted based on the + * given parameters, handling nested structures as needed. * * @param array $ret The parsed result array to update. - * @param string $inside_section The current section name. - * @param string $arr_name The name of the array key. + * @param string $insideSection The name of the current section. + * @param string $arrName The name of the array key to update. * @param array $matches Matches found during parsing. - * @param mixed $value The value to assign. + * @param mixed $value The value to assign to the array. * @return array The updated parsed result array. */ - public static function fixValue3($ret, $inside_section, $arr_name, $matches, $value) + public static function organizeValue($ret, $insideSection, $arrName, $matches, $value) { - if (!isset($ret[$inside_section][$arr_name]) || !is_array($ret[$inside_section][$arr_name])) { - $ret[$inside_section][$arr_name] = array(); + if (!isset($ret[$insideSection][$arrName]) || !is_array($ret[$insideSection][$arrName])) { + $ret[$insideSection][$arrName] = array(); } if (isset($matches[1]) && !empty($matches[1])) { - $ret[$inside_section][$arr_name][$matches[1]] = $value; + $ret[$insideSection][$arrName][$matches[1]] = $value; } else { - $ret[$inside_section][$arr_name][] = $value; + $ret[$insideSection][$arrName][] = $value; } return $ret; } diff --git a/tests/sqlite.php b/tests/sqlite.php new file mode 100644 index 00000000..486fac00 --- /dev/null +++ b/tests/sqlite.php @@ -0,0 +1,324 @@ +connect(); + + $album = new Album(null, $database); + $acuanPengawasan = new AcuanPengawasan(null, $database); + + // create table if not exists + $util = new PicoDatabaseUtilSqlite(); + $tableStructure = $util->showCreateTable($album, true); + echo $tableStructure."\r\n"; + + $database->query($tableStructure); + + $tableStructure2 = $util->showCreateTable($acuanPengawasan, true); + echo $tableStructure2."\r\n"; + + $database->query($tableStructure); + $database->query($tableStructure2); + + + $album->setName("Meraih Mimpi 1 "); + $album->setTitle("Meraih Mimpi 1"); + $album->setDescription("Album pertama dengan judul Meraih Mimpi 1"); + $album->setProducerId("5678"); + $album->setReleaseDate("2024-09-09"); + $album->setNumberOfSong(10); + $album->duration(185*60); + $album->setSortOrder(1); + $album->setIpCreate("::1"); + $album->setIpEdit("::1"); + $album->setTimeCreate(date("Y-m-d H:i:s")); + $album->setTimeEdit(date("Y-m-d H:i:s")); + $album->setAdminCreate("1"); + $album->setAdminEdit("1"); + $album->setIpCreate("::1"); + $album->setActive(true); + $album->setIpCreate("::1"); + $album->setAsDraft(false); + + $album->save(); + + + $album->unsetAlbumId(); + $album->setName("Meraih Mimpi 2 "); + $album->setTitle("Meraih Mimpi 2"); + $album->setDescription("Album pertama dengan judul Meraih Mimpi 2"); + $album->save(); + + $album->unsetAlbumId(); + $album->setName("Meraih Mimpi 3 "); + $album->setTitle("Meraih Mimpi 3"); + $album->setDescription("Album pertama dengan judul Meraih Mimpi 3"); + $album->save(); + + $album->unsetAlbumId(); + $album->setName("Meraih Mimpi 4"); + $album->setTitle("Meraih Mimpi 4"); + $album->setDescription("Album pertama dengan judul Meraih Mimpi 4"); + $album->save(); + + + $album2 = new Album(null, $database); + + $res = $album2->findAll(); + foreach($res->getResult() as $row) + { + echo $row."\r\n"; + } +} +catch(Exception $e) +{ + echo $e->getMessage(); +} \ No newline at end of file diff --git a/tutorial.md b/tutorial.md index 8ff4bd48..a3f4d962 100644 --- a/tutorial.md +++ b/tutorial.md @@ -2315,6 +2315,302 @@ This setup ensures that the session save path is securely managed and decrypted ### Conclusion This implementation provides a robust framework for session management in a PHP application, allowing flexibility in storage options (files or Redis) while emphasizing security through encryption. The use of YAML for configuration keeps the setup clean and easily adjustable. By encapsulating session configuration in dedicated classes, you enhance maintainability and security. +## Database + + +### Overview + +`PicoDatabase` is a PHP class designed for simplified database interactions using PDO (PHP Data Objects). It provides methods to connect to a database, execute SQL commands, manage transactions, and fetch results in various formats. This manual outlines how to use the class, its features, and provides examples for reference. + +### Features + +- **Connection Management**: Establish and manage database connections. +- **SQL Execution**: Execute various SQL commands such as INSERT, UPDATE, DELETE, and SELECT. +- **Transaction Handling**: Support for committing and rolling back transactions. +- **Result Fetching**: Fetch results in different formats (array, object, etc.). +- **Callbacks**: Support for custom callback functions for query execution and debugging. +- **Unique ID Generation**: Generate unique identifiers for database records. + +### Installation + +To use the `PicoDatabase` class, ensure you have PHP with PDO support. Include the class file in your project, and you can instantiate it with your database credentials. + +```php +use MagicObject\Database\PicoDatabase; + +// Example credentials setup +$credentials = new SecretObject(); +$db = new PicoDatabase($credentials); +``` + +**Credentials** + +To create database credentials, please see the `SecretObject` section. + +```php +setHost('localhost'); + * $credentials->setUsername('user'); + * $credentials->setPassword('password'); + * ``` + * + * 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 + */ +class PicoDatabaseCredentials extends SecretObject +{ + /** + * Database driver (e.g., 'mysql', 'pgsql'). + * + * @var string + */ + protected $driver = 'mysql'; + + /** + * Database server host. + * + * @EncryptIn + * @DecryptOut + * @var string + */ + protected $host = 'localhost'; + + /** + * Database server port. + * + * @var int + */ + protected $port = 3306; + + /** + * Database username. + * + * @EncryptIn + * @DecryptOut + * @var string + */ + protected $username = ""; + + /** + * Database user password. + * + * @EncryptIn + * @DecryptOut + * @var string + */ + protected $password = ""; + + /** + * Database name. + * + * @EncryptIn + * @DecryptOut + * @var string + */ + protected $databaseName = ""; + + /** + * Database schema (default: 'public'). + * + * @EncryptIn + * @DecryptOut + * @var string + */ + protected $databaseSchema = "public"; + + /** + * Application time zone. + * + * @var string + */ + protected $timeZone = "Asia/Jakarta"; +} +``` + +### Class Methods + +#### Constructor + +```php +public function __construct($databaseCredentials, $callbackExecuteQuery = null, $callbackDebugQuery = null) +``` + + +**Parameters:** + +- `SecretObject $databaseCredentials`: Database credentials object. +- `callable|null $callbackExecuteQuery`: Optional callback for executing modifying queries. + - If the callback has **3 parameters**, it will be: + - `$sqlQuery`: The SQL query being executed. + - `$params`: The parameters used in the SQL query. + - `$type`: The type of query (e.g., `PicoDatabase::QUERY_INSERT`). + - If the callback has **2 parameters**, it will be: + - `$sqlQuery`: The SQL query being executed. + - `$type`: The type of query. +- `callable|null $callbackDebugQuery`: Optional callback for debugging queries. + - If the callback has **2 parameters**, it will be: + - `$sqlQuery`: The SQL query being debugged. + - `$params`: The parameters used in the SQL query. + - If the callback has **1 parameter**, it will be: + - `$sqlQuery`: The SQL query being debugged. + +#### Connecting to the Database + +```php +public function connect($withDatabase = true): bool +``` + + +**Parameters**: + +- `bool $withDatabase`: Whether to select the database upon connection. + +**Returns**: `true` if connection is successful, `false` otherwise. + +#### Disconnecting from the Database + +```php +public function disconnect(): self +``` + +**Returns**: Current instance for method chaining. + +#### Query Execution + +```php +public function query($sql, $params = null) +``` + + +**Parameters**: +- `string $sql`: SQL command to be executed. +- `array|null $params`: Optional parameters for the SQL query. +**Returns**: PDOStatement object or `false` on failure. + +##### Fetch a Single Result + +```php +public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null) +``` + +**Parameters**: +- `string $sql`: SQL command. +- `int $tentativeType`: Fetch mode (default is `PDO::FETCH_ASSOC`). +- `mixed $defaultValue`: Default value if no results found. +- `array|null $params`: Optional parameters. +**Returns**: Fetched result or default value. + +#### Fetch All Results + +```php +public function fetchAll($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null) +``` + +Similar to fetch, but returns all matching results as an array. + +### Transaction Management + +#### Commit Transaction + +```php +public function commit(): bool +``` + +**Returns:** true if successful. + +#### Rollback Transaction + +```php +public function rollback(): bool +``` + +**Returns:** true if successful. + +#### Unique ID Generation + +```php +public function generateNewId(): string +``` + +**Returns:** A unique 20-byte ID. + +#### Last Inserted ID + +```php +public function lastInsertId($name = null): string|false +``` + +**Parameters:** + +- string|null $name: Sequence name (for PostgreSQL). + +**Returns:** The last inserted ID or false on error. + +### Connection Status + +#### Check Connection + +```php +public function isConnected(): bool +``` + +### Example Usage + +#### Connecting and Fetching Data + +```php +// Instantiate PicoDatabase +$db = new PicoDatabase($credentials); + +// Connect to the database +if ($db->connect()) { + // Fetch a user by ID + $user = $db->fetch("SELECT * FROM users WHERE id = ?", [1]); + print_r($user); + + // Disconnect + $db->disconnect(); +} +``` + +#### Executing a Transaction + +```php +$db->connect(); +$db->setAudoCommit(false); // Disable autocommit + +try { + $db->executeInsert("INSERT INTO users (name) VALUES (?)", ['John Doe']); + $db->commit(); // Commit the transaction +} catch (Exception $e) { + $db->rollback(); // Rollback on error +} +``` + +### Conclusion + +`PicoDatabase` is a robust class for managing database operations in PHP applications. By following the examples and method descriptions provided in this manual, you can effectively utilize its features for your database interactions. For further assistance, refer to the source code and documentation available at [MagicObject GitHub](https://github.com/Planetbiru/MagicObject). ## Entity Entity is class to access database. Entity is derived from MagicObject. Some annotations required to activated all entity features. @@ -11114,6 +11410,441 @@ where song.active = true This way, `$active` will be escaped before being executed by the database. You don't need to escape it first. +## PicoSqlite + +### Overview + +`PicoSqlite` is a PHP class designed for simplified interactions with SQLite databases using PDO (PHP Data Objects). This class extends `PicoDatabase` and provides methods for connecting to the database, creating tables, and performing basic CRUD (Create, Read, Update, Delete) operations. + +Here are some advantages of using SQLite: + +1. **Lightweight**: SQLite is a serverless, self-contained database engine that requires minimal setup and uses a single file to store the entire database, making it easy to manage and deploy. + +2. **Easy to Use**: Its simple API allows for straightforward integration with PHP, enabling quick database operations without the overhead of complex configurations. + +3. **No Server Required**: Unlike other database systems, SQLite does not require a separate server process, which simplifies the development process and reduces resource usage. + +4. **Cross-Platform**: SQLite databases are cross-platform and can be used on various operating systems without compatibility issues. + +5. **Fast Performance**: For smaller databases and applications, SQLite often outperforms more complex database systems, thanks to its lightweight architecture. + +6. **ACID Compliance**: SQLite provides full ACID (Atomicity, Consistency, Isolation, Durability) compliance, ensuring reliable transactions and data integrity. + +7. **Rich Feature Set**: Despite being lightweight, SQLite supports many advanced features like transactions, triggers, views, and complex queries. + +8. **No Configuration Required**: SQLite is easy to set up and requires no configuration, allowing developers to focus on building applications rather than managing the database server. + +9. **Great for Prototyping**: Its simplicity makes it ideal for prototyping applications before moving to a more complex database system. + +10. **Good for Read-Heavy Workloads**: SQLite performs well in read-heavy scenarios, making it suitable for applications where data is frequently read but rarely modified. + + +These features make SQLite a popular choice for many PHP applications, especially for smaller projects or for applications that need a lightweight database solution. + +SQLite has a slightly different method for determining whether a SELECT query returns matching rows. While other databases often utilize the rowCount() method to get this information, SQLite does not support this functionality in the same way. To address this limitation, MagicObject has implemented a solution that seamlessly handles row checking for users. With MagicObject, developers can interact with SQLite without needing to worry about the intricacies of row counting. This allows for a more intuitive and efficient experience when working with SQLite, enabling users to focus on their application logic rather than the underlying database mechanics. + +### Requirements + +- PHP 7.0 or higher +- PDO extension enabled + +### Installation + +To use the `PicoSqlite` class, include it in your PHP project. Ensure that your project structure allows for proper namespace loading. + +```php +use MagicObject\Database\PicoSqlite; + +// Example usage: +$db = new PicoSqlite('path/to/database.sqlite'); +``` + +### Class Methods + +#### Constructor + +```php +public function __construct($databaseFilePath) +``` + +**Parameters:** + + string $databaseFilePath: The path to the SQLite database file. + +**Throws:** PDOException if the connection fails. + +**Usage Example:** + +```php +$sqlite = new PicoSqlite('path/to/database.sqlite'); +``` + +#### Connecting to the Database + +```php +public function connect($withDatabase = true) +``` + +**Parameters:** +- bool $withDatabase: Optional. Default is true. Indicates whether to select the database when connecting. + +**Returns:** `bool` - True if the connection is successful, false otherwise. + +**Usage Example:** + +```php +if ($sqlite->connect()) { + echo "Connected to database successfully."; +} else { + echo "Failed to connect."; +} +``` + +#### Check Table + +```php +public function tableExists($tableName) : bool +``` + +**Parameters:** + +- string $tableName: The name of the table to check. + +**Returns:** `bool` - True if the table exists, false otherwise. + +**Usage Example:** + +```php +if ($sqlite->tableExists('users')) { + echo "Table exists."; +} else { + echo "Table does not exist."; +} +``` + +#### Create Table + +```php +public function createTable($tableName, $columns) : int|false +``` + +**Parameters:** + +- string $tableName: The name of the table to create. +- string[] $columns: An array of columns in the format 'column_name TYPE'. + +**Returns:** `int|false` - Number of rows affected or false on failure. + +**Usage Example:** + +```php +$columns = ['id INTEGER PRIMARY KEY', 'name TEXT', 'email TEXT']; +$sqlite->createTable('users', $columns); +``` + +#### Insert + +```php +public function insert($tableName, $data) : array +``` + +**Parameters:** + +- string $tableName: The name of the table to insert into. +- array $data: An associative array of column names and values to insert. + +**Returns:** `bool` - True on success, false on failure. + +**Usage Example:** + +```php +$data = ['name' => 'John Doe', 'email' => 'john@example.com']; +$sqlite->insert('users', $data); +``` + +```php +public function update($tableName, $data, $conditions) : bool +``` + +**Parameters:** + +- string $tableName: The name of the table to update. + array $data: An associative array of column names and new values. +- array $conditions: An associative array of conditions for the WHERE clause. + +**Returns:** `bool` - True on success, false on failure. + +**Usage Example:** + +```php +$data = ['name' => 'John Smith']; +$conditions = ['id' => 1]; +$sqlite->update('users', $data, $conditions); +``` + +#### Delete + +```php +public function delete($tableName, $conditions) : bool +``` + +**Parameters:** + +- string $tableName: The name of the table to delete from. +- array $conditions: An associative array of conditions for the WHERE clause. + +**Returns:** `bool` - True on success, false on failure. + +**Usage Example:** + +```php +$conditions = ['id' => 1]; +$sqlite->delete('users', $conditions); +``` + +### Entity with PicoSqlite + +```php +connect(); + + $album = new Album(null, $database); + + // create table if not exists + $util = new PicoDatabaseUtilSqlite(); + $tableStructure = $util->showCreateTable($album, true); + $database->query($tableStructure); + + $album->setAlbumId("1235"); + $album->setName("Meraih Mimpi 2 "); + $album->setTitle("Meraih Mimpi 2"); + $album->setDescription("Album pertama dengan judul Meraih Mimpi 2"); + $album->setProducerId("5678"); + $album->setReleaseDate("2024-09-09"); + $album->setNumberOfSong(10); + $album->duration(185*60); + $album->setSortOrder(1); + $album->setIpCreate("::1"); + $album->setIpEdit("::1"); + $album->setTimeCreate(date("Y-m-d H:i:s")); + $album->setTimeEdit(date("Y-m-d H:i:s")); + $album->setAdminCreate("1"); + $album->setAdminEdit("1"); + $album->setIpCreate("::1"); + $album->setActive(true); + $album->setAsDraft(false); + echo $album."\r\n--------\r\n"; + $album->save(); + + $album2 = new Album(null, $database); + + $res = $album2->findAll(); + foreach($res->getResult() as $row) + { + echo $row."\r\n"; + } +} +catch(Exception $e) +{ + echo $e->getMessage(); +} +``` + +### Error Handling + +If an operation fails, `PicoSqlite` may throw exceptions or return false. It is recommended to implement error handling using try-catch blocks to catch `PDOException` for connection-related issues. + +### Conclusion + +`PicoSqlite` provides an efficient way to interact with SQLite databases. Its straightforward API allows developers to perform common database operations with minimal code. For more advanced database operations, consider extending the class or using additional PDO features. ## Upload File Uploading lots of files with arrays is difficult for some developers, especially novice developers. There is a significant difference between uploading a single file and multiple files.