From ef05bac631bb125fb4c4bf8d0a7692f0351e9278 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 06:45:44 +0700 Subject: [PATCH 01/55] Replace DATABASE_TYPE_POSTGRESQL with DATABASE_TYPE_PGSQL --- src/Database/PicoDatabase.php | 4 ++-- src/Database/PicoDatabaseQueryBuilder.php | 4 ++-- src/Database/PicoDatabaseType.php | 7 +++++++ src/Generator/PicoDatabaseDump.php | 6 +++--- src/Util/Database/PicoDatabaseUtilPostgreSql.php | 2 +- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php index 47dd40bb..19343849 100644 --- a/src/Database/PicoDatabase.php +++ b/src/Database/PicoDatabase.php @@ -189,7 +189,7 @@ private function connectRDMS($withDatabase = true) throw new InvalidDatabaseConfiguration("Database username may not be empty. Please check your database configuration!"); } $initialQueries = "SET time_zone = '$timeZoneOffset';"; - if ($this->getDatabaseType() == PicoDatabaseType::DATABASE_TYPE_POSTGRESQL && + if ($this->getDatabaseType() == PicoDatabaseType::DATABASE_TYPE_PGSQL && $this->databaseCredentials->getDatabaseSchema() != null && $this->databaseCredentials->getDatabaseSchema() != "") { $initialQueries .= "SET search_path TO " . $this->databaseCredentials->getDatabaseSchema(); @@ -229,7 +229,7 @@ private function getDbType($databaseType) // NOSONAR } else if(stripos($databaseType, 'postgre') !== false || stripos($databaseType, 'pgsql') !== false) { - return PicoDatabaseType::DATABASE_TYPE_POSTGRESQL; + return PicoDatabaseType::DATABASE_TYPE_PGSQL; } else if(stripos($databaseType, 'maria') !== false) { diff --git a/src/Database/PicoDatabaseQueryBuilder.php b/src/Database/PicoDatabaseQueryBuilder.php index f30cea85..ca15702a 100644 --- a/src/Database/PicoDatabaseQueryBuilder.php +++ b/src/Database/PicoDatabaseQueryBuilder.php @@ -98,7 +98,7 @@ public function isMySql() */ public function isPgSql() { - return strcasecmp($this->databaseType, PicoDatabaseType::DATABASE_TYPE_POSTGRESQL) == 0; + return strcasecmp($this->databaseType, PicoDatabaseType::DATABASE_TYPE_PGSQL) == 0; } /** @@ -638,7 +638,7 @@ public function escapeSQL($query) stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_SQLITE) !== false) { return str_replace(["\r", "\n"], ["\\r", "\\n"], addslashes($query)); } - if (stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_POSTGRESQL) !== false) { + if (stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_PGSQL) !== false) { return str_replace(["\r", "\n"], ["\\r", "\\n"], $this->replaceQuote($query)); } return $query; diff --git a/src/Database/PicoDatabaseType.php b/src/Database/PicoDatabaseType.php index 06bbb79a..48348ad1 100644 --- a/src/Database/PicoDatabaseType.php +++ b/src/Database/PicoDatabaseType.php @@ -42,6 +42,13 @@ class PicoDatabaseType */ const DATABASE_TYPE_POSTGRESQL = "postgresql"; + /** + * Constant for PostgreSQL database type. + * + * @var string + */ + const DATABASE_TYPE_PGSQL = "pgsql"; + /** * Constant for SQLite database type. * diff --git a/src/Generator/PicoDatabaseDump.php b/src/Generator/PicoDatabaseDump.php index 15374cd6..112c4733 100644 --- a/src/Generator/PicoDatabaseDump.php +++ b/src/Generator/PicoDatabaseDump.php @@ -66,7 +66,7 @@ public function dumpStructure($entity, $databaseType, $createIfNotExists = false $tool = new PicoDatabaseUtilMySql(); return $tool->dumpStructure($tableInfo, $picoTableName, $createIfNotExists, $dropIfExists, $engine, $charset); } - else if($databaseType == PicoDatabaseType::DATABASE_TYPE_POSTGRESQL) + else if($databaseType == PicoDatabaseType::DATABASE_TYPE_PGSQL) { $tool = new PicoDatabaseUtilPostgreSql(); return $tool->dumpStructure($tableInfo, $picoTableName, $createIfNotExists, $dropIfExists, $engine, $charset); @@ -92,7 +92,7 @@ public function dumpStructureTable($tableInfo, $databaseType, $createIfNotExists $tool = new PicoDatabaseUtilMySql(); return $tool->dumpStructure($tableInfo, $picoTableName, $createIfNotExists, $dropIfExists, $engine, $charset); } - else if($databaseType == PicoDatabaseType::DATABASE_TYPE_POSTGRESQL) + else if($databaseType == PicoDatabaseType::DATABASE_TYPE_PGSQL) { $tool = new PicoDatabaseUtilPostgreSql(); return $tool->dumpStructure($tableInfo, $picoTableName, $createIfNotExists, $dropIfExists, $engine, $charset); @@ -370,7 +370,7 @@ private function addAutoIncrement($queryAlter, $tableInfo, $tableName, $createdC $query = $this->updateQueryAlterTableNullable($query, $entityColumn); $query = $this->updateQueryAlterTableDefaultValue($query, $entityColumn); - if ($databaseType == PicoDatabaseType::DATABASE_TYPE_POSTGRESQL) { + if ($databaseType == PicoDatabaseType::DATABASE_TYPE_PGSQL) { $columnName = $entityColumn['name']; $sequenceName = $tableName . "_" . $columnName; $queries[] = ""; diff --git a/src/Util/Database/PicoDatabaseUtilPostgreSql.php b/src/Util/Database/PicoDatabaseUtilPostgreSql.php index 5d7ee674..2054ba5a 100644 --- a/src/Util/Database/PicoDatabaseUtilPostgreSql.php +++ b/src/Util/Database/PicoDatabaseUtilPostgreSql.php @@ -318,7 +318,7 @@ public function dumpRecord($columns, $tableName, $record) } } - $queryBuilder = new PicoDatabaseQueryBuilder(PicoDatabaseType::DATABASE_TYPE_POSTGRESQL); + $queryBuilder = new PicoDatabaseQueryBuilder(PicoDatabaseType::DATABASE_TYPE_PGSQL); $queryBuilder->newQuery() ->insert() ->into($tableName) From 14f7ff3017c5c38b017e9729b509784c74220b49 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 07:29:01 +0700 Subject: [PATCH 02/55] MagicObject version 2.7 --- README.md | 20 +++++ manual/includes/_download-file.md | 88 ++++++++++++++++++++++ manual/includes/_with-pdo.md | 30 ++++++++ manual/index.html | 117 ++++++++++++++++++++++++++--- manual/index.html.md | 2 + src/Database/PicoDatabase.php | 21 ++++++ src/MagicObject.php | 33 ++++++-- tutorial.md | 120 ++++++++++++++++++++++++++++++ 8 files changed, 413 insertions(+), 18 deletions(-) create mode 100644 manual/includes/_download-file.md create mode 100644 manual/includes/_with-pdo.md diff --git a/README.md b/README.md index b0156454..79b106e1 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,26 @@ Additionally, MagicObject 2.1 allows users to parse table structures directly fr These utilities not only enhance efficiency but also provide a robust foundation for database development, allowing users to focus on building applications rather than wrestling with database compatibility issues. With MagicObject 2.1, database management becomes more intuitive and accessible, empowering developers to harness the full potential of their data. + +# **Introducing PDO Support in MagicObject 2.7** + +## **Overview** + +With the release of **MagicObject 2.7**, a significant update has been introduced to allow users to leverage **PDO** (PHP Data Objects) for database connections. In previous versions, **MagicObject** required the use of **PicoDatabase**, its custom database handling class. However, recognizing that many developers are accustomed to establishing database connections via traditional PDO, this new version introduces flexibility by allowing PDO connections to be passed directly to the **MagicObject** constructor. + +This update aims to bridge the gap between traditional PDO-based database management and the advanced features provided by **MagicObject**, thus enhancing compatibility while retaining all the powerful functionality of the framework. + +## **Why PDO Support?** + +The decision to support **PDO** was made to accommodate users who have already established database connections in their applications using PDO, instead of relying on **PicoDatabase** from the start. By supporting PDO, **MagicObject** allows users to continue working with their preferred method of connecting to the database while still benefiting from the full range of features and utilities **MagicObject** offers. + +While PDO is now an option for initializing **MagicObject**, it is used only in the constructor. Once the object is initialized, **MagicObject** continues to use **PicoDatabase** for all subsequent database interactions, ensuring that users can still benefit from **PicoDatabase**'s advanced features like automatic query building, database abstraction, and optimized query execution. + +## **How PDO Support Works** + +In **MagicObject 2.7**, when you pass a **PDO** connection object to the constructor, it is automatically converted into a **PicoDatabase** instance using the `PicoDatabase::fromPdo()` static method. This ensures that even though PDO is used to establish the initial connection, the object will still operate using **PicoDatabase** for all subsequent database operations. The constructor of **MagicObject** ensures that the database connection is properly initialized and the type of database is correctly detected based on the PDO driver. + + # Tutorial Tutorial is provided here https://github.com/Planetbiru/MagicObject/blob/main/tutorial.md diff --git a/manual/includes/_download-file.md b/manual/includes/_download-file.md new file mode 100644 index 00000000..a7b18b9b --- /dev/null +++ b/manual/includes/_download-file.md @@ -0,0 +1,88 @@ + +## Resumable File Download using PicoDownloadFile + +### Namespace: + +`MagicObject\File` + +### Description: + +The `PicoDownloadFile` class is designed to facilitate efficient file downloading in PHP, supporting **partial content** (range requests) for large files. It ensures that requested files exist, handles errors gracefully, and enables downloading in chunks to minimize server load and bandwidth consumption, particularly for large files. + +The class supports the following: + +- Verifying the existence of the file. +- Handling byte-range requests for resuming downloads. +- Sending appropriate HTTP headers to manage the download. +- Streaming the file to the client in manageable chunks (default size: 8 KB). +- Returning relevant HTTP status codes and error messages. + +This class is ideal for scenarios where large files need to be served to clients and you want to offer functionality like resuming interrupted downloads. + + +### Constructor: `__construct($filepath, $filename = null)` + +**Parameters**: + +- `$filepath` (string): The full path to the file that should be downloaded. +- `$filename` (string|null, optional): The name of the file for download. If not provided, the filename is extracted from the `filepath` using `basename()`. + +**Description**: Initializes the `PicoDownloadFile` object with the path of the file to be downloaded and an optional filename for the download response. If the filename is not specified, the base name of the file is used. + +**Example**: + +```php +$file = new PicoDownloadFile("/path/to/large-file.zip", "downloaded-file.zip"); +``` + +### Method: `download($exit = false)` + +**Parameters**: + +- `$exit` (bool, optional): Whether to terminate the script after sending the file. Default is `false`. + +**Returns**: + +- `bool`: Returns `true` if the entire file was successfully sent, `false` if only part of the file was sent (due to range requests). + +**Description**: This method is responsible for initiating the file download process. It performs the following: + +1. Verifies the existence of the file. +2. Handles byte-range requests for partial downloads (useful for resuming interrupted downloads). +3. Sends the appropriate HTTP headers for the file download. +4. Streams the file to the client in chunks of 8 KB (by default). + +If `$exit` is set to `true`, the script will terminate after the file is sent. + +**Example 1** + +```php +download(true); // Initiate download and terminate the script after sending +``` + +**Example 2** + +```php +download(false); // Initiate download without terminate the script after sending +if($finished && file_exists($path)) +{ + unlink($path); // Delete file when finish +} +``` + +### Error Handling: + +- **404 - File Not Found**: If the file does not exist at the specified path, a 404 error is returned. +- **416 - Range Not Satisfiable**: If an invalid byte range is requested (e.g., the start byte is larger than the end byte), a 416 error is returned. +- **500 - Internal Server Error**: If there is an issue opening the file for reading (e.g., permissions issues), a 500 error is returned. + diff --git a/manual/includes/_with-pdo.md b/manual/includes/_with-pdo.md new file mode 100644 index 00000000..cef2c4f2 --- /dev/null +++ b/manual/includes/_with-pdo.md @@ -0,0 +1,30 @@ + +## MagicObject with PDO + +### Overview + +With the release of **MagicObject 2.7**, a significant update has been introduced to allow users to leverage **PDO** (PHP Data Objects) for database connections. In previous versions, **MagicObject** required the use of **PicoDatabase**, its custom database handling class. However, recognizing that many developers are accustomed to establishing database connections via traditional PDO, this new version introduces flexibility by allowing PDO connections to be passed directly to the **MagicObject** constructor. + +This update aims to bridge the gap between traditional PDO-based database management and the advanced features provided by **MagicObject**, thus enhancing compatibility while retaining all the powerful functionality of the framework. + +### Why PDO Support? + +The decision to support **PDO** was made to accommodate users who have already established database connections in their applications using PDO, instead of relying on **PicoDatabase** from the start. By supporting PDO, **MagicObject** allows users to continue working with their preferred method of connecting to the database while still benefiting from the full range of features and utilities **MagicObject** offers. + +While PDO is now an option for initializing **MagicObject**, it is used only in the constructor. Once the object is initialized, **MagicObject** continues to use **PicoDatabase** for all subsequent database interactions, ensuring that users can still benefit from **PicoDatabase**'s advanced features like automatic query building, database abstraction, and optimized query execution. + +### How PDO Support Works + +In **MagicObject 2.7**, when you pass a **PDO** connection object to the constructor, it is automatically converted into a **PicoDatabase** instance using the `PicoDatabase::fromPdo()` static method. This ensures that even though PDO is used to establish the initial connection, the object will still operate using **PicoDatabase** for all subsequent database operations. The constructor of **MagicObject** ensures that the database connection is properly initialized and the type of database is correctly detected based on the PDO driver. + +### Benefits of PDO Support in MagicObject 2.7 + +- **Compatibility**: This change makes **MagicObject** more compatible with existing applications that are already using PDO for database connections. Developers can continue to use PDO for initializing connections while taking advantage of **PicoDatabase**'s advanced database features for the rest of the application. + +- **Flexibility**: Developers now have the flexibility to choose between traditional PDO connections and **PicoDatabase**, depending on their needs. This is especially useful for applications transitioning to **MagicObject** but needing to maintain compatibility with existing database handling code. + +- **Ease of Transition**: By supporting PDO in the constructor, **MagicObject** makes it easier for developers to gradually adopt its features without the need to refactor existing database handling code. + +### Conclusion + +Version 2.7 of **MagicObject** introduces an important enhancement by allowing PDO connections to be used alongside **PicoDatabase**. This update provides greater flexibility for developers, allowing them to work with traditional PDO connections if they choose, while still benefiting from the advanced features of **MagicObject** for database interactions. This change aligns with the goal of making **MagicObject** more accessible to a wider range of developers, whether they are just starting with **MagicObject** or are looking to transition from an existing PDO-based application. diff --git a/manual/index.html b/manual/index.html index 194982d5..f2fa5162 100644 --- a/manual/index.html +++ b/manual/index.html @@ -2345,6 +2345,32 @@

Conclusion

+

MagicObject with PDO

+

Overview

+

With the release of MagicObject 2.7, a significant update has been introduced to allow users to leverage PDO (PHP Data Objects) for database connections. In previous versions, MagicObject required the use of PicoDatabase, its custom database handling class. However, recognizing that many developers are accustomed to establishing database connections via traditional PDO, this new version introduces flexibility by allowing PDO connections to be passed directly to the MagicObject constructor.

+

This update aims to bridge the gap between traditional PDO-based database management and the advanced features provided by MagicObject, thus enhancing compatibility while retaining all the powerful functionality of the framework.

+

Why PDO Support?

+

The decision to support PDO was made to accommodate users who have already established database connections in their applications using PDO, instead of relying on PicoDatabase from the start. By supporting PDO, MagicObject allows users to continue working with their preferred method of connecting to the database while still benefiting from the full range of features and utilities MagicObject offers.

+

While PDO is now an option for initializing MagicObject, it is used only in the constructor. Once the object is initialized, MagicObject continues to use PicoDatabase for all subsequent database interactions, ensuring that users can still benefit from PicoDatabase's advanced features like automatic query building, database abstraction, and optimized query execution.

+

How PDO Support Works

+

In MagicObject 2.7, when you pass a PDO connection object to the constructor, it is automatically converted into a PicoDatabase instance using the PicoDatabase::fromPdo() static method. This ensures that even though PDO is used to establish the initial connection, the object will still operate using PicoDatabase for all subsequent database operations. The constructor of MagicObject ensures that the database connection is properly initialized and the type of database is correctly detected based on the PDO driver.

+

Benefits of PDO Support in MagicObject 2.7

+
    +
  • +

    Compatibility: This change makes MagicObject more compatible with existing applications that are already using PDO for database connections. Developers can continue to use PDO for initializing connections while taking advantage of PicoDatabase's advanced database features for the rest of the application.

    +
  • +
  • +

    Flexibility: Developers now have the flexibility to choose between traditional PDO connections and PicoDatabase, depending on their needs. This is especially useful for applications transitioning to MagicObject but needing to maintain compatibility with existing database handling code.

    +
  • +
  • +

    Ease of Transition: By supporting PDO in the constructor, MagicObject makes it easier for developers to gradually adopt its features without the need to refactor existing database handling code.

    +
  • +
+

Conclusion

+

Version 2.7 of MagicObject introduces an important enhancement by allowing PDO connections to be used alongside PicoDatabase. This update provides greater flexibility for developers, allowing them to work with traditional PDO connections if they choose, while still benefiting from the advanced features of MagicObject for database interactions. This change aligns with the goal of making MagicObject more accessible to a wider range of developers, whether they are just starting with MagicObject or are looking to transition from an existing PDO-based application.

+
+ +

Entity

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

Constructor

@@ -6832,7 +6858,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:

@@ -7325,7 +7351,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.

@@ -7617,7 +7643,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:

@@ -8500,7 +8526,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.

@@ -9130,7 +9156,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

@@ -9295,7 +9321,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

@@ -9809,7 +9835,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
 
@@ -9934,7 +9960,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.

@@ -10201,7 +10227,7 @@

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.

@@ -10568,7 +10594,7 @@

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

Overview

Uploading files can be challenging, especially for novice developers. This guide explains how to manage single and multiple file uploads in PHP, highlighting the differences and providing straightforward examples.

@@ -10635,7 +10661,74 @@

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.

-
+
+

Resumable File Download using PicoDownloadFile

+

Namespace:

+

MagicObject\File

+

Description:

+

The PicoDownloadFile class is designed to facilitate efficient file downloading in PHP, supporting partial content (range requests) for large files. It ensures that requested files exist, handles errors gracefully, and enables downloading in chunks to minimize server load and bandwidth consumption, particularly for large files.

+

The class supports the following:

+
    +
  • Verifying the existence of the file.
  • +
  • Handling byte-range requests for resuming downloads.
  • +
  • Sending appropriate HTTP headers to manage the download.
  • +
  • Streaming the file to the client in manageable chunks (default size: 8 KB).
  • +
  • Returning relevant HTTP status codes and error messages.
  • +
+

This class is ideal for scenarios where large files need to be served to clients and you want to offer functionality like resuming interrupted downloads.

+

Constructor: __construct($filepath, $filename = null)

+

Parameters:

+
    +
  • $filepath (string): The full path to the file that should be downloaded.
  • +
  • $filename (string|null, optional): The name of the file for download. If not provided, the filename is extracted from the filepath using basename().
  • +
+

Description: Initializes the PicoDownloadFile object with the path of the file to be downloaded and an optional filename for the download response. If the filename is not specified, the base name of the file is used.

+

Example:

+
$file = new PicoDownloadFile("/path/to/large-file.zip", "downloaded-file.zip");
+

Method: download($exit = false)

+

Parameters:

+
    +
  • $exit (bool, optional): Whether to terminate the script after sending the file. Default is false.
  • +
+

Returns:

+
    +
  • bool: Returns true if the entire file was successfully sent, false if only part of the file was sent (due to range requests).
  • +
+

Description: This method is responsible for initiating the file download process. It performs the following:

+
    +
  1. Verifies the existence of the file.
  2. +
  3. Handles byte-range requests for partial downloads (useful for resuming interrupted downloads).
  4. +
  5. Sends the appropriate HTTP headers for the file download.
  6. +
  7. Streams the file to the client in chunks of 8 KB (by default).
  8. +
+

If $exit is set to true, the script will terminate after the file is sent.

+

Example 1

+
<?php
+require 'vendor/autoload.php'; // Include the PicoDownloadFile class
+$path = "/path/to/large-file.zip";
+$localName = "downloaded-file.zip";
+$file = new MagicObject\File\PicoDownloadFile($path, $localName);
+$file->download(true); // Initiate download and terminate the script after sending
+

Example 2

+
<?php
+require 'vendor/autoload.php'; // Include the PicoDownloadFile class
+$path = "/path/to/large-file.zip";
+$localName = "downloaded-file.zip";
+$file = new MagicObject\File\PicoDownloadFile($path, $localName);
+$finished = $file->download(false); // Initiate download without terminate the script after sending
+if($finished && file_exists($path))
+{
+    unlink($path); // Delete file when finish
+}
+

Error Handling:

+
    +
  • 404 - File Not Found: If the file does not exist at the specified path, a 404 error is returned.
  • +
  • 416 - Range Not Satisfiable: If an invalid byte range is requested (e.g., the start byte is larger than the end byte), a 416 error is returned.
  • +
  • 500 - Internal Server Error: If there is an issue opening the file for reading (e.g., permissions issues), a 500 error is returned.
  • +
+
+ +

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.

@@ -10855,7 +10948,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 c4528002..ab8a49fe 100644 --- a/manual/index.html.md +++ b/manual/index.html.md @@ -26,6 +26,7 @@ includes: - input - session - database + - with-pdo - entity - specification - pagable @@ -37,6 +38,7 @@ includes: - database-query-builder - sqlite - upload-file + - download-file - data-table - database-migration --- diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php index 19343849..7bd0beb2 100644 --- a/src/Database/PicoDatabase.php +++ b/src/Database/PicoDatabase.php @@ -94,6 +94,27 @@ class PicoDatabase //NOSONAR */ protected $callbackDebugQuery = null; + /** + * Creates a PicoDatabase instance from an existing PDO connection. + * + * This static method accepts a PDO connection object, initializes a new + * PicoDatabase instance, and sets up the database connection and type. + * It also marks the database as connected and returns the configured + * PicoDatabase object. + * + * @param PDO $pdo The PDO connection object representing an active connection to the database. + * @return PicoDatabase Returns a new instance of the PicoDatabase class, + * with the PDO connection and database type set. + */ + public static function fromPdo($pdo) + { + $database = new self(new SecretObject()); + $database->databaseConnection = $pdo; + $database->databaseType = $database->getDbType($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)); + $database->connected = true; + return $database; + } + /** * Constructor to initialize the PicoDatabase object. * diff --git a/src/MagicObject.php b/src/MagicObject.php index 3c014664..4dec15ec 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -144,10 +144,24 @@ public function nullPropertyList() /** * Constructor. * - * Initializes the object with provided data and database connection. - * - * @param self|array|stdClass|object|null $data Initial data to populate the object. - * @param PicoDatabase|null $database Database connection instance. + * Initializes the object with the provided data and optionally connects to a database. + * The constructor can accept different types of data to populate the object and can + * also accept a PDO connection or a PicoDatabase instance to set up the database connection. + * + * @param self|array|stdClass|object|null $data Initial data to populate the object. This can be: + * - `self`: An instance of the same class to clone data. + * - `array`: An associative array of data, which will be camel-cased. + * - `stdClass`: A standard object to populate the properties. + * - `object`: A generic object to populate the properties. + * - `null`: No data, leaving the object empty. + * + * @param PicoDatabase|PDO|null $database A database connection instance, either: + * - `PicoDatabase`: An already instantiated PicoDatabase object. + * - `PDO`: A PDO connection object, which will be converted into a PicoDatabase instance using `PicoDatabase::fromPdo()`. + * - `null`: No database connection. + * + * @throws InvalidAnnotationException If the annotations are invalid or cannot be parsed. + * @throws InvalidQueryInputException If an error occurs while parsing the key-value pair annotations. */ public function __construct($data = null, $database = null) { @@ -173,9 +187,16 @@ public function __construct($data = null, $database = null) } $this->loadData($data); } - if($database != null && $database instanceof PicoDatabase) + if($database != null) { - $this->_database = $database; + if($database instanceof PicoDatabase) + { + $this->_database = $database; + } + else if($database instanceof PDO) + { + $this->_database = PicoDatabase::fromPdo($database); + } } } diff --git a/tutorial.md b/tutorial.md index 70a93c92..a89437fa 100644 --- a/tutorial.md +++ b/tutorial.md @@ -2753,6 +2753,37 @@ try { ### 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). + +## MagicObject with PDO + +### Overview + +With the release of **MagicObject 2.7**, a significant update has been introduced to allow users to leverage **PDO** (PHP Data Objects) for database connections. In previous versions, **MagicObject** required the use of **PicoDatabase**, its custom database handling class. However, recognizing that many developers are accustomed to establishing database connections via traditional PDO, this new version introduces flexibility by allowing PDO connections to be passed directly to the **MagicObject** constructor. + +This update aims to bridge the gap between traditional PDO-based database management and the advanced features provided by **MagicObject**, thus enhancing compatibility while retaining all the powerful functionality of the framework. + +### Why PDO Support? + +The decision to support **PDO** was made to accommodate users who have already established database connections in their applications using PDO, instead of relying on **PicoDatabase** from the start. By supporting PDO, **MagicObject** allows users to continue working with their preferred method of connecting to the database while still benefiting from the full range of features and utilities **MagicObject** offers. + +While PDO is now an option for initializing **MagicObject**, it is used only in the constructor. Once the object is initialized, **MagicObject** continues to use **PicoDatabase** for all subsequent database interactions, ensuring that users can still benefit from **PicoDatabase**'s advanced features like automatic query building, database abstraction, and optimized query execution. + +### How PDO Support Works + +In **MagicObject 2.7**, when you pass a **PDO** connection object to the constructor, it is automatically converted into a **PicoDatabase** instance using the `PicoDatabase::fromPdo()` static method. This ensures that even though PDO is used to establish the initial connection, the object will still operate using **PicoDatabase** for all subsequent database operations. The constructor of **MagicObject** ensures that the database connection is properly initialized and the type of database is correctly detected based on the PDO driver. + +### Benefits of PDO Support in MagicObject 2.7 + +- **Compatibility**: This change makes **MagicObject** more compatible with existing applications that are already using PDO for database connections. Developers can continue to use PDO for initializing connections while taking advantage of **PicoDatabase**'s advanced database features for the rest of the application. + +- **Flexibility**: Developers now have the flexibility to choose between traditional PDO connections and **PicoDatabase**, depending on their needs. This is especially useful for applications transitioning to **MagicObject** but needing to maintain compatibility with existing database handling code. + +- **Ease of Transition**: By supporting PDO in the constructor, **MagicObject** makes it easier for developers to gradually adopt its features without the need to refactor existing database handling code. + +### Conclusion + +Version 2.7 of **MagicObject** introduces an important enhancement by allowing PDO connections to be used alongside **PicoDatabase**. This update provides greater flexibility for developers, allowing them to work with traditional PDO connections if they choose, while still benefiting from the advanced features of **MagicObject** for database interactions. This change aligns with the goal of making **MagicObject** more accessible to a wider range of developers, whether they are just starting with **MagicObject** or are looking to transition from an existing PDO-based application. + ## Entity Entity is class to access database. Entity is derived from MagicObject. Some annotations required to activated all entity features. @@ -12075,6 +12106,95 @@ else ### 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. + +## Resumable File Download using PicoDownloadFile + +### Namespace: + +`MagicObject\File` + +### Description: + +The `PicoDownloadFile` class is designed to facilitate efficient file downloading in PHP, supporting **partial content** (range requests) for large files. It ensures that requested files exist, handles errors gracefully, and enables downloading in chunks to minimize server load and bandwidth consumption, particularly for large files. + +The class supports the following: + +- Verifying the existence of the file. +- Handling byte-range requests for resuming downloads. +- Sending appropriate HTTP headers to manage the download. +- Streaming the file to the client in manageable chunks (default size: 8 KB). +- Returning relevant HTTP status codes and error messages. + +This class is ideal for scenarios where large files need to be served to clients and you want to offer functionality like resuming interrupted downloads. + + +### Constructor: `__construct($filepath, $filename = null)` + +**Parameters**: + +- `$filepath` (string): The full path to the file that should be downloaded. +- `$filename` (string|null, optional): The name of the file for download. If not provided, the filename is extracted from the `filepath` using `basename()`. + +**Description**: Initializes the `PicoDownloadFile` object with the path of the file to be downloaded and an optional filename for the download response. If the filename is not specified, the base name of the file is used. + +**Example**: + +```php +$file = new PicoDownloadFile("/path/to/large-file.zip", "downloaded-file.zip"); +``` + +### Method: `download($exit = false)` + +**Parameters**: + +- `$exit` (bool, optional): Whether to terminate the script after sending the file. Default is `false`. + +**Returns**: + +- `bool`: Returns `true` if the entire file was successfully sent, `false` if only part of the file was sent (due to range requests). + +**Description**: This method is responsible for initiating the file download process. It performs the following: + +1. Verifies the existence of the file. +2. Handles byte-range requests for partial downloads (useful for resuming interrupted downloads). +3. Sends the appropriate HTTP headers for the file download. +4. Streams the file to the client in chunks of 8 KB (by default). + +If `$exit` is set to `true`, the script will terminate after the file is sent. + +**Example 1** + +```php +download(true); // Initiate download and terminate the script after sending +``` + +**Example 2** + +```php +download(false); // Initiate download without terminate the script after sending +if($finished && file_exists($path)) +{ + unlink($path); // Delete file when finish +} +``` + +### Error Handling: + +- **404 - File Not Found**: If the file does not exist at the specified path, a 404 error is returned. +- **416 - Range Not Satisfiable**: If an invalid byte range is requested (e.g., the start byte is larger than the end byte), a 416 error is returned. +- **500 - Internal Server Error**: If there is an issue opening the file for reading (e.g., permissions issues), a 500 error is returned. + + ## 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. From f1e4ea56866f8ccea8bdfb882bcf729dfda07f6e Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 07:31:15 +0700 Subject: [PATCH 03/55] Update title --- manual/includes/_upload-file.md | 2 +- manual/index.html | 2 +- tutorial.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manual/includes/_upload-file.md b/manual/includes/_upload-file.md index d51a14bb..ed2b570f 100644 --- a/manual/includes/_upload-file.md +++ b/manual/includes/_upload-file.md @@ -1,4 +1,4 @@ -## Upload File +## File Upload ### Overview diff --git a/manual/index.html b/manual/index.html index f2fa5162..65f588e7 100644 --- a/manual/index.html +++ b/manual/index.html @@ -10595,7 +10595,7 @@

Conclusion

-

Upload File

+

File Upload

Overview

Uploading files can be challenging, especially for novice developers. This guide explains how to manage single and multiple file uploads in PHP, highlighting the differences and providing straightforward examples.

Key Features

diff --git a/tutorial.md b/tutorial.md index a89437fa..91dc616a 100644 --- a/tutorial.md +++ b/tutorial.md @@ -12018,7 +12018,7 @@ If an operation fails, `PicoSqlite` may throw exceptions or return false. It is ### 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 +## File Upload ### Overview From b4425d3bb09c2879e6afe364d53206d084e38d68 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 07:32:14 +0700 Subject: [PATCH 04/55] Update title --- manual/includes/_download-file.md | 2 +- manual/index.html | 2 +- tutorial.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manual/includes/_download-file.md b/manual/includes/_download-file.md index a7b18b9b..2c3cb3c9 100644 --- a/manual/includes/_download-file.md +++ b/manual/includes/_download-file.md @@ -1,5 +1,5 @@ -## Resumable File Download using PicoDownloadFile +## Resumable File Download ### Namespace: diff --git a/manual/index.html b/manual/index.html index 65f588e7..c21c646e 100644 --- a/manual/index.html +++ b/manual/index.html @@ -10662,7 +10662,7 @@

Summary

-

Resumable File Download using PicoDownloadFile

+

Resumable File Download

Namespace:

MagicObject\File

Description:

diff --git a/tutorial.md b/tutorial.md index 91dc616a..e1bc981d 100644 --- a/tutorial.md +++ b/tutorial.md @@ -12107,7 +12107,7 @@ else 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. -## Resumable File Download using PicoDownloadFile +## Resumable File Download ### Namespace: From 1ef995e85f4e15a0f8d273db72d89d277c5e6b6e Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 07:33:33 +0700 Subject: [PATCH 05/55] Update title --- manual/includes/_download-file.md | 12 ++++++++++-- manual/index.html | 6 ++++-- tutorial.md | 12 ++++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/manual/includes/_download-file.md b/manual/includes/_download-file.md index 2c3cb3c9..4ca0d691 100644 --- a/manual/includes/_download-file.md +++ b/manual/includes/_download-file.md @@ -20,7 +20,11 @@ The class supports the following: This class is ideal for scenarios where large files need to be served to clients and you want to offer functionality like resuming interrupted downloads. -### Constructor: `__construct($filepath, $filename = null)` +### Constructor + +```php +__construct($filepath, $filename = null) +``` **Parameters**: @@ -35,7 +39,11 @@ This class is ideal for scenarios where large files need to be served to clients $file = new PicoDownloadFile("/path/to/large-file.zip", "downloaded-file.zip"); ``` -### Method: `download($exit = false)` +### Method + +```php +download($exit = false) +``` **Parameters**: diff --git a/manual/index.html b/manual/index.html index c21c646e..6e5a8c7b 100644 --- a/manual/index.html +++ b/manual/index.html @@ -10676,7 +10676,8 @@

Description:

  • Returning relevant HTTP status codes and error messages.
  • This class is ideal for scenarios where large files need to be served to clients and you want to offer functionality like resuming interrupted downloads.

    -

    Constructor: __construct($filepath, $filename = null)

    +

    Constructor

    +
    __construct($filepath, $filename = null)

    Parameters:

    • $filepath (string): The full path to the file that should be downloaded.
    • @@ -10685,7 +10686,8 @@

      Constructor: __construct($filepath, $filename = null)

      Description: Initializes the PicoDownloadFile object with the path of the file to be downloaded and an optional filename for the download response. If the filename is not specified, the base name of the file is used.

      Example:

      $file = new PicoDownloadFile("/path/to/large-file.zip", "downloaded-file.zip");
      -

      Method: download($exit = false)

      +

      Method

      +
      download($exit = false)

      Parameters:

      • $exit (bool, optional): Whether to terminate the script after sending the file. Default is false.
      • diff --git a/tutorial.md b/tutorial.md index e1bc981d..70476e60 100644 --- a/tutorial.md +++ b/tutorial.md @@ -12128,7 +12128,11 @@ The class supports the following: This class is ideal for scenarios where large files need to be served to clients and you want to offer functionality like resuming interrupted downloads. -### Constructor: `__construct($filepath, $filename = null)` +### Constructor + +```php +__construct($filepath, $filename = null) +``` **Parameters**: @@ -12143,7 +12147,11 @@ This class is ideal for scenarios where large files need to be served to clients $file = new PicoDownloadFile("/path/to/large-file.zip", "downloaded-file.zip"); ``` -### Method: `download($exit = false)` +### Method + +```php +download($exit = false) +``` **Parameters**: From e32d711f9e2cb9634b47f542de9361f65edbfd38 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 07:34:59 +0700 Subject: [PATCH 06/55] Update example --- manual/includes/_download-file.md | 4 ++-- manual/index.html | 4 ++-- tutorial.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/manual/includes/_download-file.md b/manual/includes/_download-file.md index 4ca0d691..e5627abc 100644 --- a/manual/includes/_download-file.md +++ b/manual/includes/_download-file.md @@ -69,7 +69,7 @@ If `$exit` is set to `true`, the script will terminate after the file is sent. require 'vendor/autoload.php'; // Include the PicoDownloadFile class $path = "/path/to/large-file.zip"; $localName = "downloaded-file.zip"; -$file = new MagicObject\File\PicoDownloadFile($path, $localName); +$file = new PicoDownloadFile($path, $localName); $file->download(true); // Initiate download and terminate the script after sending ``` @@ -80,7 +80,7 @@ $file->download(true); // Initiate download and terminate the script after sendi require 'vendor/autoload.php'; // Include the PicoDownloadFile class $path = "/path/to/large-file.zip"; $localName = "downloaded-file.zip"; -$file = new MagicObject\File\PicoDownloadFile($path, $localName); +$file = new PicoDownloadFile($path, $localName); $finished = $file->download(false); // Initiate download without terminate the script after sending if($finished && file_exists($path)) { diff --git a/manual/index.html b/manual/index.html index 6e5a8c7b..bd634d02 100644 --- a/manual/index.html +++ b/manual/index.html @@ -10709,14 +10709,14 @@

        Method

        require 'vendor/autoload.php'; // Include the PicoDownloadFile class $path = "/path/to/large-file.zip"; $localName = "downloaded-file.zip"; -$file = new MagicObject\File\PicoDownloadFile($path, $localName); +$file = new PicoDownloadFile($path, $localName); $file->download(true); // Initiate download and terminate the script after sending

        Example 2

        <?php
         require 'vendor/autoload.php'; // Include the PicoDownloadFile class
         $path = "/path/to/large-file.zip";
         $localName = "downloaded-file.zip";
        -$file = new MagicObject\File\PicoDownloadFile($path, $localName);
        +$file = new PicoDownloadFile($path, $localName);
         $finished = $file->download(false); // Initiate download without terminate the script after sending
         if($finished && file_exists($path))
         {
        diff --git a/tutorial.md b/tutorial.md
        index 70476e60..7914301f 100644
        --- a/tutorial.md
        +++ b/tutorial.md
        @@ -12177,7 +12177,7 @@ If `$exit` is set to `true`, the script will terminate after the file is sent.
         require 'vendor/autoload.php'; // Include the PicoDownloadFile class
         $path = "/path/to/large-file.zip";
         $localName = "downloaded-file.zip";
        -$file = new MagicObject\File\PicoDownloadFile($path, $localName);
        +$file = new PicoDownloadFile($path, $localName);
         $file->download(true); // Initiate download and terminate the script after sending
         ```
         
        @@ -12188,7 +12188,7 @@ $file->download(true); // Initiate download and terminate the script after sendi
         require 'vendor/autoload.php'; // Include the PicoDownloadFile class
         $path = "/path/to/large-file.zip";
         $localName = "downloaded-file.zip";
        -$file = new MagicObject\File\PicoDownloadFile($path, $localName);
        +$file = new PicoDownloadFile($path, $localName);
         $finished = $file->download(false); // Initiate download without terminate the script after sending
         if($finished && file_exists($path))
         {
        
        From 44d869e51604e553c9dfbd60d9fff749ee7517a2 Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 10:51:29 +0700
        Subject: [PATCH 07/55] Update PicoDatabaseUtil.php
        
        ---
         src/Util/Database/PicoDatabaseUtil.php | 2 +-
         1 file changed, 1 insertion(+), 1 deletion(-)
        
        diff --git a/src/Util/Database/PicoDatabaseUtil.php b/src/Util/Database/PicoDatabaseUtil.php
        index a5d793db..d7ed4cbc 100644
        --- a/src/Util/Database/PicoDatabaseUtil.php
        +++ b/src/Util/Database/PicoDatabaseUtil.php
        @@ -352,7 +352,7 @@ public static function uuid()
              * @param string $sqlText The raw SQL string containing one or more queries.
              * @return array An array of queries with their respective delimiters.
              */
        -    public function splitSql($sqlText) //NOSONAR
        +    public function splitSql($sqlText) 
             {
                 $sqlText = str_replace("\n", "\r\n", $sqlText);
                 $sqlText = str_replace("\r\r\n", "\r\n", $sqlText);
        
        From 4b04df6b3796076624a401fa3bfce753e19d09a3 Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 12:38:31 +0700
        Subject: [PATCH 08/55] Update database utility
        
        ---
         src/Util/Database/PicoDatabaseUtil.php | 135 +++++++++----------------
         src/Util/Database/PicoSqlParser.php    |  14 +--
         2 files changed, 58 insertions(+), 91 deletions(-)
        
        diff --git a/src/Util/Database/PicoDatabaseUtil.php b/src/Util/Database/PicoDatabaseUtil.php
        index d7ed4cbc..32a9909e 100644
        --- a/src/Util/Database/PicoDatabaseUtil.php
        +++ b/src/Util/Database/PicoDatabaseUtil.php
        @@ -352,100 +352,65 @@ public static function uuid()
              * @param string $sqlText The raw SQL string containing one or more queries.
              * @return array An array of queries with their respective delimiters.
              */
        -    public function splitSql($sqlText) 
        +    public function splitSql($sqlText)
             {
        -        $sqlText = str_replace("\n", "\r\n", $sqlText);
        -        $sqlText = str_replace("\r\r\n", "\r\n", $sqlText);
        -        $arr = explode("\r\n", $sqlText);
        -        $arr2 = array();
        -        foreach($arr as $key=>$val)
        -        {
        -            $arr[$key] = ltrim($val);
        -            if(stripos($arr[$key], "-- ") !== 0 && $arr[$key] != "--" && $arr[$key] != "")
        -            {
        -                $arr2[] = $arr[$key];
        -            }
        -        }
        -        $arr = $arr2;
        -        unset($arr2);
        +        // Normalize newlines and clean up any redundant line breaks
        +        $sqlText = str_replace("\r\r\n", "\r\n", str_replace("\n", "\r\n", $sqlText));
        +        
        +        // Split the SQL text by newlines
        +        $lines = explode("\r\n", $sqlText);
        +        
        +        // Clean up lines, remove comments, and empty lines
        +        $cleanedLines = array_filter(array_map('ltrim', $lines), function ($line) {
        +            return !(empty($line) || stripos($line, "-- ") === 0 || $line == "--");
        +        });
        +        
        +        // Initialize state variables
        +        $queries = [];
        +        $currentQuery = '';
        +        $isAppending = false;
        +        $delimiter = ';';
        +        $skip = false;
         
        -        $append = 0;
        -        $skip = 0;
        -        $start = 1;
        -        $nquery = -1;
        -        $delimiter = ";";
        -        $queryArray = array();
        -        $delimiterArray = array();
        +        foreach ($cleanedLines as $line) {
        +            // Skip lines if needed
        +            if ($skip) {
        +                $skip = false;
        +                continue;
        +            }
         
        -        foreach($arr as $line=>$text)
        -        {
        -            if($text == "" && $append == 1)
        -            {
        -                $queryArray[$nquery] .= "\r\n";
        +            // Handle "delimiter" statements
        +            if (stripos(trim($line), 'delimiter ') === 0) {
        +                $parts = explode(' ', trim($line));
        +                $delimiter = $parts[1] ?? ';';
        +                continue;
                     }
        -            if($append == 0)
        -            {
        -                if(stripos(ltrim($text, " \t "), "--") === 0)
        -                {
        -                    $skip = 1;
        -                    $nquery++;
        -                    $start = 1;
        -                    $append = 0;
        -                }
        -                else
        -                {
        -                    $skip = 0;
        +
        +            // Start a new query if necessary
        +            if (!$isAppending) {
        +                if (!empty($currentQuery)) {
        +                    // Store the previous query and reset for the next one
        +                    $queries[] = ['query' => rtrim($currentQuery, self::INLINE_TRIM), 'delimiter' => $delimiter];
                         }
        +                $currentQuery = '';
        +                $isAppending = true;
                     }
        -            if($skip == 0)
        -            {
        -                if($start == 1)
        -                {
        -                    $nquery++;
        -                    $queryArray[$nquery] = "";
        -                    $delimiterArray[$nquery] = $delimiter;
        -                    $start = 0;
        -                }
        -                $queryArray[$nquery] .= $text."\r\n";
        -                $delimiterArray[$nquery] = $delimiter;
        -                $text = ltrim($text, " \t ");
        -                $start = strlen($text)-strlen($delimiter)-1;
        -                if(stripos(substr($text, $start), $delimiter) !== false || $text == $delimiter)
        -                {
        -                    $nquery++;
        -                    $start = 1;
        -                    $append = 0;
        -                }
        -                else
        -                {
        -                    $start = 0;
        -                    $append = 1;
        -                }
        -                $delimiterArray[$nquery] = $delimiter;
        -                if(stripos($text, "delimiter ") !== false)
        -                {
        -                    $text = trim(preg_replace("/\s+/"," ",$text));
        -                    $arr2 = explode(" ", $text);
        -                    $delimiter = $arr2[1];
        -                    $nquery++;
        -                    $delimiterArray[$nquery] = $delimiter;
        -                    $start = 1;
        -                    $append = 0;
        -                }
        +
        +            // Append current line to the current query
        +            $currentQuery .= $line . "\r\n";
        +
        +            // Check if the query ends with the delimiter
        +            if (substr(rtrim($line), -strlen($delimiter)) === $delimiter) {
        +                $isAppending = false; // End of query, so we stop appending
                     }
                 }
        -        $result = array();
        -        foreach($queryArray as $line=>$sql)
        -        {
        -            $delimiter = $delimiterArray[$line];
        -            if(stripos($sql, "delimiter ") !== 0)
        -            {
        -                $sql = rtrim($sql, self::INLINE_TRIM);
        -                $sql = substr($sql, 0, strlen($sql)-strlen($delimiter));
        -                $result[] = array("query"=> $sql, "delimiter"=>$delimiter);
        -            }
        +
        +        // Add the last query if any
        +        if (!empty($currentQuery)) {
        +            $queries[] = ['query' => rtrim($currentQuery, self::INLINE_TRIM), 'delimiter' => $delimiter];
                 }
        -        return $result;
        +
        +        return $queries;
             }
         
             /**
        diff --git a/src/Util/Database/PicoSqlParser.php b/src/Util/Database/PicoSqlParser.php
        index e725b65f..6593120f 100644
        --- a/src/Util/Database/PicoSqlParser.php
        +++ b/src/Util/Database/PicoSqlParser.php
        @@ -26,12 +26,12 @@
         class PicoSqlParser
         {
             // Constant definitions for keys in the parsed table information
        -    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';
        +    const KEY_COLUMN_NAME = 'Field';
        +    const KEY_PRIMARY_KEY = 'Key';
        +    const KEY_TYPE        = 'Type';
        +    const KEY_LENGTH      = 'Length';
        +    const KEY_NULLABLE    = 'Nullable';
        +    const KEY_DEFAULT     = 'Default';
             
             /**
              * List of valid SQL data types supported by this parser.
        @@ -232,6 +232,7 @@ public function init()
              */
             public function parseAll($sql)
             {
        +        $sql = str_replace("`", "", $sql);
                 $inf = [];
                 $rg_tb = '/(create\s+table\s+if\s+not\s+exists|create\s+table)\s+(?.*)\s+\(/i';
                 
        @@ -265,4 +266,5 @@ public function getTableInfo()
             {
                 return $this->tableInfo;
             }
        +
         }
        
        From cf03b3f4671ba91e4866893e1512b2192d2053db Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 13:14:13 +0700
        Subject: [PATCH 09/55] Update configure-import.php
        
        ---
         tests/configure-import.php | 2 +-
         1 file changed, 1 insertion(+), 1 deletion(-)
        
        diff --git a/tests/configure-import.php b/tests/configure-import.php
        index 8643631e..207c62ab 100644
        --- a/tests/configure-import.php
        +++ b/tests/configure-import.php
        @@ -8,6 +8,6 @@
         $config = new SecretObject();
         $config->loadYamlFile('import.yml', true, true, true);
         
        -PicoDatabaseUtilMySql::autoConfigureImportData($config);
        +(new PicoDatabaseUtilMySql())->autoConfigureImportData($config);
         file_put_contents('import.yml', $config->dumpYaml(0, 2));
         
        
        From 11a1365c6e4fd2d47e69966378d88f1589969deb Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 13:47:00 +0700
        Subject: [PATCH 10/55] Update PicoDatabase.php
        
        ---
         src/Database/PicoDatabase.php | 274 +++++++++++++++++++++++++---------
         1 file changed, 202 insertions(+), 72 deletions(-)
        
        diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php
        index f1641ae3..1fb37d7f 100644
        --- a/src/Database/PicoDatabase.php
        +++ b/src/Database/PicoDatabase.php
        @@ -112,9 +112,71 @@ public static function fromPdo($pdo)
                 $database->databaseConnection = $pdo;
                 $database->databaseType = $database->getDbType($pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
                 $database->connected = true;
        +        $database->databaseCredentials = $database->getDatabaseCredentialsFromPdo($pdo);
                 return $database;
             }
         
        +    /**
        +     * Get PDO connection details, including driver, host, port, database name, and schema.
        +     *
        +     * This function retrieves information about the PDO connection, such as the database driver, host, port, 
        +     * database name, and schema based on the type of database (e.g., MySQL, PostgreSQL, SQLite).
        +     *
        +     * It uses the PDO connection's attributes and queries the database if necessary to obtain the schema name.
        +     *
        +     * @param PDO $pdo The PDO connection object.
        +     * @return SecretObject Returns a SecretObject containing the connection details (driver, host, port, database name, schema).
        +     * 
        +     * @throws PDOException If there is an error with the PDO query or connection.
        +     */
        +    private function getDatabaseCredentialsFromPdo($pdo)
        +    {
        +        // Get the driver name (e.g., mysql, pgsql, sqlite)
        +        $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
        +
        +        // Get the connection status, which includes the DSN (Data Source Name)
        +        $dsn = $pdo->getAttribute(PDO::ATTR_CONNECTION_STATUS);
        +        $dsnParts = parse_url($dsn);
        +
        +        // Extract the host from the DSN (if available)
        +        $host = isset($dsnParts['host']) ? $dsnParts['host'] : null;
        +
        +        // Extract the port from the DSN (if available)
        +        $port = isset($dsnParts['port']) ? $dsnParts['port'] : null;
        +
        +        // Get the database name from the DSN (usually found at the end of the DSN after host and port)
        +        $databaseName = isset($dsnParts['path']) ? ltrim($dsnParts['path'], '/') : null;
        +
        +        $dbType = $this->getDbType($driver);
        +        if ($dbType == PicoDatabaseType::DATABASE_TYPE_PGSQL) {
        +            // For PostgreSQL, fetch the current schema using a query
        +            $stmt = $pdo->query('SELECT current_schema()');
        +            $schema = $stmt->fetchColumn(); // Fetch the schema name
        +        }
        +        elseif ($dbType == PicoDatabaseType::DATABASE_TYPE_MYSQL || $dbType == PicoDatabaseType::DATABASE_TYPE_MARIADB) {
        +            // For MySQL, the schema is the same as the database name
        +            $schema = $databaseName; // MySQL schema is the database name
        +        }
        +        elseif ($dbType ==PicoDatabaseType::DATABASE_TYPE_SQLITE) {
        +            // For SQLite, there is no concept of schema, so set it to null
        +            $schema = null;
        +        } else {
        +            // For other drivers, set schema to null (or handle it as needed)
        +            $schema = null;
        +        }
        +
        +        // Create and populate the SecretObject with the connection details
        +        $databaseCredentials = new SecretObject();
        +        $databaseCredentials->setDriver($driver);
        +        $databaseCredentials->setHost($host);
        +        $databaseCredentials->setPort($port);
        +        $databaseCredentials->setDatabaseName($databaseName);
        +        $databaseCredentials->setDatabaseSchema($schema);
        +
        +        // Return the populated SecretObject containing the connection details
        +        return $databaseCredentials;
        +    }
        +
             /**
              * Constructor to initialize the PicoDatabase object.
              *
        @@ -236,11 +298,18 @@ private function connectRDMS($withDatabase = true)
             /**
              * Determine the database type based on the provided database type string.
              *
        -     * This method checks the input string for common database type identifiers (SQLite, PostgreSQL, 
        -     * MariaDB, MySQL) and returns the corresponding constant from the PicoDatabaseType class.
        +     * This method evaluates the given string to identify common database type names
        +     * (e.g., SQLite, PostgreSQL, MariaDB, MySQL) and returns the corresponding 
        +     * constant from the `PicoDatabaseType` class that represents the type of database.
        +     * The function performs case-insensitive string matching using `stripos` to check for
        +     * keywords like "sqlite", "postgre", "pgsql", "maria", and defaults to MySQL if no match is found.
              *
        -     * @param string $databaseType The database type string to evaluate.
        -     * @return string The corresponding database type constant from PicoDatabaseType.
        +     * @param string $databaseType The database type string to evaluate, such as 'SQLite', 'PostgreSQL', 'MariaDB', or 'MySQL'.
        +     * @return string The corresponding database type constant from `PicoDatabaseType`:
        +     *                - `PicoDatabaseType::DATABASE_TYPE_SQLITE`
        +     *                - `PicoDatabaseType::DATABASE_TYPE_PGSQL`
        +     *                - `PicoDatabaseType::DATABASE_TYPE_MARIADB`
        +     *                - `PicoDatabaseType::DATABASE_TYPE_MYSQL`
              */
             private function getDbType($databaseType) // NOSONAR
             {
        @@ -298,6 +367,8 @@ private function constructConnectionString($withDatabase = true)
             /**
              * Disconnect from the database.
              *
        +     * This method sets the database connection to `null`, effectively closing the connection to the database.
        +     *
              * @return self Returns the current instance for method chaining.
              */
             public function disconnect()
        @@ -307,9 +378,11 @@ public function disconnect()
             }
         
             /**
        -     * Set the time zone offset.
        +     * Set the time zone offset for the database session.
              *
        -     * @param string $timeZoneOffset Client time zone.
        +     * This method sets the time zone offset for the current session, which can be useful for time-related operations.
        +     *
        +     * @param string $timeZoneOffset The time zone offset to set for the session (e.g., '+00:00', 'Europe/London').
              * @return self Returns the current instance for method chaining.
              */
             public function setTimeZoneOffset($timeZoneOffset)
        @@ -320,9 +393,11 @@ public function setTimeZoneOffset($timeZoneOffset)
             }
         
             /**
        -     * Change the database.
        +     * Switch to a different database.
        +     *
        +     * This method changes the currently active database to the specified one.
              *
        -     * @param string $databaseName Database name.
        +     * @param string $databaseName The name of the database to switch to.
              * @return self Returns the current instance for method chaining.
              */
             public function useDatabase($databaseName)
        @@ -333,10 +408,13 @@ public function useDatabase($databaseName)
             }
         
             /**
        -     * Set autocommit ON or OFF.
        +     * Set autocommit mode for transactions.
              *
        -     * @param bool $autocommit Flag autocommit.
        -     * @return bool True if autocommit is set successfully, false otherwise.
        +     * This method enables or disables autocommit mode for database transactions. When autocommit is off,
        +     * you must explicitly call `commit()` or `rollback()` to finalize or revert the transaction.
        +     *
        +     * @param bool $autocommit Flag indicating whether autocommit should be enabled (`true`) or disabled (`false`).
        +     * @return bool Returns `true` if the autocommit setting was successfully updated, `false` otherwise.
              */
             public function setAudoCommit($autocommit)
             {
        @@ -345,9 +423,11 @@ public function setAudoCommit($autocommit)
             }
         
             /**
        -     * Commit the transaction.
        +     * Commit the current transaction.
        +     *
        +     * This method commits the transaction, making all changes made during the transaction permanent.
              *
        -     * @return bool True if the transaction was committed successfully, false otherwise.
        +     * @return bool Returns `true` if the transaction was successfully committed, `false` otherwise.
              */
             public function commit()
             {
        @@ -355,9 +435,11 @@ public function commit()
             }
         
             /**
        -     * Rollback the transaction.
        +     * Rollback the current transaction.
        +     *
        +     * This method rolls back the transaction, undoing any changes made during the transaction.
              *
        -     * @return bool True if the transaction was rolled back successfully, false otherwise.
        +     * @return bool Returns `true` if the transaction was successfully rolled back, `false` otherwise.
              */
             public function rollback()
             {
        @@ -365,9 +447,11 @@ public function rollback()
             }
         
             /**
        -     * Get the database connection.
        +     * Get the current database connection.
              *
        -     * @return PDO Represents a connection between PHP and a database server.
        +     * This method returns the active PDO connection object, which can be used for executing queries directly.
        +     *
        +     * @return PDO The active PDO connection object representing the connection to the database server.
              */
             public function getDatabaseConnection()
             {
        @@ -375,11 +459,14 @@ public function getDatabaseConnection()
             }
         
             /**
        -     * Execute a query.
        +     * Execute a SQL query.
        +     *
        +     * This method executes a SQL query with optional parameters and returns the resulting PDO statement object.
              *
        -     * @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.
        +     * @param string $sql The SQL query to execute.
        +     * @param array|null $params Optional parameters to bind to the query.
        +     * @return PDOStatement|false Returns a `PDOStatement` object if the query was executed successfully, 
        +     *                             or `false` if the execution failed.
              * @throws PDOException If an error occurs while executing the query.
              */
             public function query($sql, $params = null)
        @@ -388,13 +475,15 @@ public function query($sql, $params = null)
             }
         
             /**
        -     * Fetch a result.
        +     * Fetch a result from the database.
        +     *
        +     * This method executes a query and returns a single result. If no result is found, the default value is returned.
              *
        -     * @param string $sql SQL to be executed.
        -     * @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.
        +     * @param string $sql SQL query to be executed.
        +     * @param int $tentativeType The fetch mode to be used (e.g., PDO::FETCH_ASSOC).
        +     * @param mixed $defaultValue The default value to return if no results are found.
        +     * @param array|null $params Optional parameters to bind to the SQL query.
        +     * @return array|object|stdClass|null Returns the fetched result (array, object, or stdClass), or the default value if no results are found.
              */
             public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null)
             {
        @@ -428,11 +517,13 @@ public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = n
             }
         
             /**
        -     * Check if a record exists.
        +     * Check if a record exists in the database.
              *
        -     * @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.
        +     * This method executes a query and checks if any record is returned.
        +     *
        +     * @param string $sql SQL query to be executed.
        +     * @param array|null $params Optional parameters to bind to the SQL query.
        +     * @return bool Returns `true` if the record exists, `false` otherwise.
              * @throws NullPointerException If the database connection is null.
              */
             public function isRecordExists($sql, $params = null)
        @@ -461,12 +552,14 @@ public function isRecordExists($sql, $params = null)
             }
         
             /**
        -     * Fetch all results.
        +     * Fetch all results from the database.
        +     *
        +     * This method executes a query and returns all matching results. If no results are found, the default value is returned.
              *
        -     * @param string $sql SQL to be executed.
        -     * @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.
        +     * @param string $sql SQL query to be executed.
        +     * @param int $tentativeType The fetch mode to be used (e.g., PDO::FETCH_ASSOC).
        +     * @param mixed $defaultValue The default value to return if no results are found.
        +     * @param array|null $params Optional parameters to bind to 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, $params = null)
        @@ -501,10 +594,12 @@ public function fetchAll($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue
             }
         
             /**
        -     * Execute a query without returning anything.
        +     * Execute a SQL query without returning any results.
        +     *
        +     * This method executes a query without expecting any result, typically used for non-SELECT queries (INSERT, UPDATE, DELETE).
              *
        -     * @param string $sql Query string to be executed.
        -     * @param array|null $params Optional parameters for the SQL query.
        +     * @param string $sql SQL query to be executed.
        +     * @param array|null $params Optional parameters to bind to the SQL query.
              * @throws NullPointerException If the database connection is null.
              */
             public function execute($sql, $params = null)
        @@ -524,11 +619,13 @@ public function execute($sql, $params = null)
             }
         
             /**
        -     * Execute a query and return the statement.
        +     * Execute a SQL query and return the statement object.
              *
        -     * @param string $sql Query string 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.
        +     * This method executes a query and returns the PDOStatement object, which can be used to fetch results or retrieve row count.
        +     *
        +     * @param string $sql SQL query to be executed.
        +     * @param array|null $params Optional parameters to bind to 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.
              */
        @@ -551,11 +648,13 @@ public function executeQuery($sql, $params = null)
             }
         
             /**
        -     * Execute an insert query.
        +     * Execute an insert query and return the statement.
        +     *
        +     * This method executes an insert query and returns the PDOStatement object.
              *
        -     * @param string $sql Query string 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.
        +     * @param string $sql SQL query to be executed.
        +     * @param array|null $params Optional parameters to bind to the SQL query.
        +     * @return PDOStatement|false Returns the PDOStatement object if successful, or `false` on failure.
              */
             public function executeInsert($sql, $params = null)
             {
        @@ -565,11 +664,13 @@ public function executeInsert($sql, $params = null)
             }
         
             /**
        -     * Execute an update query.
        +     * Execute an update query and return the statement.
              *
        -     * @param string $sql Query string 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.
        +     * This method executes an update query and returns the PDOStatement object.
        +     *
        +     * @param string $sql SQL query to be executed.
        +     * @param array|null $params Optional parameters to bind to the SQL query.
        +     * @return PDOStatement|false Returns the PDOStatement object if successful, or `false` on failure.
              */
             public function executeUpdate($sql, $params = null)
             {
        @@ -579,11 +680,13 @@ public function executeUpdate($sql, $params = null)
             }
         
             /**
        -     * Execute a delete query.
        +     * Execute a delete query and return the statement.
        +     *
        +     * This method executes a delete query and returns the PDOStatement object.
              *
        -     * @param string $sql Query string 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.
        +     * @param string $sql SQL query to be executed.
        +     * @param array|null $params Optional parameters to bind to the SQL query.
        +     * @return PDOStatement|false Returns the PDOStatement object if successful, or `false` on failure.
              */
             public function executeDelete($sql, $params = null)
             {
        @@ -593,11 +696,13 @@ public function executeDelete($sql, $params = null)
             }
         
             /**
        -     * Execute a transaction query.
        +     * Execute a transaction query and return the statement.
        +     *
        +     * This method executes a query as part of a transaction and returns the PDOStatement object.
              *
        -     * @param string $sql Query string 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.
        +     * @param string $sql SQL query to be executed.
        +     * @param array|null $params Optional parameters to bind to the SQL query.
        +     * @return PDOStatement|false Returns the PDOStatement object if successful, or `false` on failure.
              */
             public function executeTransaction($sql, $params = null)
             {
        @@ -607,11 +712,13 @@ public function executeTransaction($sql, $params = null)
             }
         
             /**
        -     * Execute a callback query function.
        +     * Execute a callback query function after executing the query.
              *
        -     * @param string $query SQL to be executed.
        -     * @param array|null $params Optional parameters for the SQL query.
        -     * @param string|null $type Query type.
        +     * This method calls the provided callback function after executing a query.
        +     *
        +     * @param string $query SQL query to be executed.
        +     * @param array|null $params Optional parameters to bind to the SQL query.
        +     * @param string|null $type Type of the query (e.g., INSERT, UPDATE, DELETE, etc.).
              */
             private function executeCallback($query, $params = null, $type = null)
             {
        @@ -635,8 +742,10 @@ private function executeCallback($query, $params = null, $type = null)
             /**
              * Execute a debug query function.
              *
        -     * @param string $query SQL to be executed.
        -     * @param array|null $params Optional parameters for the SQL query.
        +     * This method calls a debug callback function if it is set.
        +     *
        +     * @param string $query SQL query to be executed.
        +     * @param array|null $params Optional parameters to bind to the SQL query.
              */
             private function executeDebug($query, $params = null)
             {
        @@ -662,7 +771,11 @@ private function executeDebug($query, $params = null)
             /**
              * Generate a unique 20-byte ID.
              *
        -     * @return string 20 bytes unique identifier.
        +     * This method generates a unique ID by concatenating a 13-character string
        +     * from `uniqid()` with a 6-character random hexadecimal string, ensuring
        +     * the resulting string is 20 characters in length.
        +     *
        +     * @return string A unique 20-byte identifier.
              */
             public function generateNewId()
             {
        @@ -677,7 +790,11 @@ public function generateNewId()
             /**
              * Get the last inserted ID.
              *
        -     * @param string|null $name Sequence name (e.g., PostgreSQL).
        +     * This method retrieves the ID of the last inserted record. Optionally,
        +     * you can provide a sequence name (e.g., for PostgreSQL) to fetch the last
        +     * inserted ID from a specific sequence.
        +     *
        +     * @param string|null $name The sequence name (e.g., PostgreSQL). Default is null.
              * @return string|false Returns the last inserted ID as a string, or false if there was an error.
              */
             public function lastInsertId($name = null)
        @@ -686,9 +803,12 @@ public function lastInsertId($name = null)
             }
         
             /**
        -     * Get the value of databaseCredentials.
        +     * Get the value of database credentials.
        +     *
        +     * This method returns the object containing the database credentials used
        +     * to establish the database connection.
              *
        -     * @return SecretObject Returns the database credentials object.
        +     * @return SecretObject The database credentials object.
              */
             public function getDatabaseCredentials()
             {
        @@ -696,7 +816,10 @@ public function getDatabaseCredentials()
             }
         
             /**
        -     * Get indication whether the database is connected or not.
        +     * Check whether the database is connected.
        +     *
        +     * This method returns a boolean value indicating whether the database
        +     * connection is currently active.
              *
              * @return bool Returns true if connected, false otherwise.
              */
        @@ -706,9 +829,16 @@ public function isConnected()
             }
         
             /**
        -     * Get the database type.
        +     * Get the type of the database.
        +     *
        +     * This method returns the type of the database that is currently connected.
        +     * The possible values are constants from the `PicoDatabaseType` class:
        +     * - `PicoDatabaseType::DATABASE_TYPE_MYSQL`
        +     * - `PicoDatabaseType::DATABASE_TYPE_MARIADB`
        +     * - `PicoDatabaseType::DATABASE_TYPE_PGSQL`
        +     * - `PicoDatabaseType::DATABASE_TYPE_SQLITE`
              *
        -     * @return string Returns the type of the database (e.g., MySQL, PostgreSQL).
        +     * @return string The type of the database.
              */
             public function getDatabaseType()
             {
        
        From 349286215156f4f74508b3f27d374943673da3c6 Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 14:04:57 +0700
        Subject: [PATCH 11/55] Map MySQL system time zone or abbreviations like 'WIB'
         to a valid PHP time zone.
        
        ---
         src/Database/PicoDatabase.php | 128 ++++++++++++++++++++++++++++++++--
         1 file changed, 121 insertions(+), 7 deletions(-)
        
        diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php
        index 1fb37d7f..2c50cef5 100644
        --- a/src/Database/PicoDatabase.php
        +++ b/src/Database/PicoDatabase.php
        @@ -117,15 +117,15 @@ public static function fromPdo($pdo)
             }
         
             /**
        -     * Get PDO connection details, including driver, host, port, database name, and schema.
        +     * Get PDO connection details, including driver, host, port, database name, schema, and time zone.
              *
              * This function retrieves information about the PDO connection, such as the database driver, host, port, 
        -     * database name, and schema based on the type of database (e.g., MySQL, PostgreSQL, SQLite).
        +     * database name, schema, and time zone based on the type of database (e.g., MySQL, PostgreSQL, SQLite).
              *
        -     * It uses the PDO connection's attributes and queries the database if necessary to obtain the schema name.
        +     * It uses the PDO connection's attributes and queries the database if necessary to obtain the schema name and time zone.
              *
              * @param PDO $pdo The PDO connection object.
        -     * @return SecretObject Returns a SecretObject containing the connection details (driver, host, port, database name, schema).
        +     * @return SecretObject Returns a SecretObject containing the connection details (driver, host, port, database name, schema, and time zone).
              * 
              * @throws PDOException If there is an error with the PDO query or connection.
              */
        @@ -147,22 +147,56 @@ private function getDatabaseCredentialsFromPdo($pdo)
                 // Get the database name from the DSN (usually found at the end of the DSN after host and port)
                 $databaseName = isset($dsnParts['path']) ? ltrim($dsnParts['path'], '/') : null;
         
        +        // Initialize the schema and time zone
        +        $schema = null;
        +        $timezone = null;
        +
        +        // Determine the database type
                 $dbType = $this->getDbType($driver);
        +        
        +        // Retrieve the schema and time zone based on the database type
                 if ($dbType == PicoDatabaseType::DATABASE_TYPE_PGSQL) {
        -            // For PostgreSQL, fetch the current schema using a query
        +            // For PostgreSQL, fetch the current schema and time zone using queries
                     $stmt = $pdo->query('SELECT current_schema()');
                     $schema = $stmt->fetchColumn(); // Fetch the schema name
        +            
        +            $stmtTimezone = $pdo->query('SHOW timezone');
        +            $timezone = $stmtTimezone->fetchColumn(); // Fetch the time zone
                 }
                 elseif ($dbType == PicoDatabaseType::DATABASE_TYPE_MYSQL || $dbType == PicoDatabaseType::DATABASE_TYPE_MARIADB) {
                     // For MySQL, the schema is the same as the database name
                     $schema = $databaseName; // MySQL schema is the database name
        +            
        +            // Retrieve the global time zone from MySQL
        +            $stmtTimezone = $pdo->query('SELECT @@global.time_zone');
        +            $timezone = $stmtTimezone->fetchColumn(); // Fetch the global time zone
        +
        +            // If the time zone is set to 'SYSTEM', retrieve the system's time zone and convert it
        +            if ($timezone == 'SYSTEM') {
        +                $stmtSystemTimeZone = $pdo->query('SELECT @@system_time_zone');
        +                $systemTimeZone = $stmtSystemTimeZone->fetchColumn();
        +
        +                // Convert MySQL system time zone to PHP-compatible time zone (e.g., 'Asia/Jakarta')
        +                // This conversion may require a lookup table, as MySQL system time zones
        +                // (e.g., 'CST', 'PST') are not directly equivalent to PHP time zones (e.g., 'Asia/Jakarta').
        +                // Here, we will simply return the system time zone as a placeholder:
        +                $timezone = $systemTimeZone;
        +            }
                 }
        -        elseif ($dbType ==PicoDatabaseType::DATABASE_TYPE_SQLITE) {
        +        elseif ($dbType == PicoDatabaseType::DATABASE_TYPE_SQLITE) {
                     // For SQLite, there is no concept of schema, so set it to null
                     $schema = null;
        +            // SQLite does not have a time zone setting
        +            $timezone = null;
                 } else {
        -            // For other drivers, set schema to null (or handle it as needed)
        +            // For other drivers, set schema and time zone to null (or handle it as needed)
                     $schema = null;
        +            $timezone = null;
        +        }
        +
        +        // If the time zone is provided, convert it to a recognized PHP time zone if necessary
        +        if (isset($timezone)) {
        +            $timezone = $this->mysqlToPhpTimezone($timezone);
                 }
         
                 // Create and populate the SecretObject with the connection details
        @@ -172,11 +206,91 @@ private function getDatabaseCredentialsFromPdo($pdo)
                 $databaseCredentials->setPort($port);
                 $databaseCredentials->setDatabaseName($databaseName);
                 $databaseCredentials->setDatabaseSchema($schema);
        +        $databaseCredentials->setTimeZone($timezone);
         
                 // Return the populated SecretObject containing the connection details
                 return $databaseCredentials;
             }
         
        +    /**
        +     * Map MySQL system time zone or abbreviations like 'WIB' to a valid PHP time zone.
        +     *
        +     * This function converts time zone abbreviations (like 'WIB', 'WITA', 'WIT') or system time zones
        +     * to a recognized PHP time zone format (e.g., 'Asia/Jakarta').
        +     *
        +     * @param string $timezoneAbbr The time zone abbreviation or system time zone (e.g., 'WIB', 'SYSTEM').
        +     * @return string|null Returns a PHP-compatible time zone (e.g., 'Asia/Jakarta') or null if not recognized.
        +     */
        +    private function mysqlToPhpTimezone($timezoneAbbr)
        +    {
        +        $timezoneMapping = [
        +            // Indonesia
        +            'WIB'  => 'Asia/Jakarta',   // Western Indonesia Time (e.g., Jakarta, Bali)
        +            'WITA' => 'Asia/Makassar',  // Central Indonesia Time (e.g., Bali, Sulawesi)
        +            'WIT'  => 'Asia/Jayapura',  // Eastern Indonesia Time (e.g., Papua)
        +
        +            // Common USA Time Zones
        +            'PST'  => 'America/Los_Angeles', // Pacific Standard Time (Standard Time)
        +            'PDT'  => 'America/Los_Angeles', // Pacific Daylight Time (Daylight Saving Time)
        +            'MST'  => 'America/Denver',      // Mountain Standard Time
        +            'MDT'  => 'America/Denver',      // Mountain Daylight Time
        +            'CST'  => 'America/Chicago',     // Central Standard Time
        +            'CDT'  => 'America/Chicago',     // Central Daylight Time
        +            'EST'  => 'America/New_York',    // Eastern Standard Time
        +            'EDT'  => 'America/New_York',    // Eastern Daylight Time
        +            'AKST' => 'America/Anchorage',  // Alaska Standard Time
        +            'AKDT' => 'America/Anchorage',  // Alaska Daylight Time
        +            'HST'  => 'Pacific/Honolulu',   // Hawaii Standard Time
        +
        +            // United Kingdom
        +            'GMT'  => 'Europe/London',      // Greenwich Mean Time (Standard Time)
        +            'BST'  => 'Europe/London',      // British Summer Time (Daylight Saving Time)
        +
        +            // Central Europe
        +            'CET'  => 'Europe/Paris',       // Central European Time
        +            'CEST' => 'Europe/Paris',       // Central European Summer Time (Daylight Saving Time)
        +
        +            // Central Asia and Russia
        +            'MSK'  => 'Europe/Moscow',      // Moscow Standard Time
        +            'MSD'  => 'Europe/Moscow',      // Moscow Daylight Time (not used anymore)
        +
        +            // Australia
        +            'AEST' => 'Australia/Sydney',   // Australian Eastern Standard Time
        +            'AEDT' => 'Australia/Sydney',   // Australian Eastern Daylight Time
        +            'ACST' => 'Australia/Adelaide', // Australian Central Standard Time
        +            'ACDT' => 'Australia/Adelaide', // Australian Central Daylight Time
        +            'AWST' => 'Australia/Perth',    // Australian Western Standard Time
        +
        +            // Africa
        +            'CAT'  => 'Africa/Harare',      // Central Africa Time
        +            'EAT'  => 'Africa/Nairobi',     // East Africa Time
        +            'WAT'  => 'Africa/Algiers',     // West Africa Time
        +
        +            // India
        +            'IST'  => 'Asia/Kolkata',       // Indian Standard Time
        +
        +            // China and East Asia
        +            'CST'  => 'Asia/Shanghai',      // China Standard Time
        +            'JST'  => 'Asia/Tokyo',         // Japan Standard Time
        +            'KST'  => 'Asia/Seoul',         // Korea Standard Time
        +
        +            // Other time zones
        +            'UTC'  => 'UTC',                // Coordinated Universal Time
        +            'Z'    => 'UTC',                // Zulu time (same as UTC)
        +            'ART'  => 'Africa/Argentina',   // Argentina Time
        +            'NFT'  => 'Pacific/Norfolk',    // Norfolk Time Zone (Australia)
        +
        +            // Time zones used in specific areas
        +            'NST'  => 'Asia/Kolkata',       // Newfoundland Standard Time (if used as an abbreviation)
        +        ];
        +
        +        // Return the mapped PHP time zone or null if not found
        +        return isset($timezoneMapping[$timezoneAbbr]) ? $timezoneMapping[$timezoneAbbr] : null;
        +    }
        +
        +
        +
        +
             /**
              * Constructor to initialize the PicoDatabase object.
              *
        
        From 411ed4ee429065ef94d9a1df681a8fa67d9813b9 Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 14:18:00 +0700
        Subject: [PATCH 12/55] Update PicoDatabase.php
        
        ---
         src/Database/PicoDatabase.php | 7 +------
         1 file changed, 1 insertion(+), 6 deletions(-)
        
        diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php
        index 2c50cef5..d398a51e 100644
        --- a/src/Database/PicoDatabase.php
        +++ b/src/Database/PicoDatabase.php
        @@ -183,12 +183,7 @@ private function getDatabaseCredentialsFromPdo($pdo)
                         $timezone = $systemTimeZone;
                     }
                 }
        -        elseif ($dbType == PicoDatabaseType::DATABASE_TYPE_SQLITE) {
        -            // For SQLite, there is no concept of schema, so set it to null
        -            $schema = null;
        -            // SQLite does not have a time zone setting
        -            $timezone = null;
        -        } else {
        +        else {
                     // For other drivers, set schema and time zone to null (or handle it as needed)
                     $schema = null;
                     $timezone = null;
        
        From e88d18a74101284866e5e9d6f603502d3940f02d Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 14:28:02 +0700
        Subject: [PATCH 13/55] Fix codesmell
        
        ---
         src/Generator/PicoDtoGenerator.php            |  12 +-
         src/Geometry/LatBound.php                     |  20 +-
         src/Geometry/LatLng.php                       |  10 +-
         src/Geometry/LatLngBounds.php                 |  70 +++---
         src/Geometry/LngBound.php                     |  28 +--
         src/MagicDto.php                              |  11 -
         .../Database/PicoDatabaseUtilInterface.php    |   2 +-
         src/Util/PicoParsedown.php                    | 220 +++++++++---------
         8 files changed, 177 insertions(+), 196 deletions(-)
        
        diff --git a/src/Generator/PicoDtoGenerator.php b/src/Generator/PicoDtoGenerator.php
        index 5831abac..44cb01f5 100644
        --- a/src/Generator/PicoDtoGenerator.php
        +++ b/src/Generator/PicoDtoGenerator.php
        @@ -85,16 +85,8 @@ class PicoDtoGenerator
              * @param string|null $entityName Name of the entity (optional)
              * @param bool $prettify Flag to prettify output (default: false)
              */
        -    public function __construct(
        -        $database,
        -        $baseDir,
        -        $tableName,
        -        $baseNamespaceDto,
        -        $dtoName,
        -        $baseNamespaceEntity,
        -        $entityName = null,
        -        $prettify = false
        -    ) {
        +    public function __construct($database, $baseDir, $tableName, $baseNamespaceDto, $dtoName, $baseNamespaceEntity, $entityName = null, $prettify = false) //NOSONAR
        +    {
                 $this->database = $database;
                 $this->baseDir = $baseDir;
                 $this->tableName = $tableName;
        diff --git a/src/Geometry/LatBound.php b/src/Geometry/LatBound.php
        index 803895ff..be45b91f 100644
        --- a/src/Geometry/LatBound.php
        +++ b/src/Geometry/LatBound.php
        @@ -80,28 +80,28 @@ public function isEmpty()
             /**
              * Determine if this LatBounds intersects with another LatBounds.
              *
        -     * @param LatBounds $LatBounds The other LatBounds to check for intersection.
        +     * @param LatBounds $latBounds The other LatBounds to check for intersection.
              * @return bool True if there is an intersection, false otherwise.
              */
        -    public function intersects($LatBounds)
        +    public function intersects($latBounds)
             {
        -        return $this->_swLat <= $LatBounds->getSw() 
        -            ? $LatBounds->getSw() <= $this->_neLat && $LatBounds->getSw() <= $LatBounds->getNe() 
        -            : $this->_swLat <= $LatBounds->getNe() && $this->_swLat <= $this->_neLat;
        +        return $this->_swLat <= $latBounds->getSw() 
        +            ? $latBounds->getSw() <= $this->_neLat && $latBounds->getSw() <= $latBounds->getNe() 
        +            : $this->_swLat <= $latBounds->getNe() && $this->_swLat <= $this->_neLat;
             }
         
             /**
              * Check if this LatBounds is equal to another LatBounds within a certain margin of error.
              *
        -     * @param LatBounds $LatBounds The other LatBounds to compare.
        +     * @param LatBounds $latBounds The other LatBounds to compare.
              * @return bool True if they are equal, false otherwise.
              */
        -    public function equals($LatBounds)
        +    public function equals($latBounds)
             {
                 return $this->isEmpty() 
        -            ? $LatBounds->isEmpty() 
        -            : abs($LatBounds->getSw() - $this->_swLat) 
        -                + abs($this->_neLat - $LatBounds->getNe()) 
        +            ? $latBounds->isEmpty() 
        +            : abs($latBounds->getSw() - $this->_swLat) 
        +                + abs($this->_neLat - $latBounds->getNe()) 
                         <= SphericalGeometry::EQUALS_MARGIN_ERROR;
             }
         
        diff --git a/src/Geometry/LatLng.php b/src/Geometry/LatLng.php
        index 7cbabf97..56201ebf 100644
        --- a/src/Geometry/LatLng.php
        +++ b/src/Geometry/LatLng.php
        @@ -75,17 +75,17 @@ public function getLng()
             /**
              * Check if this LatLng is equal to another LatLng object within a certain margin of error.
              *
        -     * @param LatLng $LatLng The LatLng object to compare.
        +     * @param LatLng $latLng The LatLng object to compare.
              * @return bool True if they are equal, false otherwise.
              */
        -    public function equals($LatLng)
        +    public function equals($latLng)
             {
        -        if (!is_object($LatLng) || !($LatLng instanceof self)) {
        +        if (!is_object($latLng) || !($latLng instanceof self)) {
                     return false;
                 }
         
        -        return abs($this->_lat - $LatLng->getLat()) <= SphericalGeometry::EQUALS_MARGIN_ERROR 
        -            && abs($this->_lng - $LatLng->getLng()) <= SphericalGeometry::EQUALS_MARGIN_ERROR;             
        +        return abs($this->_lat - $latLng->getLat()) <= SphericalGeometry::EQUALS_MARGIN_ERROR 
        +            && abs($this->_lng - $latLng->getLng()) <= SphericalGeometry::EQUALS_MARGIN_ERROR;             
             }
         
             /**
        diff --git a/src/Geometry/LatLngBounds.php b/src/Geometry/LatLngBounds.php
        index 127c3dde..c64dbfd3 100644
        --- a/src/Geometry/LatLngBounds.php
        +++ b/src/Geometry/LatLngBounds.php
        @@ -26,32 +26,32 @@ class LatLngBounds
             /**
              * LatLngBounds constructor.
              *
        -     * @param LatLng|null $LatLngSw The southwestern LatLng object.
        -     * @param LatLng|null $LatLngNe The northeastern LatLng object.
        +     * @param LatLng|null $latLngSw The southwestern LatLng object.
        +     * @param LatLng|null $tatLngNe The northeastern LatLng object.
              *
              * @throws E_USER_ERROR If the provided LatLng objects are invalid.
              */
        -    public function __construct($LatLngSw = null, $LatLngNe = null) 
        +    public function __construct($latLngSw = null, $tatLngNe = null) 
             {   
        -        if ((!is_null($LatLngSw) && !($LatLngSw instanceof LatLng))
        -            || (!is_null($LatLngNe) && !($LatLngNe instanceof LatLng)))
        +        if ((!is_null($latLngSw) && !($latLngSw instanceof LatLng))
        +            || (!is_null($tatLngNe) && !($tatLngNe instanceof LatLng)))
                 {
                     trigger_error('LatLngBounds class -> Invalid LatLng object.', E_USER_ERROR);
                 }
         
        -        if ($LatLngSw && !$LatLngNe) 
        +        if ($latLngSw && !$tatLngNe) 
                 {
        -            $LatLngNe = $LatLngSw;
        +            $tatLngNe = $latLngSw;
                 }
         
        -        if ($LatLngSw)
        +        if ($latLngSw)
                 {
        -            $sw = SphericalGeometry::clampLatitude($LatLngSw->getLat());
        -            $ne = SphericalGeometry::clampLatitude($LatLngNe->getLat());
        +            $sw = SphericalGeometry::clampLatitude($latLngSw->getLat());
        +            $ne = SphericalGeometry::clampLatitude($tatLngNe->getLat());
                     $this->_LatBounds = new LatBounds($sw, $ne);
         
        -            $sw = $LatLngSw->getLng();
        -            $ne = $LatLngNe->getLng();
        +            $sw = $latLngSw->getLng();
        +            $ne = $tatLngNe->getLng();
         
                     if ($ne - $sw >= 360) 
                     {
        @@ -59,8 +59,8 @@ public function __construct($LatLngSw = null, $LatLngNe = null)
                     }
                     else 
                     {
        -                $sw = SphericalGeometry::wrapLongitude($LatLngSw->getLng());
        -                $ne = SphericalGeometry::wrapLongitude($LatLngNe->getLng());
        +                $sw = SphericalGeometry::wrapLongitude($latLngSw->getLng());
        +                $ne = SphericalGeometry::wrapLongitude($tatLngNe->getLng());
                         $this->_LngBounds = new LngBounds($sw, $ne);
                     }
                 } 
        @@ -183,64 +183,64 @@ public function toUrlValue($precision = 6)
             /**
              * Check if this LatLngBounds is equal to another LatLngBounds object.
              *
        -     * @param LatLngBounds $LatLngBounds The LatLngBounds object to compare.
        +     * @param LatLngBounds $latLngBounds The LatLngBounds object to compare.
              * @return bool True if they are equal, false otherwise.
              */
        -    public function equals($LatLngBounds)
        +    public function equals($latLngBounds)
             {
        -        return !$LatLngBounds 
        +        return !$latLngBounds 
                     ? false 
        -            : $this->_LatBounds->equals($LatLngBounds->getLatBounds()) 
        -                && $this->_LngBounds->equals($LatLngBounds->getLngBounds());
        +            : $this->_LatBounds->equals($latLngBounds->getLatBounds()) 
        +                && $this->_LngBounds->equals($latLngBounds->getLngBounds());
             }
         
             /**
              * Check if this LatLngBounds intersects with another LatLngBounds.
              *
        -     * @param LatLngBounds $LatLngBounds The LatLngBounds to check for intersection.
        +     * @param LatLngBounds $latLngBounds The LatLngBounds to check for intersection.
              * @return bool True if they intersect, false otherwise.
              */
        -    public function intersects($LatLngBounds)
        +    public function intersects($latLngBounds)
             {
        -        return $this->_LatBounds->intersects($LatLngBounds->getLatBounds()) 
        -            && $this->_LngBounds->intersects($LatLngBounds->getLngBounds());
        +        return $this->_LatBounds->intersects($latLngBounds->getLatBounds()) 
        +            && $this->_LngBounds->intersects($latLngBounds->getLngBounds());
             }
         
             /**
              * Extend this bounding box to include another LatLngBounds.
              *
        -     * @param LatLngBounds $LatLngBounds The LatLngBounds to extend with.
        +     * @param LatLngBounds $latLngBounds The LatLngBounds to extend with.
              * @return $this The current instance for method chaining.
              */
        -    public function union($LatLngBounds)
        +    public function union($latLngBounds)
             {
        -        $this->extend($LatLngBounds->getSouthWest());
        -        $this->extend($LatLngBounds->getNorthEast());
        +        $this->extend($latLngBounds->getSouthWest());
        +        $this->extend($latLngBounds->getNorthEast());
                 return $this;
             }
         
             /**
              * Check if this LatLngBounds contains a specific LatLng point.
              *
        -     * @param LatLng $LatLng The LatLng point to check for containment.
        +     * @param LatLng $latLng The LatLng point to check for containment.
              * @return bool True if the point is contained, false otherwise.
              */
        -    public function contains($LatLng)
        +    public function contains($latLng)
             {
        -        return $this->_LatBounds->contains($LatLng->getLat()) 
        -            && $this->_LngBounds->contains($LatLng->getLng());
        +        return $this->_LatBounds->contains($latLng->getLat()) 
        +            && $this->_LngBounds->contains($latLng->getLng());
             }
         
             /**
              * Extend the bounding box to include a new LatLng point.
              *
        -     * @param LatLng $LatLng The LatLng point to extend with.
        +     * @param LatLng $latLng The LatLng point to extend with.
              * @return $this The current instance for method chaining.
              */
        -    public function extend($LatLng)
        +    public function extend($latLng)
             {
        -        $this->_LatBounds->extend($LatLng->getLat());
        -        $this->_LngBounds->extend($LatLng->getLng());
        +        $this->_LatBounds->extend($latLng->getLat());
        +        $this->_LngBounds->extend($latLng->getLng());
                 return $this;    
             }
         }
        diff --git a/src/Geometry/LngBound.php b/src/Geometry/LngBound.php
        index 2e420401..0d32a6ac 100644
        --- a/src/Geometry/LngBound.php
        +++ b/src/Geometry/LngBound.php
        @@ -88,43 +88,43 @@ public function isEmpty()
             /**
              * Check if this LngBounds intersects with another LngBounds.
              *
        -     * @param LngBounds $LngBounds The LngBounds to check for intersection.
        +     * @param LngBounds $lngBounds The LngBounds to check for intersection.
              * @return bool True if they intersect, false otherwise.
              */
        -    public function intersects($LngBounds) // NOSONAR
        +    public function intersects($lngBounds) // NOSONAR
             {
        -        if ($this->isEmpty() || $LngBounds->isEmpty()) 
        +        if ($this->isEmpty() || $lngBounds->isEmpty()) 
                 {
                     return false;
                 } 
                 else if ($this->_swLng > $this->_neLng) 
                 {
        -            return $LngBounds->getSw() > $LngBounds->getNe() 
        -                || $LngBounds->getSw() <= $this->_neLng 
        -                || $LngBounds->getNe() >= $this->_swLng;
        +            return $lngBounds->getSw() > $lngBounds->getNe() 
        +                || $lngBounds->getSw() <= $this->_neLng 
        +                || $lngBounds->getNe() >= $this->_swLng;
                 } 
        -        else if ($LngBounds->getSw() > $LngBounds->getNe()) 
        +        else if ($lngBounds->getSw() > $lngBounds->getNe()) 
                 {
        -            return $LngBounds->getSw() <= $this->_neLng || $LngBounds->getNe() >= $this->_swLng;
        +            return $lngBounds->getSw() <= $this->_neLng || $lngBounds->getNe() >= $this->_swLng;
                 } 
                 else 
                 {
        -            return $LngBounds->getSw() <= $this->_neLng && $LngBounds->getNe() >= $this->_swLng;
        +            return $lngBounds->getSw() <= $this->_neLng && $lngBounds->getNe() >= $this->_swLng;
                 }
             }
         
             /**
              * Check if this LngBounds is equal to another LngBounds.
              *
        -     * @param LngBounds $LngBounds The LngBounds object to compare.
        +     * @param LngBounds $lngBounds The LngBounds object to compare.
              * @return bool True if they are equal, false otherwise.
              */
        -    public function equals($LngBounds)
        +    public function equals($lngBounds)
             {
                 return $this->isEmpty() 
        -            ? $LngBounds->isEmpty() 
        -            : fmod(abs($LngBounds->getSw() - $this->_swLng), 360) 
        -                + fmod(abs($LngBounds->getNe() - $this->_neLng), 360) 
        +            ? $lngBounds->isEmpty() 
        +            : fmod(abs($lngBounds->getSw() - $this->_swLng), 360) 
        +                + fmod(abs($lngBounds->getNe() - $this->_neLng), 360) 
                         <= SphericalGeometry::EQUALS_MARGIN_ERROR;   
             }
         
        diff --git a/src/MagicDto.php b/src/MagicDto.php
        index b8109860..98e9ebdd 100644
        --- a/src/MagicDto.php
        +++ b/src/MagicDto.php
        @@ -135,17 +135,6 @@ public function loadData($data)
                 return $this;
             }
         
        -    private function isMyChild($object) {
        -        $reflection = new ReflectionClass($object);
        -        $parentClass = $reflection->getParentClass();
        -        if(!$parentClass)
        -        {
        -            return false;
        -        }
        -        return get_class($parentClass) == get_class(new self());
        -    }
        -    
        -
             /**
              * Loads XML data into the object.
              *
        diff --git a/src/Util/Database/PicoDatabaseUtilInterface.php b/src/Util/Database/PicoDatabaseUtilInterface.php
        index ea229551..9f5ac03a 100644
        --- a/src/Util/Database/PicoDatabaseUtilInterface.php
        +++ b/src/Util/Database/PicoDatabaseUtilInterface.php
        @@ -20,7 +20,7 @@
          * Implementations of this interface should consider validation and error handling to ensure data integrity 
          * and security during database operations.
          */
        -interface PicoDatabaseUtilInterface
        +interface PicoDatabaseUtilInterface //NOSONAR
         {
             public function getColumnList($database, $picoTableName);
             public function getAutoIncrementKey($tableInfo);
        diff --git a/src/Util/PicoParsedown.php b/src/Util/PicoParsedown.php
        index 6ac9a213..d10e6f29 100644
        --- a/src/Util/PicoParsedown.php
        +++ b/src/Util/PicoParsedown.php
        @@ -208,12 +208,12 @@ public function setSafeMode($safeMode)
              */
             protected function lines(array $lines) //NOSONAR
             {
        -        $CurrentBlock = null;
        +        $currentBlock = null;
         
                 foreach ($lines as $line) {
                     if (chop($line) === '') {
        -                if (isset($CurrentBlock)) {
        -                    $CurrentBlock['interrupted'] = true;
        +                if (isset($currentBlock)) {
        +                    $currentBlock['interrupted'] = true;
                         }
         
                         continue;
        @@ -248,16 +248,16 @@ protected function lines(array $lines) //NOSONAR
         
                     # ~
         
        -            if (isset($CurrentBlock['continuable'])) {
        -                $block = $this->{'block' . $CurrentBlock['type'] . 'Continue'}($line, $CurrentBlock);
        +            if (isset($currentBlock['continuable'])) {
        +                $block = $this->{'block' . $currentBlock['type'] . 'Continue'}($line, $currentBlock);
         
                         if (isset($block)) {
        -                    $CurrentBlock = $block;
        +                    $currentBlock = $block;
         
                             continue;
                         } else {
        -                    if ($this->isBlockCompletable($CurrentBlock['type'])) {
        -                        $CurrentBlock = $this->{'block' . $CurrentBlock['type'] . 'Complete'}($CurrentBlock);
        +                    if ($this->isBlockCompletable($currentBlock['type'])) {
        +                        $currentBlock = $this->{'block' . $currentBlock['type'] . 'Complete'}($currentBlock);
                             }
                         }
                     }
        @@ -280,13 +280,13 @@ protected function lines(array $lines) //NOSONAR
                     # ~
         
                     foreach ($blockTypes as $blockType) {
        -                $block = $this->{'block' . $blockType}($line, $CurrentBlock);
        +                $block = $this->{'block' . $blockType}($line, $currentBlock);
         
                         if (isset($block)) {
                             $block['type'] = $blockType;
         
                             if (!isset($block['identified'])) {
        -                        $Blocks[] = $CurrentBlock;
        +                        $blocks[] = $currentBlock;
         
                                 $block['identified'] = true;
                             }
        @@ -295,7 +295,7 @@ protected function lines(array $lines) //NOSONAR
                                 $block['continuable'] = true;
                             }
         
        -                    $CurrentBlock = $block;
        +                    $currentBlock = $block;
         
                             continue 2;
                         }
        @@ -303,34 +303,34 @@ protected function lines(array $lines) //NOSONAR
         
                     # ~
         
        -            if (isset($CurrentBlock) && !isset($CurrentBlock['type']) && !isset($CurrentBlock['interrupted'])) {
        -                $CurrentBlock['element']['text'] .= "\n" . $text;
        +            if (isset($currentBlock) && !isset($currentBlock['type']) && !isset($currentBlock['interrupted'])) {
        +                $currentBlock['element']['text'] .= "\n" . $text;
                     } else {
        -                $Blocks[] = $CurrentBlock;
        +                $blocks[] = $currentBlock;
         
        -                $CurrentBlock = $this->paragraph($line);
        +                $currentBlock = $this->paragraph($line);
         
        -                $CurrentBlock['identified'] = true;
        +                $currentBlock['identified'] = true;
                     }
                 }
         
                 # ~
         
        -        if (isset($CurrentBlock['continuable']) && $this->isBlockCompletable($CurrentBlock['type'])) {
        -            $CurrentBlock = $this->{'block' . $CurrentBlock['type'] . 'Complete'}($CurrentBlock);
        +        if (isset($currentBlock['continuable']) && $this->isBlockCompletable($currentBlock['type'])) {
        +            $currentBlock = $this->{'block' . $currentBlock['type'] . 'Complete'}($currentBlock);
                 }
         
                 # ~
         
        -        $Blocks[] = $CurrentBlock;
        +        $blocks[] = $currentBlock;
         
        -        unset($Blocks[0]);
        +        unset($blocks[0]);
         
                 # ~
         
                 $markup = '';
         
        -        foreach ($Blocks as $block) {
        +        foreach ($blocks as $block) {
                     if (isset($block['hidden'])) {
                         continue;
                     }
        @@ -349,12 +349,12 @@ protected function lines(array $lines) //NOSONAR
             /**
              * Check if the specified block type can be continued.
              *
        -     * @param string $Type The type of the block to check.
        +     * @param string $type The type of the block to check.
              * @return bool True if the block type can be continued, false otherwise.
              */
        -    protected function isBlockContinuable($Type)
        +    protected function isBlockContinuable($type)
             {
        -        return method_exists($this, 'block' . $Type . 'Continue');
        +        return method_exists($this, 'block' . $type . 'Continue');
             }
         
             /**
        @@ -362,12 +362,12 @@ protected function isBlockContinuable($Type)
              *
              * This method checks if a method exists for completing a block of the specified type.
              *
        -     * @param string $Type The block type to check.
        +     * @param string $type The block type to check.
              * @return bool True if the block type can be completed, false otherwise.
              */
        -    protected function isBlockCompletable($Type)
        +    protected function isBlockCompletable($type)
             {
        -        return method_exists($this, 'block' . $Type . 'Complete');
        +        return method_exists($this, 'block' . $type . 'Complete');
             }
         
             /**
        @@ -965,16 +965,16 @@ protected function blockReference($line)
                 {
                     $id = strtolower($matches[1]);
         
        -            $Data = array(
        +            $data = array(
                         'url' => $matches[2],
                         'title' => null,
                     );
         
                     if (isset($matches[3])) {
        -                $Data['title'] = $matches[3];
        +                $data['title'] = $matches[3];
                     }
         
        -            $this->definitionData['Reference'][$id] = $Data;
        +            $this->definitionData['Reference'][$id] = $data;
         
                     return array(
                         'hidden' => true,
        @@ -1030,7 +1030,7 @@ protected function blockTable($line, array $block = null) //NOSONAR
         
                     # ~
         
        -            $HeaderElements = array();
        +            $headerElements = array();
         
                     $header = $block['element']['text'];
         
        @@ -1042,7 +1042,7 @@ protected function blockTable($line, array $block = null) //NOSONAR
                     foreach ($headerCells as $index => $headerCell) {
                         $headerCell = trim($headerCell);
         
        -                $HeaderElement = array(
        +                $headerElement = array(
                             'name' => 'th',
                             'text' => $headerCell,
                             'handler' => 'line',
        @@ -1051,12 +1051,12 @@ protected function blockTable($line, array $block = null) //NOSONAR
                         if (isset($alignments[$index])) {
                             $alignment = $alignments[$index];
         
        -                    $HeaderElement['attributes'] = array(
        +                    $headerElement['attributes'] = array(
                                 'style' => 'text-align: ' . $alignment . ';',
                             );
                         }
         
        -                $HeaderElements[] = $HeaderElement;
        +                $headerElements[] = $headerElement;
                     }
         
                     # ~
        @@ -1084,7 +1084,7 @@ protected function blockTable($line, array $block = null) //NOSONAR
                     $block['element']['text'][0]['text'][] = array(
                         'name' => 'tr',
                         'handler' => 'elements',
        -                'text' => $HeaderElements,
        +                'text' => $headerElements,
                     );
         
                     return $block;
        @@ -1107,7 +1107,7 @@ protected function blockTableContinue($line, $block) //NOSONAR
                 }
         
                 if ($line['text'][0] === '|' || strpos($line['text'], '|')) {
        -            $Elements = array();
        +            $elements = array();
         
                     $row = $line['text'];
         
        @@ -1131,13 +1131,13 @@ protected function blockTableContinue($line, $block) //NOSONAR
                             );
                         }
         
        -                $Elements[] = $element;
        +                $elements[] = $element;
                     }
         
                     $element = array(
                         'name' => 'tr',
                         'handler' => 'elements',
        -                'text' => $Elements,
        +                'text' => $elements,
                     );
         
                     $block['element']['text'][1]['text'][] = $element;
        @@ -1214,7 +1214,7 @@ public function line($text, $nonNestables = array()) //NOSONAR
         
                     $markerPosition = strpos($text, $marker);
         
        -            $Excerpt = array('text' => $excerpt, 'context' => $text);
        +            $excerpt = array('text' => $excerpt, 'context' => $text);
         
                     foreach ($this->inlineTypes[$marker] as $inlineType) {
                         # check to see if the current inline type is nestable in the current context
        @@ -1223,41 +1223,41 @@ public function line($text, $nonNestables = array()) //NOSONAR
                             continue;
                         }
         
        -                $Inline = $this->{'inline' . $inlineType}($Excerpt);
        +                $inline = $this->{'inline' . $inlineType}($excerpt);
         
        -                if (!isset($Inline)) {
        +                if (!isset($inline)) {
                             continue;
                         }
         
                         # makes sure that the inline belongs to "our" marker
         
        -                if (isset($Inline['position']) && $Inline['position'] > $markerPosition) {
        +                if (isset($inline['position']) && $inline['position'] > $markerPosition) {
                             continue;
                         }
         
                         # sets a default inline position
         
        -                if (!isset($Inline['position'])) {
        -                    $Inline['position'] = $markerPosition;
        +                if (!isset($inline['position'])) {
        +                    $inline['position'] = $markerPosition;
                         }
         
                         # cause the new element to 'inherit' our non nestables
         
                         foreach ($nonNestables as $non_nestable) {
        -                    $Inline['element']['nonNestables'][] = $non_nestable;
        +                    $inline['element']['nonNestables'][] = $non_nestable;
                         }
         
                         # the text that comes before the inline
        -                $unmarkedText = substr($text, 0, $Inline['position']);
        +                $unmarkedText = substr($text, 0, $inline['position']);
         
                         # compile the unmarked text
                         $markup .= $this->unmarkedText($unmarkedText);
         
                         # compile the inline
        -                $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
        +                $markup .= isset($inline['markup']) ? $inline['markup'] : $this->element($inline['element']);
         
                         # remove the examined text
        -                $text = substr($text, $Inline['position'] + $Inline['extent']);
        +                $text = substr($text, $inline['position'] + $inline['extent']);
         
                         continue 2;
                     }
        @@ -1281,14 +1281,14 @@ public function line($text, $nonNestables = array()) //NOSONAR
              *
              * This method checks for code markers and captures the code content.
              *
        -     * @param array $Excerpt The excerpt to process.
        +     * @param array $excerpt The excerpt to process.
              * @return array|null The inline code element or null if not applicable.
              */
        -    protected function inlineCode($Excerpt)
        +    protected function inlineCode($excerpt)
             {
        -        $marker = $Excerpt['text'][0];
        +        $marker = $excerpt['text'][0];
         
        -        if (preg_match('/^(' . $marker . '+)[ ]*(.+?)[ ]*(?') !== false && preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) {
        +        if (strpos($excerpt['text'], '>') !== false && preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $excerpt['text'], $matches)) {
                     $url = $matches[1];
         
                     if (!isset($matches[2])) {
        @@ -1337,20 +1337,20 @@ protected function inlineEmailTag($Excerpt)
              *
              * This method checks for markers indicating strong or emphasized text.
              *
        -     * @param array $Excerpt The excerpt to process.
        +     * @param array $excerpt The excerpt to process.
              * @return array|null The emphasis element or null if not applicable.
              */
        -    protected function inlineEmphasis($Excerpt)
        +    protected function inlineEmphasis($excerpt)
             {
        -        if (!isset($Excerpt['text'][1])) {
        +        if (!isset($excerpt['text'][1])) {
                     return null;
                 }
         
        -        $marker = $Excerpt['text'][0];
        +        $marker = $excerpt['text'][0];
         
        -        if ($Excerpt['text'][1] === $marker && preg_match($this->strongRegex[$marker], $Excerpt['text'], $matches)) {
        +        if ($excerpt['text'][1] === $marker && preg_match($this->strongRegex[$marker], $excerpt['text'], $matches)) {
                     $emphasis = 'strong';
        -        } elseif (preg_match($this->emRegex[$marker], $Excerpt['text'], $matches)) {
        +        } elseif (preg_match($this->emRegex[$marker], $excerpt['text'], $matches)) {
                     $emphasis = 'em';
                 } else {
                     return null;
        @@ -1371,14 +1371,14 @@ protected function inlineEmphasis($Excerpt)
              *
              * This method checks for special characters that are preceded by a backslash.
              *
        -     * @param array $Excerpt The excerpt to process.
        +     * @param array $excerpt The excerpt to process.
              * @return array|null The escaped character element or null if not applicable.
              */
        -    protected function inlineEscapeSequence($Excerpt)
        +    protected function inlineEscapeSequence($excerpt)
             {
        -        if (isset($Excerpt['text'][1]) && in_array($Excerpt['text'][1], $this->specialCharacters)) {
        +        if (isset($excerpt['text'][1]) && in_array($excerpt['text'][1], $this->specialCharacters)) {
                     return array(
        -                'markup' => $Excerpt['text'][1],
        +                'markup' => $excerpt['text'][1],
                         'extent' => 2,
                     );
                 }
        @@ -1389,48 +1389,48 @@ protected function inlineEscapeSequence($Excerpt)
              *
              * This method identifies image links formatted with brackets and captures the source and alt text.
              *
        -     * @param array $Excerpt The excerpt to process.
        +     * @param array $excerpt The excerpt to process.
              * @return array|null The image element or null if not applicable.
              */
        -    protected function inlineImage($Excerpt)
        +    protected function inlineImage($excerpt)
             {
        -        if (!isset($Excerpt['text'][1]) || $Excerpt['text'][1] !== '[') {
        +        if (!isset($excerpt['text'][1]) || $excerpt['text'][1] !== '[') {
                     return null;
                 }
         
        -        $Excerpt['text'] = substr($Excerpt['text'], 1);
        +        $excerpt['text'] = substr($excerpt['text'], 1);
         
        -        $Link = $this->inlineLink($Excerpt);
        +        $link = $this->inlineLink($excerpt);
         
        -        if ($Link === null) {
        +        if ($link === null) {
                     return null;
                 }
         
        -        $Inline = array(
        -            'extent' => $Link['extent'] + 1,
        +        $inline = array(
        +            'extent' => $link['extent'] + 1,
                     'element' => array(
                         'name' => 'img',
                         'attributes' => array(
        -                    'src' => $Link['element']['attributes']['href'],
        -                    'alt' => $Link['element']['text'],
        +                    'src' => $link['element']['attributes']['href'],
        +                    'alt' => $link['element']['text'],
                         ),
                     ),
                 );
         
        -        $Inline['element']['attributes'] += $Link['element']['attributes'];
        +        $inline['element']['attributes'] += $link['element']['attributes'];
         
        -        unset($Inline['element']['attributes']['href']);
        +        unset($inline['element']['attributes']['href']);
         
        -        return $Inline;
        +        return $inline;
             }
         
             /**
              * Processes inline links in the provided text.
              *
        -     * @param array $Excerpt Contains the text to be processed.
        +     * @param array $excerpt Contains the text to be processed.
              * @return array|null Returns an array containing the extent of the match and the link element, or null if no match is found.
              */
        -    protected function inlineLink($Excerpt)
        +    protected function inlineLink($excerpt)
             {
                 $element = array(
                     'name' => 'a',
        @@ -1445,7 +1445,7 @@ protected function inlineLink($Excerpt)
         
                 $extent = 0;
         
        -        $remainder = $Excerpt['text'];
        +        $remainder = $excerpt['text'];
         
                 if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) //NOSONAR
                 {
        @@ -1481,10 +1481,10 @@ protected function inlineLink($Excerpt)
                         return null;
                     }
         
        -            $Definition = $this->definitionData['Reference'][$definition];
        +            $definition = $this->definitionData['Reference'][$definition];
         
        -            $element['attributes']['href'] = $Definition['url'];
        -            $element['attributes']['title'] = $Definition['title'];
        +            $element['attributes']['href'] = $definition['url'];
        +            $element['attributes']['title'] = $definition['title'];
                 }
         
                 return array(
        @@ -1496,16 +1496,16 @@ protected function inlineLink($Excerpt)
             /**
              * Processes inline HTML markup in the provided text.
              *
        -     * @param array $Excerpt Contains the text to be processed.
        +     * @param array $excerpt Contains the text to be processed.
              * @return array|null Returns an array with the markup and its extent, or null if no valid markup is found.
              */
        -    protected function inlineMarkup($Excerpt) //NOSONAR
        +    protected function inlineMarkup($excerpt) //NOSONAR
             {
        -        if ($this->markupEscaped || $this->safeMode || strpos($Excerpt['text'], '>') === false) {
        +        if ($this->markupEscaped || $this->safeMode || strpos($excerpt['text'], '>') === false) {
                     return null;
                 }
         
        -        if ($Excerpt['text'][1] === '/' && preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) //NOSONAR
        +        if ($excerpt['text'][1] === '/' && preg_match('/^<\/\w[\w-]*[ ]*>/s', $excerpt['text'], $matches)) //NOSONAR
                 {
                     return array(
                         'markup' => $matches[0],
        @@ -1513,7 +1513,7 @@ protected function inlineMarkup($Excerpt) //NOSONAR
                     );
                 }
         
        -        if ($Excerpt['text'][1] === '!' && preg_match('/^/s', $Excerpt['text'], $matches)) //NOSONAR
        +        if ($excerpt['text'][1] === '!' && preg_match('/^/s', $excerpt['text'], $matches)) //NOSONAR
                 {
                     return array(
                         'markup' => $matches[0],
        @@ -1521,7 +1521,7 @@ protected function inlineMarkup($Excerpt) //NOSONAR
                     );
                 }
         
        -        if ($Excerpt['text'][1] !== ' ' && preg_match('/^<\w[\w-]*(?:[ ]*' . $this->regexHtmlAttribute . ')*[ ]*\/?>/s', $Excerpt['text'], $matches)) {
        +        if ($excerpt['text'][1] !== ' ' && preg_match('/^<\w[\w-]*(?:[ ]*' . $this->regexHtmlAttribute . ')*[ ]*\/?>/s', $excerpt['text'], $matches)) {
                     return array(
                         'markup' => $matches[0],
                         'extent' => strlen($matches[0]),
        @@ -1532,23 +1532,23 @@ protected function inlineMarkup($Excerpt) //NOSONAR
             /**
              * Processes inline special characters in the provided text.
              *
        -     * @param array $Excerpt Contains the text to be processed.
        +     * @param array $excerpt Contains the text to be processed.
              * @return array|null Returns an array with the escaped character and its extent, or null if no special character is found.
              */
        -    protected function inlineSpecialCharacter($Excerpt)
        +    protected function inlineSpecialCharacter($excerpt)
             {
        -        if ($Excerpt['text'][0] === '&' && !preg_match('/^&#?\w+;/', $Excerpt['text'])) {
        +        if ($excerpt['text'][0] === '&' && !preg_match('/^&#?\w+;/', $excerpt['text'])) {
                     return array(
                         'markup' => '&',
                         'extent' => 1,
                     );
                 }
         
        -        $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
        +        $specialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
         
        -        if (isset($SpecialCharacter[$Excerpt['text'][0]])) {
        +        if (isset($specialCharacter[$excerpt['text'][0]])) {
                     return array(
        -                'markup' => '&' . $SpecialCharacter[$Excerpt['text'][0]] . ';',
        +                'markup' => '&' . $specialCharacter[$excerpt['text'][0]] . ';',
                         'extent' => 1,
                     );
                 }
        @@ -1557,16 +1557,16 @@ protected function inlineSpecialCharacter($Excerpt)
             /**
              * Processes inline strikethrough text in the provided text.
              *
        -     * @param array $Excerpt Contains the text to be processed.
        +     * @param array $excerpt Contains the text to be processed.
              * @return array|null Returns an array with the extent of the strikethrough and the corresponding element, or null if no match is found.
              */
        -    protected function inlineStrikethrough($Excerpt)
        +    protected function inlineStrikethrough($excerpt)
             {
        -        if (!isset($Excerpt['text'][1])) {
        +        if (!isset($excerpt['text'][1])) {
                     return null;
                 }
         
        -        if ($Excerpt['text'][1] === '~' && preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) {
        +        if ($excerpt['text'][1] === '~' && preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $excerpt['text'], $matches)) {
                     return array(
                         'extent' => strlen($matches[0]),
                         'element' => array(
        @@ -1581,16 +1581,16 @@ protected function inlineStrikethrough($Excerpt)
             /**
              * Processes inline URLs in the provided text.
              *
        -     * @param array $Excerpt Contains the text to be processed.
        +     * @param array $excerpt Contains the text to be processed.
              * @return array|null Returns an array with the extent and URL element, or null if no valid URL is found.
              */
        -    protected function inlineUrl($Excerpt)
        +    protected function inlineUrl($excerpt)
             {
        -        if ($this->urlsLinked !== true || !isset($Excerpt['text'][2]) || $Excerpt['text'][2] !== '/') {
        +        if ($this->urlsLinked !== true || !isset($excerpt['text'][2]) || $excerpt['text'][2] !== '/') {
                     return null;
                 }
         
        -        if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) //NOSONAR
        +        if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) //NOSONAR
                 {
                     $url = $matches[0][0];
         
        @@ -1612,12 +1612,12 @@ protected function inlineUrl($Excerpt)
             /**
              * Processes inline URL tags formatted in angle brackets.
              *
        -     * @param array $Excerpt Contains the text to be processed.
        +     * @param array $excerpt Contains the text to be processed.
              * @return array|null Returns an array with the extent and URL element, or null if no valid tag is found.
              */
        -    protected function inlineUrlTag($Excerpt)
        +    protected function inlineUrlTag($excerpt)
             {
        -        if (strpos($Excerpt['text'], '>') !== false && preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) {
        +        if (strpos($excerpt['text'], '>') !== false && preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $excerpt['text'], $matches)) {
                     $url = $matches[1];
         
                     return array(
        @@ -1714,14 +1714,14 @@ protected function element(array $element) //NOSONAR
             /**
              * Generates HTML markup for multiple elements.
              *
        -     * @param array $Elements An array of elements to be rendered as HTML.
        +     * @param array $elements An array of elements to be rendered as HTML.
              * @return string The generated HTML markup for all elements.
              */
        -    protected function elements(array $Elements)
        +    protected function elements(array $elements)
             {
                 $markup = '';
         
        -        foreach ($Elements as $element) {
        +        foreach ($elements as $element) {
                     $markup .= "\n" . $this->element($element);
                 }
         
        
        From 1ea47e4b9a8c992451386de1c881f1b934d599a8 Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 14:28:24 +0700
        Subject: [PATCH 14/55] Update PicoDataLabels.php
        
        ---
         src/DataLabel/PicoDataLabels.php | 3 ++-
         1 file changed, 2 insertions(+), 1 deletion(-)
        
        diff --git a/src/DataLabel/PicoDataLabels.php b/src/DataLabel/PicoDataLabels.php
        index 6941f728..bedac271 100644
        --- a/src/DataLabel/PicoDataLabels.php
        +++ b/src/DataLabel/PicoDataLabels.php
        @@ -41,7 +41,8 @@ public function append($data)
              */
             public function generate()
             {
        -        foreach ($this->data as $data) {
        +        foreach ($this->data as $data) //NOSONAR
        +        {
                     // Implementation for processing each data label goes here
                 }
             }
        
        From d18b51f11094d90a4012c52c2e36ea8d3f8c4123 Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 15:31:03 +0700
        Subject: [PATCH 15/55] Update docblock
        
        ---
         src/Request/InputCookie.php     |  26 +++++---
         src/Request/PicoRequestBase.php | 102 +++++++++++++++++---------------
         2 files changed, 71 insertions(+), 57 deletions(-)
        
        diff --git a/src/Request/InputCookie.php b/src/Request/InputCookie.php
        index 5d283feb..724280b3 100644
        --- a/src/Request/InputCookie.php
        +++ b/src/Request/InputCookie.php
        @@ -6,6 +6,8 @@
         
         /**
          * Class for handling input from cookies.
        + * This class provides functionality to retrieve and parse data from the global $_COOKIE array.
        + * It supports options for recursively converting objects and parsing specific types like null and boolean values.
          * 
          * @author Kamshory
          * @package MagicObject\Request
        @@ -16,16 +18,18 @@ class InputCookie extends PicoRequestBase {
             /**
              * Indicates whether to recursively convert all objects.
              *
        -     * @var boolean
        +     * @var bool
              */
             private $_recursive = false; // NOSONAR
         
             /**
              * Constructor for the InputCookie class.
        +     * Initializes the InputCookie instance, optionally setting flags for recursive object conversion, 
        +     * parsing null and boolean values, and forcing scalar value retrieval.
              *
        -     * @param bool $recursive Flag to indicate if all objects should be converted recursively.
        -     * @param bool $parseNullAndBool Flag to indicate whether to parse NULL and BOOL values.
        -     * @param bool $forceScalar Flag to indicate if only scalar values should be retrieved.
        +     * @param bool $recursive Flag to indicate if all objects should be converted recursively (default is false).
        +     * @param bool $parseNullAndBool Flag to indicate whether to parse NULL and BOOL values from cookies (default is false).
        +     * @param bool $forceScalar Flag to indicate if only scalar values should be retrieved (default is false).
              */
             public function __construct($recursive = false, $parseNullAndBool = false, $forceScalar = false)
             {
        @@ -40,9 +44,11 @@ public function __construct($recursive = false, $parseNullAndBool = false, $forc
             }
         
             /**
        -     * Get the global variable $_COOKIE.
        +     * Get the global $_COOKIE variable.
        +     * 
        +     * This method is a static wrapper to return the raw cookie data from the $_COOKIE superglobal.
              *
        -     * @return array The cookie data.
        +     * @return array The cookie data from the $_COOKIE superglobal.
              */
             public static function requestCookie()
             {
        @@ -50,15 +56,18 @@ public static function requestCookie()
             }
         
             /**
        -     * Override the loadData method to load cookie data.
        +     * Load cookie data into the object.
        +     * This method populates the object's properties with data from the provided cookie array.
        +     * It supports recursive object parsing if the _recursive flag is set.
              *
        -     * @param array $data Data to load into the object.
        +     * @param array $data Data to load into the object (usually from $_COOKIE).
              * @param bool $tolower Flag to indicate if the keys should be converted to lowercase (default is false).
              * @return self Returns the current instance for method chaining.
              */
             public function loadData($data, $tolower = false)
             {
                 if ($this->_recursive) {
        +            // Parse the data recursively if the recursive flag is set.
                     $genericObject = PicoObjectParser::parseJsonRecursive($data);
                     if ($genericObject !== null) {
                         $values = $genericObject->valueArray();
        @@ -70,6 +79,7 @@ public function loadData($data, $tolower = false)
                         }
                     }
                 } else {
        +            // Load data without recursion.
                     parent::loadData($data);
                 }
                 return $this;
        diff --git a/src/Request/PicoRequestBase.php b/src/Request/PicoRequestBase.php
        index 30deac0d..d9272472 100644
        --- a/src/Request/PicoRequestBase.php
        +++ b/src/Request/PicoRequestBase.php
        @@ -9,9 +9,11 @@
         use ReflectionClass;
         use stdClass;
         
        +
         /**
        - * Base class for handling requests.
        - * 
        + * Base class for handling HTTP requests, including input sanitization, data manipulation, 
        + * and request type checking (GET, POST, AJAX, etc.).
        + *
          * @author Kamshory
          * @package MagicObject\Database
          * @link https://github.com/Planetbiru/Request
        @@ -19,30 +21,31 @@
         class PicoRequestBase extends stdClass //NOSONAR
         {
             /**
        -     * Class parameters.
        +     * Class parameters parsed from annotations.
              *
              * @var array
              */
             private $classParams = array();
         
             /**
        -     * Flag to force input object as scalar.
        +     * Flag to force input data to be scalar only.
              *
        -     * @var boolean
        +     * @var bool
              */
             protected $forceScalar = false;
         
             /**
        -     * Flag for recursive processing.
        +     * Flag for recursive data processing.
              *
        -     * @var boolean
        +     * @var bool
              */
             protected $_recursive = false;
         
             /**
        -     * Constructor
        +     * Constructor to initialize the request handler and process class annotations.
              *
        -     * @param bool $forceScalar Indicates whether to only accept scalar values.
        +     * @param bool $forceScalar Indicates whether to accept only scalar values for data input.
        +     * @throws InvalidAnnotationException If there are invalid annotations in the class.
              */
             public function __construct($forceScalar = false)
             {
        @@ -61,10 +64,10 @@ public function __construct($forceScalar = false)
             }
         
             /**
        -     * Load data into the object.
        +     * Load data into the object, transforming keys to camelCase (optional).
              *
        -     * @param mixed $data Data to be loaded.
        -     * @param bool $tolower Flag to transform keys to lowercase.
        +     * @param mixed $data Data to be loaded (can be an array or object).
        +     * @param bool $tolower Flag indicating whether to convert keys to lowercase before loading.
              */
             public function loadData($data, $tolower = false)
             {
        @@ -81,10 +84,10 @@ public function loadData($data, $tolower = false)
             }
         
             /**
        -     * Set the value of a property.
        +     * Set a property value dynamically on the object using camelCase notation.
              *
        -     * @param string $propertyName Name of the property.
        -     * @param mixed|null $propertyValue Value of the property.
        +     * @param string $propertyName Name of the property to set.
        +     * @param mixed $propertyValue Value to assign to the property.
              * @return self
              */
             public function set($propertyName, $propertyValue)
        @@ -95,10 +98,10 @@ public function set($propertyName, $propertyValue)
             }
         
             /**
        -     * Get the value of a property.
        +     * Get a property value dynamically from the object.
              *
        -     * @param string $propertyName Name of the property.
        -     * @param array|null $params Parameters for filtering.
        +     * @param string $propertyName Name of the property to retrieve.
        +     * @param array|null $params Optional parameters for filtering the value.
              * @return mixed|null
              */
             public function get($propertyName, $params = null)
        @@ -129,9 +132,9 @@ public function get($propertyName, $params = null)
             }
         
             /**
        -     * Get the value of the object.
        +     * Get the values of all properties as an object (optionally in snake_case).
              *
        -     * @param bool $snakeCase Flag to define naming strategy.
        +     * @param bool $snakeCase Flag to convert property names to snake_case.
              * @return stdClass
              */
             public function value($snakeCase = false)
        @@ -159,10 +162,10 @@ public function value($snakeCase = false)
             }
         
             /**
        -     * Get a list of properties.
        +     * Retrieve a list of properties defined in the class, optionally as an array of property names.
              *
        -     * @param bool $reflectSelf Flag to indicate if class reflection should be used.
        -     * @param bool $asArrayProps Flag to return properties as an array.
        +     * @param bool $reflectSelf Flag to indicate whether to include only properties of the current class (not inherited).
        +     * @param bool $asArrayProps Flag to return properties as an array of names.
              * @return array
              */
             protected function propertyList($reflectSelf = false, $asArrayProps = false)
        @@ -196,12 +199,12 @@ function($property) use($class)
             }
         
             /**
        -     * Filter input data.
        +     * Filter input data from global variables (GET, POST, etc.) according to the specified filter type.
              *
        -     * @param int $type Request type.
        -     * @param string $variableName Name of the variable.
        -     * @param int $filter Filter type.
        -     * @param bool $escapeSQL Flag to escape SQL.
        +     * @param int $type The type of input (e.g., INPUT_GET, INPUT_POST).
        +     * @param string $variableName The name of the variable to filter.
        +     * @param int $filter The filter type to apply (e.g., FILTER_SANITIZE_EMAIL).
        +     * @param bool $escapeSQL Flag to escape SQL-specific characters.
              * @return mixed
              */
             public function filterInput($type, $variableName, $filter = PicoFilterConstant::FILTER_DEFAULT, $escapeSQL=false) // NOSONAR
        @@ -230,13 +233,13 @@ public function filterInput($type, $variableName, $filter = PicoFilterConstant::
             }
         
             /**
        -     * Filter a value based on the specified criteria.
        +     * Filter a value (or nested values) based on the specified filter type and optional flags.
              *
              * @param mixed $val The value to be filtered.
        -     * @param int $filter The filter type.
        -     * @param bool $escapeSQL Flag to escape SQL.
        +     * @param int $filter The filter type to apply (e.g., FILTER_SANITIZE_URL).
        +     * @param bool $escapeSQL Flag to escape SQL-specific characters.
              * @param bool $nullIfEmpty Flag to return null if the value is empty.
        -     * @param bool $requireScalar Flag to require scalar values only.
        +     * @param bool $requireScalar Flag to require scalar values.
              * @return mixed|null
              */
             public function filterValue($val, $filter = PicoFilterConstant::FILTER_DEFAULT, $escapeSQL = false, $nullIfEmpty = false, $requireScalar = false)
        @@ -273,11 +276,11 @@ public function filterValue($val, $filter = PicoFilterConstant::FILTER_DEFAULT,
             }
         
             /**
        -     * Filter a single value based on the specified criteria.
        +     * Filter a single value based on the specified filter type, applying specific sanitization rules.
              *
              * @param mixed $val The value to be filtered.
        -     * @param int $filter The filter type.
        -     * @param bool $escapeSQL Flag to escape SQL.
        +     * @param int $filter The filter type to apply (e.g., FILTER_SANITIZE_NUMBER_INT).
        +     * @param bool $escapeSQL Flag to escape SQL-specific characters.
              * @param bool $nullIfEmpty Flag to return null if the value is empty.
              * @return mixed
              */
        @@ -419,9 +422,9 @@ public function filterValueSingle($val, $filter = PicoFilterConstant::FILTER_DEF
             }
         
             /**
        -     * Add slashes to a string.
        +     * Add escape slashes to a string to protect against SQL injection or special character issues.
              *
        -     * @param string $input The input value.
        +     * @param string $input The input string to escape.
              * @return string
              */
             public function addslashes($input)
        @@ -430,10 +433,11 @@ public function addslashes($input)
             }
         
             /**
        -     * Get the value from a formatted number.
        +     * Format and return a numeric value by considering application-specific settings for decimal 
        +     * and thousand separators.
              *
        -     * @param stdClass|MagicObject $cfg Configuration object.
        -     * @param mixed $input Input value.
        +     * @param stdClass|MagicObject $cfg Configuration object containing separators.
        +     * @param mixed $input The input value to format.
              * @return float
              */
             public function _getValue($cfg, $input)
        @@ -468,7 +472,7 @@ public function _getValue($cfg, $input)
             /**
              * Check if the request is a GET request.
              *
        -     * @return bool
        +     * @return bool True if the request method is GET, false otherwise.
              */
             public function isGet()
             {
        @@ -478,7 +482,7 @@ public function isGet()
             /**
              * Check if the request is a POST request.
              *
        -     * @return bool
        +     * @return bool True if the request method is POST, false otherwise.
              */
             public function isPost()
             {
        @@ -488,7 +492,7 @@ public function isPost()
             /**
              * Check if the request is an AJAX request.
              *
        -     * @return bool
        +     * @return bool True if the request is an AJAX request, false otherwise.
              */
             public function isAjax()
             {
        @@ -496,9 +500,9 @@ public function isAjax()
             }
         
             /**
        -     * Retrieve the HTTP method used for the request.
        +     * Retrieve the HTTP method used for the current request.
              *
        -     * @return string
        +     * @return string The HTTP method (e.g., GET, POST).
              */
             public function getHttpMethod()
             {
        @@ -506,9 +510,9 @@ public function getHttpMethod()
             }
         
             /**
        -     * Retrieve the user agent of the request.
        +     * Retrieve the user agent string from the request headers.
              *
        -     * @return string
        +     * @return string The user agent string.
              */
             public function getUserAgent()
             {
        @@ -516,9 +520,9 @@ public function getUserAgent()
             }
         
             /**
        -     * Retrieve the client IP address.
        +     * Retrieve the client's IP address from the request headers.
              *
        -     * @return string
        +     * @return string The client's IP address.
              */
             public function getClientIp()
             {
        
        From 605394fce0ecc4b9c18f349ada1460a634802c89 Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 15:34:15 +0700
        Subject: [PATCH 16/55] Update InputServer.php
        
        ---
         src/Request/InputServer.php | 56 ++++++++++++++++++-------------------
         1 file changed, 28 insertions(+), 28 deletions(-)
        
        diff --git a/src/Request/InputServer.php b/src/Request/InputServer.php
        index ffadf70d..d47aed30 100644
        --- a/src/Request/InputServer.php
        +++ b/src/Request/InputServer.php
        @@ -10,34 +10,34 @@
          * address, and script name.
          *
          * Available methods:
        - * - getPhpSelf() returns $_SERVER['PHP_SELF']
        - * - getGatewayInterface() returns $_SERVER['GATEWAY_INTERFACE']
        - * - getServerAddr() returns $_SERVER['SERVER_ADDR']
        - * - getScriptName() returns $_SERVER['SCRIPT_NAME']
        - * - getServerSoftware() returns $_SERVER['SERVER_SOFTWARE']
        - * - getServerProtocol() returns $_SERVER['SERVER_PROTOCOL']
        - * - getRequestMethod() returns $_SERVER['REQUEST_METHOD']
        - * - getRequestTime() returns $_SERVER['REQUEST_TIME']
        - * - getRequestTimeFloat() returns $_SERVER['REQUEST_TIME_FLOAT']
        - * - getQueryString() returns $_SERVER['QUERY_STRING']
        - * - getDocumentRoot() returns $_SERVER['DOCUMENT_ROOT']
        - * - getHttps() returns $_SERVER['HTTPS']
        - * - getRemoteAddr() returns $_SERVER['REMOTE_ADDR']
        - * - getRemotePort() returns $_SERVER['REMOTE_PORT']
        - * - getRemoteUser() returns $_SERVER['REMOTE_USER']
        - * - getRedirectRemoteUser() returns $_SERVER['REDIRECT_REMOTE_USER']
        - * - getScriptFilename() returns $_SERVER['SCRIPT_FILENAME']
        - * - getServerAdmin() returns $_SERVER['SERVER_ADMIN']
        - * - getServerPort() returns $_SERVER['SERVER_PORT']
        - * - getServerSignature() returns $_SERVER['SERVER_SIGNATURE']
        - * - getPathTranslated() returns $_SERVER['PATH_TRANSLATED']
        - * - getRequestUri() returns $_SERVER['REQUEST_URI']
        - * - getPhpAuthDigest() returns $_SERVER['PHP_AUTH_DIGEST']
        - * - getPhpAuthUser() returns $_SERVER['PHP_AUTH_USER']
        - * - getPhpAuthPw() returns $_SERVER['PHP_AUTH_PW']
        - * - getAuthType() returns $_SERVER['AUTH_TYPE']
        - * - getPathInfo() returns $_SERVER['PATH_INFO']
        - * - getOrigPathInfo() returns $_SERVER['ORIG_PATH_INFO']
        + * - `getPhpSelf()` returns `$_SERVER['PHP_SELF']`
        + * - `getGatewayInterface()` returns `$_SERVER['GATEWAY_INTERFACE']`
        + * - `getServerAddr()` returns `$_SERVER['SERVER_ADDR']`
        + * - `getScriptName()` returns `$_SERVER['SCRIPT_NAME']`
        + * - `getServerSoftware()` returns `$_SERVER['SERVER_SOFTWARE']`
        + * - `getServerProtocol()` returns `$_SERVER['SERVER_PROTOCOL']`
        + * - `getRequestMethod()` returns `$_SERVER['REQUEST_METHOD']`
        + * - `getRequestTime()` returns `$_SERVER['REQUEST_TIME']`
        + * - `getRequestTimeFloat()` returns `$_SERVER['REQUEST_TIME_FLOAT']`
        + * - `getQueryString()` returns `$_SERVER['QUERY_STRING']`
        + * - `getDocumentRoot()` returns `$_SERVER['DOCUMENT_ROOT']`
        + * - `getHttps()` returns `$_SERVER['HTTPS']`
        + * - `getRemoteAddr()` returns `$_SERVER['REMOTE_ADDR']`
        + * - `getRemotePort()` returns `$_SERVER['REMOTE_PORT']`
        + * - `getRemoteUser()` returns `$_SERVER['REMOTE_USER']`
        + * - `getRedirectRemoteUser()` returns `$_SERVER['REDIRECT_REMOTE_USER']`
        + * - `getScriptFilename()` returns `$_SERVER['SCRIPT_FILENAME']`
        + * - `getServerAdmin()` returns `$_SERVER['SERVER_ADMIN']`
        + * - `getServerPort()` returns `$_SERVER['SERVER_PORT']`
        + * - `getServerSignature()` returns `$_SERVER['SERVER_SIGNATURE']`
        + * - `getPathTranslated()` returns `$_SERVER['PATH_TRANSLATED']`
        + * - `getRequestUri()` returns `$_SERVER['REQUEST_URI']`
        + * - `getPhpAuthDigest()` returns `$_SERVER['PHP_AUTH_DIGEST']`
        + * - `getPhpAuthUser()` returns `$_SERVER['PHP_AUTH_USER']`
        + * - `getPhpAuthPw()` returns `$_SERVER['PHP_AUTH_PW']`
        + * - `getAuthType()` returns `$_SERVER['AUTH_TYPE']`
        + * - `getPathInfo()` returns `$_SERVER['PATH_INFO']`
        + * - `getOrigPathInfo()` returns `$_SERVER['ORIG_PATH_INFO']`
          *
          * @author Kamshory
          * @package MagicObject\Request
        
        From 0f905fe6431578767073fe58d441a70971154b2f Mon Sep 17 00:00:00 2001
        From: "Kamshory, MT" 
        Date: Sun, 10 Nov 2024 19:54:15 +0700
        Subject: [PATCH 17/55] Update Manual
        
        ---
         manual/includes/_database.md | 18 ++++++++++++++++++
         manual/index.html            | 21 +++++++++++++++++++++
         tutorial.md                  | 18 ++++++++++++++++++
         3 files changed, 57 insertions(+)
        
        diff --git a/manual/includes/_database.md b/manual/includes/_database.md
        index 2c35af3a..fc7766a7 100644
        --- a/manual/includes/_database.md
        +++ b/manual/includes/_database.md
        @@ -14,6 +14,24 @@
         -   **Callbacks**: Support for custom callback functions for query execution and debugging.
         -   **Unique ID Generation**: Generate unique identifiers for database records.
         
        +### Database Support
        +
        +MagicObject supports the following databases:
        +
        +1. **MySQL**
        +    One of the most popular open-source relational databases, known for its speed, reliability, and ease of use. MySQL is widely used in web applications and offers strong performance, security features, and support for SQL standards.
        +
        +2. **MariaDB**
        +    A fork of MySQL, created by the original developers of MySQL after concerns over Oracle’s acquisition of MySQL. MariaDB is designed to maintain compatibility with MySQL while adding new features and optimizations. It is fully open-source and highly regarded for its performance and stability.
        +
        +3. **PostgreSQL**
        +    A powerful, open-source relational database system known for its robustness, SQL compliance, and extensive feature set, including ACID compliance, JSON support, and advanced indexing mechanisms.
        +
        +4. **SQLite**
        +    A lightweight, serverless, self-contained SQL database engine that is highly portable. It is often used for embedded systems or small-scale applications due to its minimal setup and resource usage. Despite its simplicity, SQLite supports a wide range of SQL features and is widely used in mobile apps and other local storage scenarios.
        +
        +MagicObject’s compatibility with these databases enables flexible, scalable, and efficient data management across different platforms and environments.
        +
         ### 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.
        diff --git a/manual/index.html b/manual/index.html
        index bd634d02..98f6cdba 100644
        --- a/manual/index.html
        +++ b/manual/index.html
        @@ -2082,6 +2082,27 @@ 

        Features

      • Callbacks: Support for custom callback functions for query execution and debugging.
      • Unique ID Generation: Generate unique identifiers for database records.
      +

      Database Support

      +

      MagicObject supports the following databases:

      +
        +
      1. +

        MySQL +One of the most popular open-source relational databases, known for its speed, reliability, and ease of use. MySQL is widely used in web applications and offers strong performance, security features, and support for SQL standards.

        +
      2. +
      3. +

        MariaDB +A fork of MySQL, created by the original developers of MySQL after concerns over Oracle’s acquisition of MySQL. MariaDB is designed to maintain compatibility with MySQL while adding new features and optimizations. It is fully open-source and highly regarded for its performance and stability.

        +
      4. +
      5. +

        PostgreSQL +A powerful, open-source relational database system known for its robustness, SQL compliance, and extensive feature set, including ACID compliance, JSON support, and advanced indexing mechanisms.

        +
      6. +
      7. +

        SQLite +A lightweight, serverless, self-contained SQL database engine that is highly portable. It is often used for embedded systems or small-scale applications due to its minimal setup and resource usage. Despite its simplicity, SQLite supports a wide range of SQL features and is widely used in mobile apps and other local storage scenarios.

        +
      8. +
      +

      MagicObject’s compatibility with these databases enables flexible, scalable, and efficient data management across different platforms and environments.

      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;
      diff --git a/tutorial.md b/tutorial.md
      index 7914301f..66143c68 100644
      --- a/tutorial.md
      +++ b/tutorial.md
      @@ -2421,6 +2421,24 @@ This implementation provides a robust framework for session management in a PHP
       -   **Callbacks**: Support for custom callback functions for query execution and debugging.
       -   **Unique ID Generation**: Generate unique identifiers for database records.
       
      +### Database Support
      +
      +MagicObject supports the following databases:
      +
      +1. **MySQL**
      +    One of the most popular open-source relational databases, known for its speed, reliability, and ease of use. MySQL is widely used in web applications and offers strong performance, security features, and support for SQL standards.
      +
      +2. **MariaDB**
      +    A fork of MySQL, created by the original developers of MySQL after concerns over Oracle’s acquisition of MySQL. MariaDB is designed to maintain compatibility with MySQL while adding new features and optimizations. It is fully open-source and highly regarded for its performance and stability.
      +
      +3. **PostgreSQL**
      +    A powerful, open-source relational database system known for its robustness, SQL compliance, and extensive feature set, including ACID compliance, JSON support, and advanced indexing mechanisms.
      +
      +4. **SQLite**
      +    A lightweight, serverless, self-contained SQL database engine that is highly portable. It is often used for embedded systems or small-scale applications due to its minimal setup and resource usage. Despite its simplicity, SQLite supports a wide range of SQL features and is widely used in mobile apps and other local storage scenarios.
      +
      +MagicObject’s compatibility with these databases enables flexible, scalable, and efficient data management across different platforms and environments.
      +
       ### 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.
      
      From 9ba03c7b8f0c8e682ca8de0b77222b385dd07de0 Mon Sep 17 00:00:00 2001
      From: "Kamshory, MT" 
      Date: Sun, 10 Nov 2024 19:57:13 +0700
      Subject: [PATCH 18/55] Update manual
      
      ---
       manual/includes/_database.md |  4 ++++
       manual/index.html            | 16 ++++++++--------
       tutorial.md                  |  4 ++++
       3 files changed, 16 insertions(+), 8 deletions(-)
      
      diff --git a/manual/includes/_database.md b/manual/includes/_database.md
      index fc7766a7..e54760fc 100644
      --- a/manual/includes/_database.md
      +++ b/manual/includes/_database.md
      @@ -19,15 +19,19 @@
       MagicObject supports the following databases:
       
       1. **MySQL**
      +    
           One of the most popular open-source relational databases, known for its speed, reliability, and ease of use. MySQL is widely used in web applications and offers strong performance, security features, and support for SQL standards.
       
       2. **MariaDB**
      +    
           A fork of MySQL, created by the original developers of MySQL after concerns over Oracle’s acquisition of MySQL. MariaDB is designed to maintain compatibility with MySQL while adding new features and optimizations. It is fully open-source and highly regarded for its performance and stability.
       
       3. **PostgreSQL**
      +    
           A powerful, open-source relational database system known for its robustness, SQL compliance, and extensive feature set, including ACID compliance, JSON support, and advanced indexing mechanisms.
       
       4. **SQLite**
      +    
           A lightweight, serverless, self-contained SQL database engine that is highly portable. It is often used for embedded systems or small-scale applications due to its minimal setup and resource usage. Despite its simplicity, SQLite supports a wide range of SQL features and is widely used in mobile apps and other local storage scenarios.
       
       MagicObject’s compatibility with these databases enables flexible, scalable, and efficient data management across different platforms and environments.
      diff --git a/manual/index.html b/manual/index.html
      index 98f6cdba..08eeb538 100644
      --- a/manual/index.html
      +++ b/manual/index.html
      @@ -2086,20 +2086,20 @@ 

      Database Support

      MagicObject supports the following databases:

      1. -

        MySQL -One of the most popular open-source relational databases, known for its speed, reliability, and ease of use. MySQL is widely used in web applications and offers strong performance, security features, and support for SQL standards.

        +

        MySQL

        +

        One of the most popular open-source relational databases, known for its speed, reliability, and ease of use. MySQL is widely used in web applications and offers strong performance, security features, and support for SQL standards.

      2. -

        MariaDB -A fork of MySQL, created by the original developers of MySQL after concerns over Oracle’s acquisition of MySQL. MariaDB is designed to maintain compatibility with MySQL while adding new features and optimizations. It is fully open-source and highly regarded for its performance and stability.

        +

        MariaDB

        +

        A fork of MySQL, created by the original developers of MySQL after concerns over Oracle’s acquisition of MySQL. MariaDB is designed to maintain compatibility with MySQL while adding new features and optimizations. It is fully open-source and highly regarded for its performance and stability.

      3. -

        PostgreSQL -A powerful, open-source relational database system known for its robustness, SQL compliance, and extensive feature set, including ACID compliance, JSON support, and advanced indexing mechanisms.

        +

        PostgreSQL

        +

        A powerful, open-source relational database system known for its robustness, SQL compliance, and extensive feature set, including ACID compliance, JSON support, and advanced indexing mechanisms.

      4. -

        SQLite -A lightweight, serverless, self-contained SQL database engine that is highly portable. It is often used for embedded systems or small-scale applications due to its minimal setup and resource usage. Despite its simplicity, SQLite supports a wide range of SQL features and is widely used in mobile apps and other local storage scenarios.

        +

        SQLite

        +

        A lightweight, serverless, self-contained SQL database engine that is highly portable. It is often used for embedded systems or small-scale applications due to its minimal setup and resource usage. Despite its simplicity, SQLite supports a wide range of SQL features and is widely used in mobile apps and other local storage scenarios.

      MagicObject’s compatibility with these databases enables flexible, scalable, and efficient data management across different platforms and environments.

      diff --git a/tutorial.md b/tutorial.md index 66143c68..dc3f0756 100644 --- a/tutorial.md +++ b/tutorial.md @@ -2426,15 +2426,19 @@ This implementation provides a robust framework for session management in a PHP MagicObject supports the following databases: 1. **MySQL** + One of the most popular open-source relational databases, known for its speed, reliability, and ease of use. MySQL is widely used in web applications and offers strong performance, security features, and support for SQL standards. 2. **MariaDB** + A fork of MySQL, created by the original developers of MySQL after concerns over Oracle’s acquisition of MySQL. MariaDB is designed to maintain compatibility with MySQL while adding new features and optimizations. It is fully open-source and highly regarded for its performance and stability. 3. **PostgreSQL** + A powerful, open-source relational database system known for its robustness, SQL compliance, and extensive feature set, including ACID compliance, JSON support, and advanced indexing mechanisms. 4. **SQLite** + A lightweight, serverless, self-contained SQL database engine that is highly portable. It is often used for embedded systems or small-scale applications due to its minimal setup and resource usage. Despite its simplicity, SQLite supports a wide range of SQL features and is widely used in mobile apps and other local storage scenarios. MagicObject’s compatibility with these databases enables flexible, scalable, and efficient data management across different platforms and environments. From d780a157326f3079d220959c95f5564988a23979 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 19:59:59 +0700 Subject: [PATCH 19/55] Update --- manual/includes/_database.md | 2 +- manual/index.html | 2 +- tutorial.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manual/includes/_database.md b/manual/includes/_database.md index e54760fc..768f1fe0 100644 --- a/manual/includes/_database.md +++ b/manual/includes/_database.md @@ -265,7 +265,7 @@ public function query($sql, $params = null) - `array|null $params`: Optional parameters for the SQL query. **Returns**: PDOStatement object or `false` on failure. -##### Fetch a Single Result +#### Fetch a Single Result ```php public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null) diff --git a/manual/index.html b/manual/index.html index 08eeb538..c5038ac1 100644 --- a/manual/index.html +++ b/manual/index.html @@ -2304,7 +2304,7 @@

      Query Execution

    • array|null $params: Optional parameters for the SQL query. Returns: PDOStatement object or false on failure.
    -
    Fetch a Single Result
    +

    Fetch a Single Result

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

    Parameters:

      diff --git a/tutorial.md b/tutorial.md index dc3f0756..79482103 100644 --- a/tutorial.md +++ b/tutorial.md @@ -2672,7 +2672,7 @@ public function query($sql, $params = null) - `array|null $params`: Optional parameters for the SQL query. **Returns**: PDOStatement object or `false` on failure. -##### Fetch a Single Result +#### Fetch a Single Result ```php public function fetch($sql, $tentativeType = PDO::FETCH_ASSOC, $defaultValue = null, $params = null) From 77d1b789a6523ca51f7737d4c799c02f47796056 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 20:01:47 +0700 Subject: [PATCH 20/55] Update manual --- manual/includes/_download-file.md | 6 +++--- manual/index.html | 6 +++--- tutorial.md | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/manual/includes/_download-file.md b/manual/includes/_download-file.md index e5627abc..2aaf071f 100644 --- a/manual/includes/_download-file.md +++ b/manual/includes/_download-file.md @@ -1,11 +1,11 @@ ## Resumable File Download -### Namespace: +### Namespace `MagicObject\File` -### Description: +### Description The `PicoDownloadFile` class is designed to facilitate efficient file downloading in PHP, supporting **partial content** (range requests) for large files. It ensures that requested files exist, handles errors gracefully, and enables downloading in chunks to minimize server load and bandwidth consumption, particularly for large files. @@ -88,7 +88,7 @@ if($finished && file_exists($path)) } ``` -### Error Handling: +### Error Handling - **404 - File Not Found**: If the file does not exist at the specified path, a 404 error is returned. - **416 - Range Not Satisfiable**: If an invalid byte range is requested (e.g., the start byte is larger than the end byte), a 416 error is returned. diff --git a/manual/index.html b/manual/index.html index c5038ac1..eb6fc971 100644 --- a/manual/index.html +++ b/manual/index.html @@ -10684,9 +10684,9 @@

      Summary

      Resumable File Download

      -

      Namespace:

      +

      Namespace

      MagicObject\File

      -

      Description:

      +

      Description

      The PicoDownloadFile class is designed to facilitate efficient file downloading in PHP, supporting partial content (range requests) for large files. It ensures that requested files exist, handles errors gracefully, and enables downloading in chunks to minimize server load and bandwidth consumption, particularly for large files.

      The class supports the following:

        @@ -10743,7 +10743,7 @@

        Method

        { unlink($path); // Delete file when finish }
        -

        Error Handling:

        +

        Error Handling

        • 404 - File Not Found: If the file does not exist at the specified path, a 404 error is returned.
        • 416 - Range Not Satisfiable: If an invalid byte range is requested (e.g., the start byte is larger than the end byte), a 416 error is returned.
        • diff --git a/tutorial.md b/tutorial.md index 79482103..1e433804 100644 --- a/tutorial.md +++ b/tutorial.md @@ -12131,11 +12131,11 @@ This implementation offers a straightforward way to manage file uploads in PHP, ## Resumable File Download -### Namespace: +### Namespace `MagicObject\File` -### Description: +### Description The `PicoDownloadFile` class is designed to facilitate efficient file downloading in PHP, supporting **partial content** (range requests) for large files. It ensures that requested files exist, handles errors gracefully, and enables downloading in chunks to minimize server load and bandwidth consumption, particularly for large files. @@ -12218,7 +12218,7 @@ if($finished && file_exists($path)) } ``` -### Error Handling: +### Error Handling - **404 - File Not Found**: If the file does not exist at the specified path, a 404 error is returned. - **416 - Range Not Satisfiable**: If an invalid byte range is requested (e.g., the start byte is larger than the end byte), a 416 error is returned. From 403232b66eee1d0ba1025ee7465c3b06dd374760 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 20:18:55 +0700 Subject: [PATCH 21/55] Update PicoDatabaseQueryBuilder.php --- src/Database/PicoDatabaseQueryBuilder.php | 26 +++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Database/PicoDatabaseQueryBuilder.php b/src/Database/PicoDatabaseQueryBuilder.php index ca15702a..e0152617 100644 --- a/src/Database/PicoDatabaseQueryBuilder.php +++ b/src/Database/PicoDatabaseQueryBuilder.php @@ -586,6 +586,10 @@ public function startTransaction() if ($this->isMySql() || $this->isPgSql()) { return "START TRANSACTION"; } + else if($this->isSqlite()) + { + return "BEGIN TRANSACTION"; + } return null; } @@ -596,7 +600,7 @@ public function startTransaction() */ public function commit() { - if ($this->isMySql() || $this->isPgSql()) { + if ($this->isMySql() || $this->isPgSql() || $this->isSqlite()) { return "COMMIT"; } return null; @@ -609,7 +613,7 @@ public function commit() */ public function rollback() { - if ($this->isMySql() || $this->isPgSql()) { + if ($this->isMySql() || $this->isPgSql() || $this->isSqlite()) { return "ROLLBACK"; } return null; @@ -740,9 +744,13 @@ public function lastID() if ($this->isMySql()) { $this->buffer .= "LAST_INSERT_ID()\r\n"; } - if ($this->isPgSql()) { + else if ($this->isPgSql()) { $this->buffer .= "LASTVAL()\r\n"; } + else if ($this->isSqlite()) + { + $this->buffer .= "last_insert_rowid()"; + } return $this; } @@ -753,7 +761,7 @@ public function lastID() */ public function currentDate() { - if ($this->isMySql() || $this->isPgSql()) { + if ($this->isMySql() || $this->isPgSql() || $this->isSqlite()) { return "CURRENT_DATE"; } return null; @@ -766,7 +774,7 @@ public function currentDate() */ public function currentTime() { - if ($this->isMySql() || $this->isPgSql()) { + if ($this->isMySql() || $this->isPgSql() || $this->isSqlite()) { return "CURRENT_TIME"; } return null; @@ -779,7 +787,7 @@ public function currentTime() */ public function currentTimestamp() { - if ($this->isMySql() || $this->isPgSql()) { + if ($this->isMySql() || $this->isPgSql() || $this->isSqlite()) { return "CURRENT_TIMESTAMP"; } return null; @@ -793,6 +801,10 @@ public function currentTimestamp() */ public function now($precision = 0) { + if($this->isSqlite()) + { + return "CURRENT_TIMESTAMP"; + } if ($precision > 6) { $precision = 6; } @@ -853,7 +865,7 @@ public function toString() if ($this->limitOffset) { if ($this->isMySql()) { $sql .= "LIMIT " . $this->offset . ", " . $this->limit; - } elseif ($this->isPgSql()) { + } elseif ($this->isPgSql() || $this->isSqlite()) { $sql .= "LIMIT " . $this->limit . " OFFSET " . $this->offset; } } From bc5825094dc886f9b5ad3331154c05a2fca005fe Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 20:20:32 +0700 Subject: [PATCH 22/55] Update PicoDatabaseQueryBuilder.php --- src/Database/PicoDatabaseQueryBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/PicoDatabaseQueryBuilder.php b/src/Database/PicoDatabaseQueryBuilder.php index e0152617..7e048ff7 100644 --- a/src/Database/PicoDatabaseQueryBuilder.php +++ b/src/Database/PicoDatabaseQueryBuilder.php @@ -749,7 +749,7 @@ public function lastID() } else if ($this->isSqlite()) { - $this->buffer .= "last_insert_rowid()"; + $this->buffer .= "LAST_INSERT_ROWID()"; } return $this; } From 878e0be98a38d8d338b88a8d8451064e253ccfd8 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Sun, 10 Nov 2024 20:21:48 +0700 Subject: [PATCH 23/55] Update PicoDatabaseQueryBuilder.php --- src/Database/PicoDatabaseQueryBuilder.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Database/PicoDatabaseQueryBuilder.php b/src/Database/PicoDatabaseQueryBuilder.php index 53b37ec4..e0152617 100644 --- a/src/Database/PicoDatabaseQueryBuilder.php +++ b/src/Database/PicoDatabaseQueryBuilder.php @@ -98,7 +98,7 @@ public function isMySql() */ public function isPgSql() { - return strcasecmp($this->databaseType, PicoDatabaseType::DATABASE_TYPE_POSTGRESQL) == 0; + return strcasecmp($this->databaseType, PicoDatabaseType::DATABASE_TYPE_PGSQL) == 0; } /** @@ -642,7 +642,7 @@ public function escapeSQL($query) stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_SQLITE) !== false) { return str_replace(["\r", "\n"], ["\\r", "\\n"], addslashes($query)); } - if (stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_POSTGRESQL) !== false) { + if (stripos($this->databaseType, PicoDatabaseType::DATABASE_TYPE_PGSQL) !== false) { return str_replace(["\r", "\n"], ["\\r", "\\n"], $this->replaceQuote($query)); } return $query; @@ -749,7 +749,7 @@ public function lastID() } else if ($this->isSqlite()) { - $this->buffer .= "LAST_INSERT_ROWID()"; + $this->buffer .= "last_insert_rowid()"; } return $this; } From 88db545ca4e26a731a45a95429ec0eab7b22bd8c Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 07:03:02 +0700 Subject: [PATCH 24/55] Add Pagination and Sorting to Native Query --- README.md | 11 ++++- manual/includes/_native-query.md | 49 ++++++++++++++++++++ manual/index.html | 46 ++++++++++++++++++- src/Database/PicoDatabaseQueryBuilder.php | 56 +++++++++++++++++++++++ src/MagicObject.php | 43 +++++++++++++---- tests/caller.php | 27 +++++++---- tutorial.md | 49 ++++++++++++++++++++ 7 files changed, 261 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 79b106e1..f19302ec 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Additionally, MagicObject 2.1 allows users to parse table structures directly fr These utilities not only enhance efficiency but also provide a robust foundation for database development, allowing users to focus on building applications rather than wrestling with database compatibility issues. With MagicObject 2.1, database management becomes more intuitive and accessible, empowering developers to harness the full potential of their data. -# **Introducing PDO Support in MagicObject 2.7** +# **PDO Support in MagicObject 2.7** ## **Overview** @@ -137,6 +137,15 @@ While PDO is now an option for initializing **MagicObject**, it is used only in In **MagicObject 2.7**, when you pass a **PDO** connection object to the constructor, it is automatically converted into a **PicoDatabase** instance using the `PicoDatabase::fromPdo()` static method. This ensures that even though PDO is used to establish the initial connection, the object will still operate using **PicoDatabase** for all subsequent database operations. The constructor of **MagicObject** ensures that the database connection is properly initialized and the type of database is correctly detected based on the PDO driver. +# **Pageable and Sortable in Native Query in MagicObject 2.7** + +In **MagicObject version 2.7**, support for **pageable** and **sortable** functionality has been added to native queries. Previously, native queries did not support pagination and sorting directly. Instead, users had to manually include `SORT BY` and `LIMIT OFFSET` clauses in their queries, which made them less flexible. This approach was problematic because each Database Management System (DBMS) has its own syntax for writing queries, making it cumbersome to adapt queries for different platforms. + +With the introduction of pageable and sortable support in version 2.7, users can now easily pass **pagination** parameters using the `PicoPageable` type and **sorting** parameters using the `PicoSortable` type directly into their native queries. These parameters can be placed anywhere within the query, but it is recommended to position them either at the beginning or the end of the query for optimal readability and organization. + +This enhancement makes native queries more flexible and easier to maintain, as the logic for pagination and sorting is handled automatically, without requiring manual intervention for each DBMS. As a result, users can now write cleaner, more efficient, and database-agnostic native queries. + + # Tutorial diff --git a/manual/includes/_native-query.md b/manual/includes/_native-query.md index 8425921d..b0a574dd 100644 --- a/manual/includes/_native-query.md +++ b/manual/includes/_native-query.md @@ -63,6 +63,14 @@ Native query must be a function of a class that extends from the MagicObject cla Native queries can be created on entities used by the application. If in the previous version the entity only contained properties, then in version 2.0, the entity can also contain functions for native queries. However, entities in versions 1 and 2 both support functions but functions with native queries are only supported in version 2.0. +### Pagination and Sorting + +In **MagicObject version 2.7**, support for **pageable** and **sortable** functionality has been added to native queries. Previously, native queries did not support pagination and sorting directly. Instead, users had to manually include `SORT BY` and `LIMIT OFFSET` clauses in their queries, which made them less flexible. This approach was problematic because each Database Management System (DBMS) has its own syntax for writing queries, making it cumbersome to adapt queries for different platforms. + +With the introduction of pageable and sortable support in version 2.7, users can now easily pass **pagination** parameters using the `PicoPageable` type and **sorting** parameters using the `PicoSortable` type directly into their native queries. These parameters can be placed anywhere within the query, but it is recommended to position them either at the beginning or the end of the query for optimal readability and organization. + +This enhancement makes native queries more flexible and easier to maintain, as the logic for pagination and sorting is handled automatically, without requiring manual intervention for each DBMS. As a result, users can now write cleaner, more efficient, and database-agnostic native queries. + ### Debug Query MagicObject checks if the database connection has a debugging function for queries. If available, it sends the executed query along with the parameter values to this function, aiding users in identifying errors during query definition and execution. @@ -338,6 +346,28 @@ class Supervisor extends MagicObject // Call parent method to execute the query return $this->executeNativeQuery(); } + + /** + * Native query 13 + * + * This method will return a prepared statement for further operations if necessary. + * + * @param PicoPagebale $pageable + * @param PicoSortable $sortable + * @param bool $aktif The active status to filter results. + * @return MagicObject[] + * @query(" + SELECT supervisor.* + FROM supervisor + WHERE supervisor.supervisor_id in :supervisorId + AND supervisor.aktif = :aktif + ") + */ + public function native13($pageable, $sortable, $aktif) + { + // Call parent method to execute the query + return $this->executeNativeQuery(); + } } $obj = new Supervisor(null, $database); @@ -391,6 +421,25 @@ echo "Alamat: " . $native8->getTelepon() . "\r\n"; echo "Alamat: " . $native9[0]->getTelepon() . "\r\n"; echo "Alamat: " . $native10->getTelepon() . "\r\n"; echo "Alamat: " . $native11[0]->getTelepon() . "\r\n"; + + +$sortable = new PicoSortable(); +$sortable->addSortable(new PicoSort("nama", PicoSort::ORDER_TYPE_ASC)); +$pageable = new PicoPageable(new PicoPage(3, 20)); + +try +{ + $native13 = $obj->native13($pageable, $sortable, true); + echo "\r\nnative13:\r\n"; + foreach($native13 as $sup) + { + echo $sup."\r\n\r\n"; + } +} +catch(Exception $e) +{ + echo $e->getMessage(); +} ``` For the purpose of exporting large amounts of data, use the PDOStatement return type. PDOStatement allows users to read one by one and process it immediately, allowing PHP to release memory from the previous process. PHP does not need to store very large data in a variable. diff --git a/manual/index.html b/manual/index.html index 8c52c10e..ff2c4539 100644 --- a/manual/index.html +++ b/manual/index.html @@ -8603,6 +8603,10 @@

          Return Type

          If there is an error executing the database query, a PDOException will be thrown.

          Native query must be a function of a class that extends from the MagicObject class. In its definition, this method must call $this->executeNativeQuery(). MagicObject::executeNativeQuery() will analyze the docblock, parameters, and return type to process the given query. For ease and flexibility in writing code, the MagicObject::executeNativeQuery() function call does not pass parameters. Instead, the MagicObject::executeNativeQuery() function takes parameters from the calling function. Thus, changes to the parameters of the calling function do not require changes to the function definition.

          Native queries can be created on entities used by the application. If in the previous version the entity only contained properties, then in version 2.0, the entity can also contain functions for native queries. However, entities in versions 1 and 2 both support functions but functions with native queries are only supported in version 2.0.

          +

          Pagination and Sorting

          +

          In MagicObject version 2.7, support for pageable and sortable functionality has been added to native queries. Previously, native queries did not support pagination and sorting directly. Instead, users had to manually include SORT BY and LIMIT OFFSET clauses in their queries, which made them less flexible. This approach was problematic because each Database Management System (DBMS) has its own syntax for writing queries, making it cumbersome to adapt queries for different platforms.

          +

          With the introduction of pageable and sortable support in version 2.7, users can now easily pass pagination parameters using the PicoPageable type and sorting parameters using the PicoSortable type directly into their native queries. These parameters can be placed anywhere within the query, but it is recommended to position them either at the beginning or the end of the query for optimal readability and organization.

          +

          This enhancement makes native queries more flexible and easier to maintain, as the logic for pagination and sorting is handled automatically, without requiring manual intervention for each DBMS. As a result, users can now write cleaner, more efficient, and database-agnostic native queries.

          Debug Query

          MagicObject checks if the database connection has a debugging function for queries. If available, it sends the executed query along with the parameter values to this function, aiding users in identifying errors during query definition and execution.

          Example:

          @@ -8874,6 +8878,28 @@

          Debug Query

          // Call parent method to execute the query return $this->executeNativeQuery(); } + + /** + * Native query 13 + * + * This method will return a prepared statement for further operations if necessary. + * + * @param PicoPagebale $pageable + * @param PicoSortable $sortable + * @param bool $aktif The active status to filter results. + * @return MagicObject[] + * @query(" + SELECT supervisor.* + FROM supervisor + WHERE supervisor.supervisor_id in :supervisorId + AND supervisor.aktif = :aktif + ") + */ + public function native13($pageable, $sortable, $aktif) + { + // Call parent method to execute the query + return $this->executeNativeQuery(); + } } $obj = new Supervisor(null, $database); @@ -8926,7 +8952,25 @@

          Debug Query

          echo "Alamat: " . $native8->getTelepon() . "\r\n"; echo "Alamat: " . $native9[0]->getTelepon() . "\r\n"; echo "Alamat: " . $native10->getTelepon() . "\r\n"; -echo "Alamat: " . $native11[0]->getTelepon() . "\r\n";
          +echo "Alamat: " . $native11[0]->getTelepon() . "\r\n"; + +$sortable = new PicoSortable(); +$sortable->addSortable(new PicoSort("nama", PicoSort::ORDER_TYPE_ASC)); +$pageable = new PicoPageable(new PicoPage(3, 20)); + +try +{ + $native13 = $obj->native13($pageable, $sortable, true); + echo "\r\nnative13:\r\n"; + foreach($native13 as $sup) + { + echo $sup."\r\n\r\n"; + } +} +catch(Exception $e) +{ + echo $e->getMessage(); +}

          For the purpose of exporting large amounts of data, use the PDOStatement return type. PDOStatement allows users to read one by one and process it immediately, allowing PHP to release memory from the previous process. PHP does not need to store very large data in a variable.

          Example 12 shows how to use array parameters.

          For example:

          diff --git a/src/Database/PicoDatabaseQueryBuilder.php b/src/Database/PicoDatabaseQueryBuilder.php index e0152617..685ba43d 100644 --- a/src/Database/PicoDatabaseQueryBuilder.php +++ b/src/Database/PicoDatabaseQueryBuilder.php @@ -844,6 +844,62 @@ public function addQueryParameters($query) return $buffer; } + /** + * Adds pagination and sorting clauses to a native query string. + * + * This function appends the appropriate `ORDER BY` and `LIMIT OFFSET` + * clauses to the provided SQL query string based on the given pagination and sorting parameters. + * It supports various database management systems (DBMS) and adjusts the query syntax + * accordingly (e.g., for PostgreSQL, SQLite, MySQL, etc.). + * + * @param string $queryString The original SQL query string to which pagination and sorting will be added. + * @param PicoPageable|null $pageable The pagination parameters, or `null` if pagination is not required. + * @param PicoSortable|null $sortable The sorting parameters, or `null` if sorting is not required. + * + * @return string The modified SQL query string with added pagination and sorting clauses. + */ + public function addLimitOffset($queryString, $pageable, $sortable) + { + if(!isset($pageable) && !isset($sortable)) + { + return $queryString; + } + + $queryString = rtrim($queryString, " \r\n\t; "); + + if(isset($sortable)) + { + foreach($sortable->getSortable() as $sort) + { + $columnName = $sort->getSortBy(); + $sortType = $sort->getSortType(); + $sorts[] = $columnName . " " . $sortType; + } + if(!empty($sorts)) + { + $queryString .= "\r\nORDER BY ".implode(", ", $sorts); + } + } + if(isset($pageable)) + { + $limitOffset = $pageable->getOffsetLimit(); + $limit = $limitOffset->getLimit(); + $offset = $limitOffset->getOffset(); + if($this->isPgSql() || $this->isSqlite()) + { + // PostgeSQL and SQLite + $queryString .= "\r\nLIMIT $limit OFFSET $offset"; + } + else + { + // MariaDB and MySQL + $queryString .= "\r\nLIMIT $offset, $limit"; + } + } + + return $queryString; + } + /** * Get the current SQL query as a string. * diff --git a/src/MagicObject.php b/src/MagicObject.php index 4dec15ec..d08647d3 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -777,14 +777,23 @@ protected function executeNativeQuery() //NOSONAR } $params = []; - + $pageable = null; + $sortable = null; try { // Get database connection $pdo = $this->_database->getDatabaseConnection(); // Replace array foreach ($callerParamValues as $index => $paramValue) { - if (isset($callerParams[$index])) { + if($paramValue instanceof PicoPageable) + { + $pageable = $paramValue; + } + else if($paramValue instanceof PicoSortable) + { + $sortable = $paramValue; + } + else if (isset($callerParams[$index])) { // Format parameter name according to the query $paramName = $callerParams[$index]->getName(); if(is_array($paramValue)) @@ -793,21 +802,35 @@ protected function executeNativeQuery() //NOSONAR } } } + $queryBuilder = new PicoDatabaseQueryBuilder($this->_database->getDatabaseType()); + $queryString = $queryBuilder->addLimitOffset($queryString, $pageable, $sortable); $stmt = $pdo->prepare($queryString); + // Automatically bind each parameter foreach ($callerParamValues as $index => $paramValue) { if (isset($callerParams[$index])) { - // Format parameter name according to the query - $paramName = $callerParams[$index]->getName(); - if(!is_array($paramValue)) + if($paramValue instanceof PicoPageable) + { + // skip + } + else if($paramValue instanceof PicoSortable) { - $maped = $this->mapToPdoParamType($paramValue); - $paramType = $maped->type; - $paramValue = $maped->value; - $params[$paramName] = $paramValue; - $stmt->bindValue(":".$paramName, $paramValue, $paramType); + // skip + } + else + { + // Format parameter name according to the query + $paramName = $callerParams[$index]->getName(); + if(!is_array($paramValue)) + { + $maped = $this->mapToPdoParamType($paramValue); + $paramType = $maped->type; + $paramValue = $maped->value; + $params[$paramName] = $paramValue; + $stmt->bindValue(":".$paramName, $paramValue, $paramType); + } } } } diff --git a/tests/caller.php b/tests/caller.php index ce52c949..e26fb01d 100644 --- a/tests/caller.php +++ b/tests/caller.php @@ -1,6 +1,10 @@ executeNativeQuery(); @@ -323,11 +327,18 @@ public function native13($timeCreate, $aktif) echo "Alamat: " . $native11[0]->getTelepon() . "\r\n"; */ +$sortable = new PicoSortable(); +$sortable->addSortable(new PicoSort("nama", PicoSort::ORDER_TYPE_ASC)); +$pageable = new PicoPageable(new PicoPage(1, 2)); + try { -$native13 = $obj->native13([new DateTime('2025-03-27 15:23:47', new DateTimeZone($databaseCredential->getDatabase()->getTimeZone()))], true); -echo "\r\nnative13:\r\n"; -echo ($native13); + $native13 = $obj->native13($pageable, $sortable, true); + echo "\r\nnative13:\r\n"; + foreach($native13 as $sup) + { + echo $sup."\r\n\r\n"; + } } catch(Exception $e) { diff --git a/tutorial.md b/tutorial.md index 6283ea78..08ce1cd2 100644 --- a/tutorial.md +++ b/tutorial.md @@ -9751,6 +9751,14 @@ Native query must be a function of a class that extends from the MagicObject cla Native queries can be created on entities used by the application. If in the previous version the entity only contained properties, then in version 2.0, the entity can also contain functions for native queries. However, entities in versions 1 and 2 both support functions but functions with native queries are only supported in version 2.0. +### Pagination and Sorting + +In **MagicObject version 2.7**, support for **pageable** and **sortable** functionality has been added to native queries. Previously, native queries did not support pagination and sorting directly. Instead, users had to manually include `SORT BY` and `LIMIT OFFSET` clauses in their queries, which made them less flexible. This approach was problematic because each Database Management System (DBMS) has its own syntax for writing queries, making it cumbersome to adapt queries for different platforms. + +With the introduction of pageable and sortable support in version 2.7, users can now easily pass **pagination** parameters using the `PicoPageable` type and **sorting** parameters using the `PicoSortable` type directly into their native queries. These parameters can be placed anywhere within the query, but it is recommended to position them either at the beginning or the end of the query for optimal readability and organization. + +This enhancement makes native queries more flexible and easier to maintain, as the logic for pagination and sorting is handled automatically, without requiring manual intervention for each DBMS. As a result, users can now write cleaner, more efficient, and database-agnostic native queries. + ### Debug Query MagicObject checks if the database connection has a debugging function for queries. If available, it sends the executed query along with the parameter values to this function, aiding users in identifying errors during query definition and execution. @@ -10026,6 +10034,28 @@ class Supervisor extends MagicObject // Call parent method to execute the query return $this->executeNativeQuery(); } + + /** + * Native query 13 + * + * This method will return a prepared statement for further operations if necessary. + * + * @param PicoPagebale $pageable + * @param PicoSortable $sortable + * @param bool $aktif The active status to filter results. + * @return MagicObject[] + * @query(" + SELECT supervisor.* + FROM supervisor + WHERE supervisor.supervisor_id in :supervisorId + AND supervisor.aktif = :aktif + ") + */ + public function native13($pageable, $sortable, $aktif) + { + // Call parent method to execute the query + return $this->executeNativeQuery(); + } } $obj = new Supervisor(null, $database); @@ -10079,6 +10109,25 @@ echo "Alamat: " . $native8->getTelepon() . "\r\n"; echo "Alamat: " . $native9[0]->getTelepon() . "\r\n"; echo "Alamat: " . $native10->getTelepon() . "\r\n"; echo "Alamat: " . $native11[0]->getTelepon() . "\r\n"; + + +$sortable = new PicoSortable(); +$sortable->addSortable(new PicoSort("nama", PicoSort::ORDER_TYPE_ASC)); +$pageable = new PicoPageable(new PicoPage(3, 20)); + +try +{ + $native13 = $obj->native13($pageable, $sortable, true); + echo "\r\nnative13:\r\n"; + foreach($native13 as $sup) + { + echo $sup."\r\n\r\n"; + } +} +catch(Exception $e) +{ + echo $e->getMessage(); +} ``` For the purpose of exporting large amounts of data, use the PDOStatement return type. PDOStatement allows users to read one by one and process it immediately, allowing PHP to release memory from the previous process. PHP does not need to store very large data in a variable. From 51bd14a6e36fcf0cf8b22a3598cdec5682e1db73 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 07:15:48 +0700 Subject: [PATCH 25/55] Rename method --- src/Database/PicoDatabaseQueryBuilder.php | 6 +++--- src/MagicObject.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Database/PicoDatabaseQueryBuilder.php b/src/Database/PicoDatabaseQueryBuilder.php index 685ba43d..719da9c1 100644 --- a/src/Database/PicoDatabaseQueryBuilder.php +++ b/src/Database/PicoDatabaseQueryBuilder.php @@ -847,10 +847,10 @@ public function addQueryParameters($query) /** * Adds pagination and sorting clauses to a native query string. * - * This function appends the appropriate `ORDER BY` and `LIMIT OFFSET` + * This function appends the appropriate `ORDER BY` and `LIMIT $limit OFFSET $offset` or `LIMIT $offset, $limit` * clauses to the provided SQL query string based on the given pagination and sorting parameters. * It supports various database management systems (DBMS) and adjusts the query syntax - * accordingly (e.g., for PostgreSQL, SQLite, MySQL, etc.). + * accordingly (e.g., for PostgreSQL, SQLite, MySQL, MariaDB, etc.). * * @param string $queryString The original SQL query string to which pagination and sorting will be added. * @param PicoPageable|null $pageable The pagination parameters, or `null` if pagination is not required. @@ -858,7 +858,7 @@ public function addQueryParameters($query) * * @return string The modified SQL query string with added pagination and sorting clauses. */ - public function addLimitOffset($queryString, $pageable, $sortable) + public function addPaginationAndSorting($queryString, $pageable, $sortable) { if(!isset($pageable) && !isset($sortable)) { diff --git a/src/MagicObject.php b/src/MagicObject.php index d08647d3..97290163 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -803,7 +803,7 @@ protected function executeNativeQuery() //NOSONAR } } $queryBuilder = new PicoDatabaseQueryBuilder($this->_database->getDatabaseType()); - $queryString = $queryBuilder->addLimitOffset($queryString, $pageable, $sortable); + $queryString = $queryBuilder->addPaginationAndSorting($queryString, $pageable, $sortable); $stmt = $pdo->prepare($queryString); From 152beb39933b91c58900589c15070eaee515d194 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 07:19:53 +0700 Subject: [PATCH 26/55] Update pagination and sorting --- src/Database/PicoDatabaseQueryBuilder.php | 2 +- src/MagicObject.php | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Database/PicoDatabaseQueryBuilder.php b/src/Database/PicoDatabaseQueryBuilder.php index 719da9c1..2a966dbd 100644 --- a/src/Database/PicoDatabaseQueryBuilder.php +++ b/src/Database/PicoDatabaseQueryBuilder.php @@ -890,7 +890,7 @@ public function addPaginationAndSorting($queryString, $pageable, $sortable) // PostgeSQL and SQLite $queryString .= "\r\nLIMIT $limit OFFSET $offset"; } - else + else if($this->isMySql()) { // MariaDB and MySQL $queryString .= "\r\nLIMIT $offset, $limit"; diff --git a/src/MagicObject.php b/src/MagicObject.php index 97290163..810798b2 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -802,11 +802,14 @@ protected function executeNativeQuery() //NOSONAR } } } - $queryBuilder = new PicoDatabaseQueryBuilder($this->_database->getDatabaseType()); - $queryString = $queryBuilder->addPaginationAndSorting($queryString, $pageable, $sortable); - $stmt = $pdo->prepare($queryString); + if(isset($pageable) || isset($sortable)) + { + $queryBuilder = new PicoDatabaseQueryBuilder($this->_database->getDatabaseType()); + $queryString = $queryBuilder->addPaginationAndSorting($queryString, $pageable, $sortable); + } + $stmt = $pdo->prepare($queryString); // Automatically bind each parameter foreach ($callerParamValues as $index => $paramValue) { From e3e60c73f1bd984838377d373d0feb7aaf25fb2a Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 08:28:10 +0700 Subject: [PATCH 27/55] Update MagicObject.php --- src/MagicObject.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 810798b2..1a703b3c 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -814,11 +814,7 @@ protected function executeNativeQuery() //NOSONAR // Automatically bind each parameter foreach ($callerParamValues as $index => $paramValue) { if (isset($callerParams[$index])) { - if($paramValue instanceof PicoPageable) - { - // skip - } - else if($paramValue instanceof PicoSortable) + if($paramValue instanceof PicoPageable || $paramValue instanceof PicoSortable) { // skip } From e5e4de777e2332737e65385ddbcec5e1ce6eab23 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 09:50:34 +0700 Subject: [PATCH 28/55] Refactor `executeNativeQuery` --- src/MagicObject.php | 480 +++++++++++++++++++++++++++++--------------- tests/caller.php | 6 +- tests/dto.php | 1 - 3 files changed, 321 insertions(+), 166 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 1a703b3c..84944576 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -713,213 +713,369 @@ public function selectQuery() * instances of that class will be created for each row fetched. * * @return mixed Returns the result based on the return type of the caller function: - * - null if the return type is void. - * - integer for the number of affected rows if the return type is int. - * - object for a single result if the return type is object. - * - an array of associative arrays for multiple results if the return type is array. - * - a JSON string if the return type is string. + * - null if the return type is `void`. + * - integer for the number of affected rows if the return type is `int`. + * - object for a single result if the return type is `object`. + * - an array of associative arrays for multiple results if the return type is `array`. + * - a JSON string if the return type is `string`. * - instances of a specified class if the return type matches a class name. * * @throws PDOException If there is an error executing the database query. * @throws InvalidQueryInputException If there is no query to be executed. * @throws InvalidReturnTypeException If the return type specified is invalid. */ - protected function executeNativeQuery() //NOSONAR + protected function executeNativeQuery() { - // Retrieve caller trace information + // Retrieve trace information for the caller function $trace = debug_backtrace(); - - // Get parameters from the caller function - $callerParamValues = isset($trace[1]['args']) ? $trace[1]['args'] : []; - - // Get the name of the caller function and class + $callerParamValues = $this->getCallerParams($trace); $callerFunctionName = $trace[1]['function']; $callerClassName = $trace[1]['class']; - // Use reflection to get annotations from the caller function + // Use reflection to extract docblock annotations $reflection = new ReflectionMethod($callerClassName, $callerFunctionName); $docComment = $reflection->getDocComment(); - // Get the query from the @query annotation - preg_match('/@query\s*\("([^"]+)"\)/', $docComment, $matches); - $queryString = $matches ? $matches[1] : ''; - - $queryString = trim($queryString, " \r\n\t "); - if(empty($queryString)) - { - // Try reading the query in another way - preg_match('/@query\s*\(\s*"(.*?)"\s*\)/s', $docComment, $matches); - $queryString = $matches ? $matches[1] : ''; - if(empty($queryString)) - { - throw new InvalidQueryInputException("No query found.\r\n".$docComment); - } - } - - // Get parameter information from the caller function - $callerParams = $reflection->getParameters(); - - // Get return type from the caller function - preg_match('/@return\s+([^\s]+)/', $docComment, $matches); - $returnType = $matches ? $matches[1] : 'void'; - - // Trim return type - $returnType = trim($returnType); - - // Change self to callerClassName - if($returnType == "self[]") - { - $returnType = $callerClassName."[]"; - } - else if($returnType == "self") - { - $returnType = $callerClassName; - } + $queryString = $this->extractQueryString($docComment); + $returnType = $this->extractReturnType($docComment, $callerClassName); - $params = []; + // Initialize pageable and sortable objects $pageable = null; $sortable = null; + try { // Get database connection $pdo = $this->_database->getDatabaseConnection(); + + // Replace query parameters + $params = $this->prepareQueryParams($callerParamValues, $reflection->getParameters(), $queryString, $pageable, $sortable); + + // Prepare query with pagination and sorting if applicable + $queryString = $this->applyPaginationAndSorting($queryString, $pageable, $sortable); + + // Execute prepared statement + $stmt = $pdo->prepare($queryString); + $this->bindParams($stmt, $callerParamValues, $reflection->getParameters()); + + // Log query if necessary + $this->logQuery($stmt, $params); + + // Execute the query and return results based on the return type + $stmt->execute(); + return $this->handleReturnData($stmt, $returnType); - // Replace array - foreach ($callerParamValues as $index => $paramValue) { - if($paramValue instanceof PicoPageable) - { - $pageable = $paramValue; - } - else if($paramValue instanceof PicoSortable) - { - $sortable = $paramValue; - } - else if (isset($callerParams[$index])) { - // Format parameter name according to the query - $paramName = $callerParams[$index]->getName(); - if(is_array($paramValue)) - { - $queryString = str_replace(":".$paramName, PicoDatabaseUtil::toList($paramValue, true, true), $queryString); - } + } catch (PDOException $e) { + // Log and throw PDO exceptions + throw new PDOException($e->getMessage(), $e->getCode(), $e); + } + + return null; + } + + /** + * Extracts the parameters passed to the caller function from the debug trace. + * + * @param array $trace The debug backtrace information. + * @return array The parameter values passed to the caller function. + */ + private function getCallerParams($trace) + { + return isset($trace[1]['args']) ? $trace[1]['args'] : []; + } + + /** + * Prepares query parameters and replaces them in the query string as necessary. + * + * @param array $callerParamValues The values of the parameters passed to the caller function. + * @param array $callerParams The reflection parameters of the caller function. + * @param string $queryString The SQL query string to be prepared. + * @param mixed $pageable A pageable object (if any). + * @param mixed $sortable A sortable object (if any). + * @return array The prepared parameters. + */ + private function prepareQueryParams($callerParamValues, $callerParams, &$queryString, &$pageable, &$sortable) + { + $params = []; + + foreach ($callerParamValues as $index => $paramValue) { + if ($paramValue instanceof PicoPageable) { + $pageable = $paramValue; + } elseif ($paramValue instanceof PicoSortable) { + $sortable = $paramValue; + } elseif (isset($callerParams[$index])) { + $paramName = $callerParams[$index]->getName(); + if (is_array($paramValue)) { + $queryString = str_replace(":" . $paramName, PicoDatabaseUtil::toList($paramValue, true, true), $queryString); } } + } - if(isset($pageable) || isset($sortable)) - { - $queryBuilder = new PicoDatabaseQueryBuilder($this->_database->getDatabaseType()); - $queryString = $queryBuilder->addPaginationAndSorting($queryString, $pageable, $sortable); - } + return $params; + } - $stmt = $pdo->prepare($queryString); + /** + * Applies pagination and sorting to the query if applicable. + * + * @param string $queryString The SQL query string to be modified. + * @param mixed $pageable A pageable object (if any). + * @param mixed $sortable A sortable object (if any). + * @return string The modified query string. + */ + private function applyPaginationAndSorting($queryString, $pageable, $sortable) + { + if (isset($pageable) || isset($sortable)) { + $queryBuilder = new PicoDatabaseQueryBuilder($this->_database->getDatabaseType()); + $queryString = $queryBuilder->addPaginationAndSorting($queryString, $pageable, $sortable); + } + return $queryString; + } - // Automatically bind each parameter - foreach ($callerParamValues as $index => $paramValue) { - if (isset($callerParams[$index])) { - if($paramValue instanceof PicoPageable || $paramValue instanceof PicoSortable) - { - // skip - } - else - { - // Format parameter name according to the query - $paramName = $callerParams[$index]->getName(); - if(!is_array($paramValue)) - { - $maped = $this->mapToPdoParamType($paramValue); - $paramType = $maped->type; - $paramValue = $maped->value; - $params[$paramName] = $paramValue; - $stmt->bindValue(":".$paramName, $paramValue, $paramType); - } - } + /** + * Binds the parameters to the prepared statement. + * + * @param PDOStatement $stmt The prepared PDO statement. + * @param array $callerParamValues The values of the parameters passed to the caller function. + * @param array $callerParams The reflection parameters of the caller function. + */ + private function bindParams($stmt, $callerParamValues, $callerParams) + { + foreach ($callerParamValues as $index => $paramValue) { + if (isset($callerParams[$index])) { + if ($paramValue instanceof PicoPageable || $paramValue instanceof PicoSortable) { + // Skip pageable and sortable parameters + continue; + } + + $paramName = $callerParams[$index]->getName(); + if (!is_array($paramValue)) { + $maped = $this->mapToPdoParamType($paramValue); + $paramType = $maped->type; + $paramValue = $maped->value; + $stmt->bindValue(":" . $paramName, $paramValue, $paramType); } } - - // Send query to logger - $debugFunction = $this->_database->getCallbackDebugQuery(); - if(isset($debugFunction) && is_callable($debugFunction)) - { - call_user_func($debugFunction, PicoDatabaseUtil::getFinalQuery($stmt, $params)); - } + } + } - // Execute the query - $stmt->execute(); + /** + * Logs the query if a callback is provided. + * + * @param PDOStatement $stmt The prepared PDO statement. + * @param array $params The parameters bound to the query. + */ + private function logQuery($stmt, $params) + { + $debugFunction = $this->_database->getCallbackDebugQuery(); + if (isset($debugFunction) && is_callable($debugFunction)) { + call_user_func($debugFunction, PicoDatabaseUtil::getFinalQuery($stmt, $params)); + } + } - if ($returnType == "void") { + + /** + * Handles the processing of query results based on the specified return type. + * + * This method takes a PDOStatement object and a return type, and processes the result + * accordingly. It handles various return types including primitive types, objects, arrays, + * JSON, and custom class instances. It also manages edge cases like `self` and class-type hinting. + * + * @param PDOStatement $stmt The executed PDO statement. + * @param string $returnType The expected return type (e.g., `void`, `int`, `object`, class name). + * @return mixed The processed query result based on the return type. + * @throws InvalidReturnTypeException If the return type is invalid or the class does not exist. + */ + private function handleReturnData($stmt, $returnType) //NOSONAR + { + switch ($returnType) { + case "void": // Return null if the return type is void return null; - } - if ($returnType == "PDOStatement") { + + case "PDOStatement": // Return the PDOStatement object return $stmt; - } else if ($returnType == "int" || $returnType == "integer") { + + case "int": + case "integer": // Return the affected row count return $stmt->rowCount(); - } else if ($returnType == "object" || $returnType == "stdClass") { + + case "object": + case "stdClass": // Return one row as an object return $stmt->fetch(PDO::FETCH_OBJ); - } else if ($returnType == "array") { + + case "array": // Return all rows as an associative array return $stmt->fetchAll(PDO::FETCH_ASSOC); - } else if ($returnType == "string") { + + case "string": // Return the result as a JSON string return json_encode($stmt->fetchAll(PDO::FETCH_OBJ)); + + default: + return $this->handleCustomReturnType($stmt, $returnType); + } + } + + /** + * Handles custom return types, including array of class names or single class name. + * + * @param PDOStatement $stmt The executed PDO statement. + * @param string $returnType The custom return type (e.g., class name or array of class names). + * @return mixed The result of the query mapped to the custom return type. + * @throws InvalidReturnTypeException If the custom return type is invalid. + */ + private function handleCustomReturnType($stmt, $returnType) //NOSONAR + { + // Check for array-type hinting in the return type + if (stripos($returnType, "[") !== false) { + $className = trim(explode("[", $returnType)[0]); + + if ($className == "stdClass") { + // Return all rows as stdClass objects + return $stmt->fetchAll(PDO::FETCH_OBJ); + } elseif ($className == 'MagicObject') { + // Return all rows as MagicObject instances + return $this->mapResultsToMagicObjects($stmt); + } elseif (class_exists($className)) { + // Map result rows to the specified class + return $this->mapResultsToClass($stmt, $className); } else { - try { - // Check for array-type hinting in the return type - if (stripos($returnType, "[") !== false) { - $className = trim(explode("[", $returnType)[0]); - if ($className == "stdClass") { - // Return all rows as stdClass objects - return $stmt->fetchAll(PDO::FETCH_OBJ); - } - else if($className == 'MagicObject') { - $result = $stmt->fetchAll(PDO::FETCH_OBJ); - $ret = []; - foreach ($result as $row) { - $ret[] = new MagicObject($row); - } - return $ret; - } - else if (class_exists($className)) { - // Map result rows to the specified class - $obj = new $className(); - if($obj instanceof MagicObject) { - $result = $stmt->fetchAll(PDO::FETCH_OBJ); - foreach ($result as $row) { - $ret[] = new $className($row); - } - return $ret; - } - } - throw new InvalidReturnTypeException("Invalid return type for $className"); - } else { - // Return a single object of the specified class - $className = trim($returnType); - if($className == 'MagicObject') { - $row = $stmt->fetch(PDO::FETCH_OBJ); - return new MagicObject($row); - } - else if (class_exists($className)) { - $obj = new $className(); - if($obj instanceof MagicObject) { - $row = $stmt->fetch(PDO::FETCH_OBJ); - return $obj->loadData($row); - } - } - throw new InvalidReturnTypeException("Invalid return type for $className"); - } - } catch (Exception $e) { - // Log the exception if the class is not found - throw new InvalidReturnTypeException("Invalid return type for $className"); - } - } - } - catch (PDOException $e) + throw new InvalidReturnTypeException("Invalid return type for $className"); + } + } else { + // Return a single object of the specified class + $className = trim($returnType); + + if ($className == 'MagicObject') { + // Return one MagicObject + $row = $stmt->fetch(PDO::FETCH_OBJ); + return new MagicObject($row); + } elseif (class_exists($className)) { + // Return one instance of the specified class + return $this->mapSingleResultToClass($stmt, $className); + } else { + throw new InvalidReturnTypeException("Invalid return type for $className"); + } + } + } + + /** + * Maps the results of a query to an array of `MagicObject` instances. + * + * @param PDOStatement $stmt The executed PDO statement. + * @return MagicObject[] The array of mapped `MagicObject` instances. + */ + private function mapResultsToMagicObjects($stmt) + { + $result = $stmt->fetchAll(PDO::FETCH_OBJ); + return array_map(function($row) { + return new MagicObject($row); + }, $result); + } + + /** + * Maps the results of a query to an array of instances of a specific class. + * + * @param PDOStatement $stmt The executed PDO statement. + * @param string $className The name of the class to map each row to. + * @return object[] The array of mapped class instances. + */ + private function mapResultsToClass($stmt, $className) + { + $result = $stmt->fetchAll(PDO::FETCH_OBJ); + return array_map(function($row) use ($className) { + return new $className($row); + }, $result); + } + + /** + * Maps a single result row to an instance of a class. + * + * @param PDOStatement $stmt The executed PDO statement. + * @param string $className The name of the class to map the row to. + * @return object The mapped class instance. + */ + private function mapSingleResultToClass($stmt, $className) + { + $row = $stmt->fetch(PDO::FETCH_OBJ); + + if ($className == 'MagicObject') { + return new MagicObject($row); + } + + if (class_exists($className)) { + $obj = new $className(); + return $obj instanceof MagicObject ? $obj->loadData($row) : $row; + } + + throw new InvalidReturnTypeException("Invalid return type for $className"); + } + + + /** + * Extracts the return type from the docblock of a method. + * + * This method parses the docblock to find the `@return` annotation and extracts the return type. + * If the return type is `self`, it is replaced with the caller class name. Additionally, if + * the return type is an array of `self` (`self[]`), it is converted to an array of the caller class name (`[]`). + * + * @param string $docComment The docblock of the method being analyzed. + * @param string $callerClassName The name of the class where the method is called. + * @return string The extracted and possibly modified return type. + */ + private function extractReturnType($docComment, $callerClassName) + { + // Get return type from the caller function + preg_match('/@return\s+([^\s]+)/', $docComment, $matches); + $returnType = $matches ? $matches[1] : 'void'; + + // Trim return type + $returnType = trim($returnType); + + // Change self to callerClassName + if($returnType == "self[]") { - // Handle database errors with logging - throw new PDOException($e->getMessage(), $e->getCode(), $e); + $returnType = $callerClassName."[]"; } - return null; + else if($returnType == "self") + { + $returnType = $callerClassName; + } + + return $returnType; + } + + /** + * Extracts the query string from the docblock of a method. + * + * This method searches the docblock for the `@query` annotation and attempts to extract + * the SQL query string associated with it. If the query string is not found, it tries + * to parse the query in a different format. If no query is found, an exception is thrown. + * + * @param string $docComment The docblock of the method being analyzed. + * @return string The extracted query string from the docblock. + * @throws InvalidQueryInputException If no query string is found in the docblock. + */ + private function extractQueryString($docComment) + { + // Get the query from the @query annotation + preg_match('/@query\s*\("([^"]+)"\)/', $docComment, $matches); + $queryString = $matches ? $matches[1] : ''; + + $queryString = trim($queryString, " \r\n\t "); + if(empty($queryString)) + { + // Try reading the query in another way + preg_match('/@query\s*\(\s*"(.*?)"\s*\)/s', $docComment, $matches); + $queryString = $matches ? $matches[1] : ''; + if(empty($queryString)) + { + throw new InvalidQueryInputException("No query found.\r\n".$docComment); + } + } + return $queryString; } /** diff --git a/tests/caller.php b/tests/caller.php index e26fb01d..dd4c8472 100644 --- a/tests/caller.php +++ b/tests/caller.php @@ -11,7 +11,7 @@ require_once dirname(__DIR__) . "/vendor/autoload.php"; $databaseCredential = new SecretObject(); -$databaseCredential->loadYamlFile(dirname(dirname(__DIR__)) . "/test.yml.txt", false, true, true); +$databaseCredential->loadYamlFile(dirname(dirname(__DIR__)) . "/test.yml", false, true, true); $databaseCredential->getDatabase()->setDatabaseName("sipro"); $database = new PicoDatabase($databaseCredential->getDatabase(), null, function($sql){ echo $sql.";\r\n\r\n"; @@ -275,7 +275,7 @@ public function native13($pageable, $sortable, $aktif) $obj = new Supervisor(null, $database); -/* + $native1 = $obj->native1(1, true); $native2 = $obj->native2(1, true); @@ -325,7 +325,7 @@ public function native13($pageable, $sortable, $aktif) echo "Alamat: " . $native9[0]->getTelepon() . "\r\n"; echo "Alamat: " . $native10->getTelepon() . "\r\n"; echo "Alamat: " . $native11[0]->getTelepon() . "\r\n"; -*/ + $sortable = new PicoSortable(); $sortable->addSortable(new PicoSort("nama", PicoSort::ORDER_TYPE_ASC)); diff --git a/tests/dto.php b/tests/dto.php index 9f7206dc..292d32be 100644 --- a/tests/dto.php +++ b/tests/dto.php @@ -694,7 +694,6 @@ public function onLoadData($data) $data->setName("Malik"); $data->getProducer()->setName("aaaaa"); } - throw new InvalidParameterException("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); return $data; } } From fef276b81b5d80638b515ccaf7da0828d7402102 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 09:57:24 +0700 Subject: [PATCH 29/55] Update MagicObject.php --- src/MagicObject.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 84944576..c3b64a85 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -809,7 +809,27 @@ private function prepareQueryParams($callerParamValues, $callerParams, &$querySt } } } - + + foreach ($callerParamValues as $index => $paramValue) { + if (isset($callerParams[$index])) { + if($paramValue instanceof PicoPageable || $paramValue instanceof PicoSortable) + { + // skip + } + else + { + // Format parameter name according to the query + $paramName = $callerParams[$index]->getName(); + if(!is_array($paramValue)) + { + $maped = $this->mapToPdoParamType($paramValue); + $paramType = $maped->type; + $paramValue = $maped->value; + $params[$paramName] = $paramValue; + } + } + } + } return $params; } From c383172665833887f91c289be966614a67095a76 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 10:27:19 +0700 Subject: [PATCH 30/55] Refactor `executeNativeQuery` --- src/MagicObject.php | 460 +++++++++++++++++--------------------------- tests/titip.php | 227 ++++++++++++++++++++++ 2 files changed, 404 insertions(+), 283 deletions(-) create mode 100644 tests/titip.php diff --git a/src/MagicObject.php b/src/MagicObject.php index c3b64a85..10600bee 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -713,11 +713,11 @@ public function selectQuery() * instances of that class will be created for each row fetched. * * @return mixed Returns the result based on the return type of the caller function: - * - null if the return type is `void`. - * - integer for the number of affected rows if the return type is `int`. - * - object for a single result if the return type is `object`. - * - an array of associative arrays for multiple results if the return type is `array`. - * - a JSON string if the return type is `string`. + * - null if the return type is void. + * - integer for the number of affected rows if the return type is int. + * - object for a single result if the return type is object. + * - an array of associative arrays for multiple results if the return type is array. + * - a JSON string if the return type is string. * - instances of a specified class if the return type matches a class name. * * @throws PDOException If there is an error executing the database query. @@ -726,97 +726,39 @@ public function selectQuery() */ protected function executeNativeQuery() { - // Retrieve trace information for the caller function + // Retrieve caller trace information $trace = debug_backtrace(); - $callerParamValues = $this->getCallerParams($trace); + + // Get parameters from the caller function + $callerParamValues = isset($trace[1]['args']) ? $trace[1]['args'] : []; + + // Get the name of the caller function and class $callerFunctionName = $trace[1]['function']; $callerClassName = $trace[1]['class']; - // Use reflection to extract docblock annotations + // Use reflection to get annotations from the caller function $reflection = new ReflectionMethod($callerClassName, $callerFunctionName); $docComment = $reflection->getDocComment(); - + $queryString = $this->extractQueryString($docComment); - $returnType = $this->extractReturnType($docComment, $callerClassName); + $returnType = $this->extractReturnType($docComment, $callerClassName); - // Initialize pageable and sortable objects - $pageable = null; - $sortable = null; + // Get parameter information from the caller function + $callerParams = $reflection->getParameters(); + + $params = []; try { // Get database connection $pdo = $this->_database->getDatabaseConnection(); - - // Replace query parameters - $params = $this->prepareQueryParams($callerParamValues, $reflection->getParameters(), $queryString, $pageable, $sortable); - - // Prepare query with pagination and sorting if applicable - $queryString = $this->applyPaginationAndSorting($queryString, $pageable, $sortable); - - // Execute prepared statement - $stmt = $pdo->prepare($queryString); - $this->bindParams($stmt, $callerParamValues, $reflection->getParameters()); - - // Log query if necessary - $this->logQuery($stmt, $params); - - // Execute the query and return results based on the return type - $stmt->execute(); - return $this->handleReturnData($stmt, $returnType); - } catch (PDOException $e) { - // Log and throw PDO exceptions - throw new PDOException($e->getMessage(), $e->getCode(), $e); - } + $queryString = $this->applyQueryParameters($queryString, $callerParams, $callerParamValues); - return null; - } - - /** - * Extracts the parameters passed to the caller function from the debug trace. - * - * @param array $trace The debug backtrace information. - * @return array The parameter values passed to the caller function. - */ - private function getCallerParams($trace) - { - return isset($trace[1]['args']) ? $trace[1]['args'] : []; - } - - /** - * Prepares query parameters and replaces them in the query string as necessary. - * - * @param array $callerParamValues The values of the parameters passed to the caller function. - * @param array $callerParams The reflection parameters of the caller function. - * @param string $queryString The SQL query string to be prepared. - * @param mixed $pageable A pageable object (if any). - * @param mixed $sortable A sortable object (if any). - * @return array The prepared parameters. - */ - private function prepareQueryParams($callerParamValues, $callerParams, &$queryString, &$pageable, &$sortable) - { - $params = []; + $stmt = $pdo->prepare($queryString); - foreach ($callerParamValues as $index => $paramValue) { - if ($paramValue instanceof PicoPageable) { - $pageable = $paramValue; - } elseif ($paramValue instanceof PicoSortable) { - $sortable = $paramValue; - } elseif (isset($callerParams[$index])) { - $paramName = $callerParams[$index]->getName(); - if (is_array($paramValue)) { - $queryString = str_replace(":" . $paramName, PicoDatabaseUtil::toList($paramValue, true, true), $queryString); - } - } - } - - foreach ($callerParamValues as $index => $paramValue) { - if (isset($callerParams[$index])) { - if($paramValue instanceof PicoPageable || $paramValue instanceof PicoSortable) - { - // skip - } - else + // Automatically bind each parameter + foreach ($callerParamValues as $index => $paramValue) { + if(isset($callerParams[$index]) && !($paramValue instanceof PicoPageable) && !($paramValue instanceof PicoSortable)) { // Format parameter name according to the query $paramName = $callerParams[$index]->getName(); @@ -826,224 +768,177 @@ private function prepareQueryParams($callerParamValues, $callerParams, &$querySt $paramType = $maped->type; $paramValue = $maped->value; $params[$paramName] = $paramValue; + $stmt->bindValue(":".$paramName, $paramValue, $paramType); } } } - } - return $params; - } + + // Send query to logger + $debugFunction = $this->_database->getCallbackDebugQuery(); + if(isset($debugFunction) && is_callable($debugFunction)) + { + call_user_func($debugFunction, PicoDatabaseUtil::getFinalQuery($stmt, $params)); + } - /** - * Applies pagination and sorting to the query if applicable. - * - * @param string $queryString The SQL query string to be modified. - * @param mixed $pageable A pageable object (if any). - * @param mixed $sortable A sortable object (if any). - * @return string The modified query string. - */ - private function applyPaginationAndSorting($queryString, $pageable, $sortable) - { - if (isset($pageable) || isset($sortable)) { - $queryBuilder = new PicoDatabaseQueryBuilder($this->_database->getDatabaseType()); - $queryString = $queryBuilder->addPaginationAndSorting($queryString, $pageable, $sortable); + // Execute the query + $stmt->execute(); + + return $this->handleReturnObject($stmt, $returnType); + } + catch (PDOException $e) + { + // Handle database errors with logging + throw new PDOException($e->getMessage(), $e->getCode(), $e); } - return $queryString; + return null; } - + /** - * Binds the parameters to the prepared statement. + * Replaces array parameters in the query string and applies pagination and sorting if necessary. * - * @param PDOStatement $stmt The prepared PDO statement. - * @param array $callerParamValues The values of the parameters passed to the caller function. - * @param array $callerParams The reflection parameters of the caller function. + * This method iterates over the provided caller parameters and their values, replacing any array-type + * parameters with their string equivalents in the query string. Additionally, if any pagination or sorting + * objects (e.g., `PicoPageable` or `PicoSortable`) are detected in the parameters, it modifies the query + * string to include pagination and sorting clauses. + * + * @param string $queryString The SQL query string that may contain placeholders for parameters. + * @param ReflectionParameter[] $callerParams The parameters of the calling method (reflection objects). + * @param array $callerParamValues The actual values of the parameters passed to the calling method. + * @return string The modified query string with array parameters replaced and pagination/sorting applied. + * @throws InvalidArgumentException If the provided parameters are not in the expected format. */ - private function bindParams($stmt, $callerParamValues, $callerParams) + private function applyQueryParameters($queryString, $callerParams, $callerParamValues) { + $pageable = null; + $sortable = null; + + // Replace array foreach ($callerParamValues as $index => $paramValue) { - if (isset($callerParams[$index])) { - if ($paramValue instanceof PicoPageable || $paramValue instanceof PicoSortable) { - // Skip pageable and sortable parameters - continue; - } - + if($paramValue instanceof PicoPageable) + { + $pageable = $paramValue; + } + else if($paramValue instanceof PicoSortable) + { + $sortable = $paramValue; + } + else if (isset($callerParams[$index])) { + // Format parameter name according to the query $paramName = $callerParams[$index]->getName(); - if (!is_array($paramValue)) { - $maped = $this->mapToPdoParamType($paramValue); - $paramType = $maped->type; - $paramValue = $maped->value; - $stmt->bindValue(":" . $paramName, $paramValue, $paramType); + if(is_array($paramValue)) + { + $queryString = str_replace(":".$paramName, PicoDatabaseUtil::toList($paramValue, true, true), $queryString); } } } - } - /** - * Logs the query if a callback is provided. - * - * @param PDOStatement $stmt The prepared PDO statement. - * @param array $params The parameters bound to the query. - */ - private function logQuery($stmt, $params) - { - $debugFunction = $this->_database->getCallbackDebugQuery(); - if (isset($debugFunction) && is_callable($debugFunction)) { - call_user_func($debugFunction, PicoDatabaseUtil::getFinalQuery($stmt, $params)); + // Apply pagination and sorting if needed + if(isset($pageable) || isset($sortable)) + { + $queryBuilder = new PicoDatabaseQueryBuilder($this->_database->getDatabaseType()); + $queryString = $queryBuilder->addPaginationAndSorting($queryString, $pageable, $sortable); } + + return $queryString; } - /** - * Handles the processing of query results based on the specified return type. + * Handles the return of data based on the specified return type. * - * This method takes a PDOStatement object and a return type, and processes the result - * accordingly. It handles various return types including primitive types, objects, arrays, - * JSON, and custom class instances. It also manages edge cases like `self` and class-type hinting. + * This method processes the data returned from a PDO statement and returns it in the format + * specified by the caller's `@return` docblock annotation. It supports various return types + * such as `void`, `PDOStatement`, `int`, `object`, `array`, `string`, or any specific class + * name (including array-type hinting). * * @param PDOStatement $stmt The executed PDO statement. - * @param string $returnType The expected return type (e.g., `void`, `int`, `object`, class name). - * @return mixed The processed query result based on the return type. - * @throws InvalidReturnTypeException If the return type is invalid or the class does not exist. + * @param string $returnType The return type as specified in the caller function's docblock. + * @return mixed The processed return data, which can be a single value, object, array, + * PDOStatement, or a JSON string, based on the return type. + * @throws InvalidReturnTypeException If the return type is invalid or unrecognized. */ - private function handleReturnData($stmt, $returnType) //NOSONAR + private function handleReturnObject($stmt, $returnType) //NOSONAR { - switch ($returnType) { - case "void": - // Return null if the return type is void - return null; - - case "PDOStatement": - // Return the PDOStatement object - return $stmt; - - case "int": - case "integer": - // Return the affected row count - return $stmt->rowCount(); - - case "object": - case "stdClass": - // Return one row as an object - return $stmt->fetch(PDO::FETCH_OBJ); - - case "array": - // Return all rows as an associative array - return $stmt->fetchAll(PDO::FETCH_ASSOC); - - case "string": - // Return the result as a JSON string - return json_encode($stmt->fetchAll(PDO::FETCH_OBJ)); - - default: - return $this->handleCustomReturnType($stmt, $returnType); + if ($returnType == "void") { + // Return null if the return type is void + return null; } - } - - /** - * Handles custom return types, including array of class names or single class name. - * - * @param PDOStatement $stmt The executed PDO statement. - * @param string $returnType The custom return type (e.g., class name or array of class names). - * @return mixed The result of the query mapped to the custom return type. - * @throws InvalidReturnTypeException If the custom return type is invalid. - */ - private function handleCustomReturnType($stmt, $returnType) //NOSONAR - { - // Check for array-type hinting in the return type - if (stripos($returnType, "[") !== false) { - $className = trim(explode("[", $returnType)[0]); - - if ($className == "stdClass") { - // Return all rows as stdClass objects - return $stmt->fetchAll(PDO::FETCH_OBJ); - } elseif ($className == 'MagicObject') { - // Return all rows as MagicObject instances - return $this->mapResultsToMagicObjects($stmt); - } elseif (class_exists($className)) { - // Map result rows to the specified class - return $this->mapResultsToClass($stmt, $className); - } else { - throw new InvalidReturnTypeException("Invalid return type for $className"); - } + if ($returnType == "PDOStatement") { + // Return the PDOStatement object + return $stmt; + } else if ($returnType == "int" || $returnType == "integer") { + // Return the affected row count + return $stmt->rowCount(); + } else if ($returnType == "object" || $returnType == "stdClass") { + // Return one row as an object + return $stmt->fetch(PDO::FETCH_OBJ); + } else if ($returnType == "array") { + // Return all rows as an associative array + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } else if ($returnType == "string") { + // Return the result as a JSON string + return json_encode($stmt->fetchAll(PDO::FETCH_OBJ)); } else { - // Return a single object of the specified class - $className = trim($returnType); - - if ($className == 'MagicObject') { - // Return one MagicObject - $row = $stmt->fetch(PDO::FETCH_OBJ); - return new MagicObject($row); - } elseif (class_exists($className)) { - // Return one instance of the specified class - return $this->mapSingleResultToClass($stmt, $className); - } else { + try { + // Check for array-type hinting in the return type + if (stripos($returnType, "[") !== false) { + $className = trim(explode("[", $returnType)[0]); + if ($className == "stdClass") { + // Return all rows as stdClass objects + return $stmt->fetchAll(PDO::FETCH_OBJ); + } + else if($className == 'MagicObject') { + $result = $stmt->fetchAll(PDO::FETCH_OBJ); + $ret = []; + foreach ($result as $row) { + $ret[] = new MagicObject($row); + } + return $ret; + } + else if (class_exists($className)) { + // Map result rows to the specified class + $obj = new $className(); + if($obj instanceof MagicObject) { + $result = $stmt->fetchAll(PDO::FETCH_OBJ); + foreach ($result as $row) { + $ret[] = new $className($row); + } + return $ret; + } + } + throw new InvalidReturnTypeException("Invalid return type for $className"); + } else { + // Return a single object of the specified class + $className = trim($returnType); + if($className == 'MagicObject') { + $row = $stmt->fetch(PDO::FETCH_OBJ); + return new MagicObject($row); + } + else if (class_exists($className)) { + $obj = new $className(); + if($obj instanceof MagicObject) { + $row = $stmt->fetch(PDO::FETCH_OBJ); + return $obj->loadData($row); + } + } + throw new InvalidReturnTypeException("Invalid return type for $className"); + } + } catch (Exception $e) { + // Log the exception if the class is not found throw new InvalidReturnTypeException("Invalid return type for $className"); } } } - - /** - * Maps the results of a query to an array of `MagicObject` instances. - * - * @param PDOStatement $stmt The executed PDO statement. - * @return MagicObject[] The array of mapped `MagicObject` instances. - */ - private function mapResultsToMagicObjects($stmt) - { - $result = $stmt->fetchAll(PDO::FETCH_OBJ); - return array_map(function($row) { - return new MagicObject($row); - }, $result); - } - - /** - * Maps the results of a query to an array of instances of a specific class. - * - * @param PDOStatement $stmt The executed PDO statement. - * @param string $className The name of the class to map each row to. - * @return object[] The array of mapped class instances. - */ - private function mapResultsToClass($stmt, $className) - { - $result = $stmt->fetchAll(PDO::FETCH_OBJ); - return array_map(function($row) use ($className) { - return new $className($row); - }, $result); - } - - /** - * Maps a single result row to an instance of a class. - * - * @param PDOStatement $stmt The executed PDO statement. - * @param string $className The name of the class to map the row to. - * @return object The mapped class instance. - */ - private function mapSingleResultToClass($stmt, $className) - { - $row = $stmt->fetch(PDO::FETCH_OBJ); - - if ($className == 'MagicObject') { - return new MagicObject($row); - } - - if (class_exists($className)) { - $obj = new $className(); - return $obj instanceof MagicObject ? $obj->loadData($row) : $row; - } - - throw new InvalidReturnTypeException("Invalid return type for $className"); - } - /** - * Extracts the return type from the docblock of a method. - * - * This method parses the docblock to find the `@return` annotation and extracts the return type. - * If the return type is `self`, it is replaced with the caller class name. Additionally, if - * the return type is an array of `self` (`self[]`), it is converted to an array of the caller class name (`[]`). + * Extracts the return type from the docblock of the caller function. + * + * The method processes the `@return` annotation in the docblock of the caller function, + * and adjusts for `self` to return the actual caller class name. It also handles array type + * return values. * - * @param string $docComment The docblock of the method being analyzed. - * @param string $callerClassName The name of the class where the method is called. - * @return string The extracted and possibly modified return type. + * @param string $docComment The docblock comment of the caller function. + * @param string $callerClassName The name of the class where the caller function is defined. + * @return string The processed return type, which could be a class name, `self`, or `void`. */ private function extractReturnType($docComment, $callerClassName) { @@ -1055,27 +950,23 @@ private function extractReturnType($docComment, $callerClassName) $returnType = trim($returnType); // Change self to callerClassName - if($returnType == "self[]") - { - $returnType = $callerClassName."[]"; - } - else if($returnType == "self") - { + if ($returnType == "self[]") { + $returnType = $callerClassName . "[]"; + } else if ($returnType == "self") { $returnType = $callerClassName; } return $returnType; } - + /** - * Extracts the query string from the docblock of a method. + * Extracts the query string from the docblock of the caller function. * - * This method searches the docblock for the `@query` annotation and attempts to extract - * the SQL query string associated with it. If the query string is not found, it tries - * to parse the query in a different format. If no query is found, an exception is thrown. + * The method looks for the `@query` annotation in the docblock and extracts the query string. + * It tries to handle different formats for the annotation, throwing an exception if no query is found. * - * @param string $docComment The docblock of the method being analyzed. - * @return string The extracted query string from the docblock. + * @param string $docComment The docblock comment of the caller function. + * @return string The SQL query string extracted from the `@query` annotation. * @throws InvalidQueryInputException If no query string is found in the docblock. */ private function extractQueryString($docComment) @@ -1084,20 +975,23 @@ private function extractQueryString($docComment) preg_match('/@query\s*\("([^"]+)"\)/', $docComment, $matches); $queryString = $matches ? $matches[1] : ''; + // Trim the query string of whitespace and line breaks $queryString = trim($queryString, " \r\n\t "); - if(empty($queryString)) - { + + if (empty($queryString)) { // Try reading the query in another way preg_match('/@query\s*\(\s*"(.*?)"\s*\)/s', $docComment, $matches); $queryString = $matches ? $matches[1] : ''; - if(empty($queryString)) - { - throw new InvalidQueryInputException("No query found.\r\n".$docComment); + + if (empty($queryString)) { + throw new InvalidQueryInputException("No query found.\r\n" . $docComment); } } + return $queryString; } + /** * Maps PHP types to PDO parameter types. * diff --git a/tests/titip.php b/tests/titip.php new file mode 100644 index 00000000..f968594b --- /dev/null +++ b/tests/titip.php @@ -0,0 +1,227 @@ +/** + * Executes a database query based on the parameters and annotations from the caller function. + * + * This method uses reflection to retrieve the query string from the caller's docblock, + * bind the parameters, and execute the query against the database. + * + * It analyzes the parameters and return type of the caller function, enabling dynamic query + * execution tailored to the specified return type. Supported return types include: + * - `void`: Returns null. + * - `int` or `integer`: Returns the number of affected rows. + * - `object` or `stdClass`: Returns a single result as an object. + * - `stdClass[]`: Returns all results as an array of stdClass objects. + * - `array`: Returns all results as an associative array. + * - `string`: Returns the JSON-encoded results. + * - `PDOStatement`: Returns the prepared statement for further operations if needed. + * - `MagicObject` and its derived classes: If the return type is a class name or an array of class names, + * instances of that class will be created for each row fetched. + * + * @return mixed Returns the result based on the return type of the caller function: + * - null if the return type is void. + * - integer for the number of affected rows if the return type is int. + * - object for a single result if the return type is object. + * - an array of associative arrays for multiple results if the return type is array. + * - a JSON string if the return type is string. + * - instances of a specified class if the return type matches a class name. + * + * @throws PDOException If there is an error executing the database query. + * @throws InvalidQueryInputException If there is no query to be executed. + * @throws InvalidReturnTypeException If the return type specified is invalid. + */ + protected function executeNativeQuery() //NOSONAR + { + // Retrieve caller trace information + $trace = debug_backtrace(); + + // Get parameters from the caller function + $callerParamValues = isset($trace[1]['args']) ? $trace[1]['args'] : []; + + // Get the name of the caller function and class + $callerFunctionName = $trace[1]['function']; + $callerClassName = $trace[1]['class']; + + // Use reflection to get annotations from the caller function + $reflection = new ReflectionMethod($callerClassName, $callerFunctionName); + $docComment = $reflection->getDocComment(); + + // Get the query from the @query annotation + preg_match('/@query\s*\("([^"]+)"\)/', $docComment, $matches); + $queryString = $matches ? $matches[1] : ''; + + $queryString = trim($queryString, " \r\n\t "); + if(empty($queryString)) + { + // Try reading the query in another way + preg_match('/@query\s*\(\s*"(.*?)"\s*\)/s', $docComment, $matches); + $queryString = $matches ? $matches[1] : ''; + if(empty($queryString)) + { + throw new InvalidQueryInputException("No query found.\r\n".$docComment); + } + } + + // Get parameter information from the caller function + $callerParams = $reflection->getParameters(); + + // Get return type from the caller function + preg_match('/@return\s+([^\s]+)/', $docComment, $matches); + $returnType = $matches ? $matches[1] : 'void'; + + // Trim return type + $returnType = trim($returnType); + + // Change self to callerClassName + if($returnType == "self[]") + { + $returnType = $callerClassName."[]"; + } + else if($returnType == "self") + { + $returnType = $callerClassName; + } + + $params = []; + $pageable = null; + $sortable = null; + try { + // Get database connection + $pdo = $this->_database->getDatabaseConnection(); + + // Replace array + foreach ($callerParamValues as $index => $paramValue) { + if($paramValue instanceof PicoPageable) + { + $pageable = $paramValue; + } + else if($paramValue instanceof PicoSortable) + { + $sortable = $paramValue; + } + else if (isset($callerParams[$index])) { + // Format parameter name according to the query + $paramName = $callerParams[$index]->getName(); + if(is_array($paramValue)) + { + $queryString = str_replace(":".$paramName, PicoDatabaseUtil::toList($paramValue, true, true), $queryString); + } + } + } + + if(isset($pageable) || isset($sortable)) + { + $queryBuilder = new PicoDatabaseQueryBuilder($this->_database->getDatabaseType()); + $queryString = $queryBuilder->addPaginationAndSorting($queryString, $pageable, $sortable); + } + + $stmt = $pdo->prepare($queryString); + + // Automatically bind each parameter + foreach ($callerParamValues as $index => $paramValue) { + if (isset($callerParams[$index])) { + if($paramValue instanceof PicoPageable || $paramValue instanceof PicoSortable) + { + // skip + } + else + { + // Format parameter name according to the query + $paramName = $callerParams[$index]->getName(); + if(!is_array($paramValue)) + { + $maped = $this->mapToPdoParamType($paramValue); + $paramType = $maped->type; + $paramValue = $maped->value; + $params[$paramName] = $paramValue; + $stmt->bindValue(":".$paramName, $paramValue, $paramType); + } + } + } + } + + // Send query to logger + $debugFunction = $this->_database->getCallbackDebugQuery(); + if(isset($debugFunction) && is_callable($debugFunction)) + { + call_user_func($debugFunction, PicoDatabaseUtil::getFinalQuery($stmt, $params)); + } + + // Execute the query + $stmt->execute(); + + if ($returnType == "void") { + // Return null if the return type is void + return null; + } + if ($returnType == "PDOStatement") { + // Return the PDOStatement object + return $stmt; + } else if ($returnType == "int" || $returnType == "integer") { + // Return the affected row count + return $stmt->rowCount(); + } else if ($returnType == "object" || $returnType == "stdClass") { + // Return one row as an object + return $stmt->fetch(PDO::FETCH_OBJ); + } else if ($returnType == "array") { + // Return all rows as an associative array + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } else if ($returnType == "string") { + // Return the result as a JSON string + return json_encode($stmt->fetchAll(PDO::FETCH_OBJ)); + } else { + try { + // Check for array-type hinting in the return type + if (stripos($returnType, "[") !== false) { + $className = trim(explode("[", $returnType)[0]); + if ($className == "stdClass") { + // Return all rows as stdClass objects + return $stmt->fetchAll(PDO::FETCH_OBJ); + } + else if($className == 'MagicObject') { + $result = $stmt->fetchAll(PDO::FETCH_OBJ); + $ret = []; + foreach ($result as $row) { + $ret[] = new MagicObject($row); + } + return $ret; + } + else if (class_exists($className)) { + // Map result rows to the specified class + $obj = new $className(); + if($obj instanceof MagicObject) { + $result = $stmt->fetchAll(PDO::FETCH_OBJ); + foreach ($result as $row) { + $ret[] = new $className($row); + } + return $ret; + } + } + throw new InvalidReturnTypeException("Invalid return type for $className"); + } else { + // Return a single object of the specified class + $className = trim($returnType); + if($className == 'MagicObject') { + $row = $stmt->fetch(PDO::FETCH_OBJ); + return new MagicObject($row); + } + else if (class_exists($className)) { + $obj = new $className(); + if($obj instanceof MagicObject) { + $row = $stmt->fetch(PDO::FETCH_OBJ); + return $obj->loadData($row); + } + } + throw new InvalidReturnTypeException("Invalid return type for $className"); + } + } catch (Exception $e) { + // Log the exception if the class is not found + throw new InvalidReturnTypeException("Invalid return type for $className"); + } + } + } + catch (PDOException $e) + { + // Handle database errors with logging + throw new PDOException($e->getMessage(), $e->getCode(), $e); + } + return null; + } \ No newline at end of file From 74f909ca3b5a17a77dc5c2b01deae3a4d4f2a9d5 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 10:28:20 +0700 Subject: [PATCH 31/55] Update MagicObject.php --- src/MagicObject.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 10600bee..265ae0f3 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -749,11 +749,10 @@ protected function executeNativeQuery() $params = []; try { - // Get database connection - $pdo = $this->_database->getDatabaseConnection(); - $queryString = $this->applyQueryParameters($queryString, $callerParams, $callerParamValues); + // Get database connection + $pdo = $this->_database->getDatabaseConnection(); $stmt = $pdo->prepare($queryString); // Automatically bind each parameter From 83377fdd6487531ba15ff71e29da6f5d09fa4497 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 16:02:04 +0700 Subject: [PATCH 32/55] Refactor --- src/MagicObject.php | 249 +------------------- src/Util/Database/NativeQueryUtil.php | 321 ++++++++++++++++++++++++++ tests/caller.php | 8 +- 3 files changed, 331 insertions(+), 247 deletions(-) create mode 100644 src/Util/Database/NativeQueryUtil.php diff --git a/src/MagicObject.php b/src/MagicObject.php index 265ae0f3..bb90d470 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -25,6 +25,7 @@ use MagicObject\Exceptions\NoRecordFoundException; use MagicObject\Util\ClassUtil\PicoAnnotationParser; use MagicObject\Util\ClassUtil\PicoObjectParser; +use MagicObject\Util\Database\NativeQueryUtil; use MagicObject\Util\Database\PicoDatabaseUtil; use MagicObject\Util\PicoArrayUtil; use MagicObject\Util\PicoEnvironmentVariable; @@ -728,6 +729,7 @@ protected function executeNativeQuery() { // Retrieve caller trace information $trace = debug_backtrace(); + $nativeQueryUtil = new NativeQueryUtil(); // Get parameters from the caller function $callerParamValues = isset($trace[1]['args']) ? $trace[1]['args'] : []; @@ -740,8 +742,8 @@ protected function executeNativeQuery() $reflection = new ReflectionMethod($callerClassName, $callerFunctionName); $docComment = $reflection->getDocComment(); - $queryString = $this->extractQueryString($docComment); - $returnType = $this->extractReturnType($docComment, $callerClassName); + $queryString = $nativeQueryUtil->extractQueryString($docComment); + $returnType = $nativeQueryUtil->extractReturnType($docComment, $callerClassName); // Get parameter information from the caller function $callerParams = $reflection->getParameters(); @@ -749,7 +751,7 @@ protected function executeNativeQuery() $params = []; try { - $queryString = $this->applyQueryParameters($queryString, $callerParams, $callerParamValues); + $queryString = $nativeQueryUtil->applyQueryParameters($this->_database->getDatabaseType(), $queryString, $callerParams, $callerParamValues); // Get database connection $pdo = $this->_database->getDatabaseConnection(); @@ -782,7 +784,7 @@ protected function executeNativeQuery() // Execute the query $stmt->execute(); - return $this->handleReturnObject($stmt, $returnType); + return $nativeQueryUtil->handleReturnObject($stmt, $returnType); } catch (PDOException $e) { @@ -791,245 +793,6 @@ protected function executeNativeQuery() } return null; } - - /** - * Replaces array parameters in the query string and applies pagination and sorting if necessary. - * - * This method iterates over the provided caller parameters and their values, replacing any array-type - * parameters with their string equivalents in the query string. Additionally, if any pagination or sorting - * objects (e.g., `PicoPageable` or `PicoSortable`) are detected in the parameters, it modifies the query - * string to include pagination and sorting clauses. - * - * @param string $queryString The SQL query string that may contain placeholders for parameters. - * @param ReflectionParameter[] $callerParams The parameters of the calling method (reflection objects). - * @param array $callerParamValues The actual values of the parameters passed to the calling method. - * @return string The modified query string with array parameters replaced and pagination/sorting applied. - * @throws InvalidArgumentException If the provided parameters are not in the expected format. - */ - private function applyQueryParameters($queryString, $callerParams, $callerParamValues) - { - $pageable = null; - $sortable = null; - - // Replace array - foreach ($callerParamValues as $index => $paramValue) { - if($paramValue instanceof PicoPageable) - { - $pageable = $paramValue; - } - else if($paramValue instanceof PicoSortable) - { - $sortable = $paramValue; - } - else if (isset($callerParams[$index])) { - // Format parameter name according to the query - $paramName = $callerParams[$index]->getName(); - if(is_array($paramValue)) - { - $queryString = str_replace(":".$paramName, PicoDatabaseUtil::toList($paramValue, true, true), $queryString); - } - } - } - - // Apply pagination and sorting if needed - if(isset($pageable) || isset($sortable)) - { - $queryBuilder = new PicoDatabaseQueryBuilder($this->_database->getDatabaseType()); - $queryString = $queryBuilder->addPaginationAndSorting($queryString, $pageable, $sortable); - } - - return $queryString; - } - - /** - * Handles the return of data based on the specified return type. - * - * This method processes the data returned from a PDO statement and returns it in the format - * specified by the caller's `@return` docblock annotation. It supports various return types - * such as `void`, `PDOStatement`, `int`, `object`, `array`, `string`, or any specific class - * name (including array-type hinting). - * - * @param PDOStatement $stmt The executed PDO statement. - * @param string $returnType The return type as specified in the caller function's docblock. - * @return mixed The processed return data, which can be a single value, object, array, - * PDOStatement, or a JSON string, based on the return type. - * @throws InvalidReturnTypeException If the return type is invalid or unrecognized. - */ - private function handleReturnObject($stmt, $returnType) //NOSONAR - { - if ($returnType == "void") { - // Return null if the return type is void - return null; - } - if ($returnType == "PDOStatement") { - // Return the PDOStatement object - return $stmt; - } else if ($returnType == "int" || $returnType == "integer") { - // Return the affected row count - return $stmt->rowCount(); - } else if ($returnType == "object" || $returnType == "stdClass") { - // Return one row as an object - return $stmt->fetch(PDO::FETCH_OBJ); - } else if ($returnType == "array") { - // Return all rows as an associative array - return $stmt->fetchAll(PDO::FETCH_ASSOC); - } else if ($returnType == "string") { - // Return the result as a JSON string - return json_encode($stmt->fetchAll(PDO::FETCH_OBJ)); - } else { - try { - // Check for array-type hinting in the return type - if (stripos($returnType, "[") !== false) { - $className = trim(explode("[", $returnType)[0]); - if ($className == "stdClass") { - // Return all rows as stdClass objects - return $stmt->fetchAll(PDO::FETCH_OBJ); - } - else if($className == 'MagicObject') { - $result = $stmt->fetchAll(PDO::FETCH_OBJ); - $ret = []; - foreach ($result as $row) { - $ret[] = new MagicObject($row); - } - return $ret; - } - else if (class_exists($className)) { - // Map result rows to the specified class - $obj = new $className(); - if($obj instanceof MagicObject) { - $result = $stmt->fetchAll(PDO::FETCH_OBJ); - foreach ($result as $row) { - $ret[] = new $className($row); - } - return $ret; - } - } - throw new InvalidReturnTypeException("Invalid return type for $className"); - } else { - // Return a single object of the specified class - $className = trim($returnType); - if($className == 'MagicObject') { - $row = $stmt->fetch(PDO::FETCH_OBJ); - return new MagicObject($row); - } - else if (class_exists($className)) { - $obj = new $className(); - if($obj instanceof MagicObject) { - $row = $stmt->fetch(PDO::FETCH_OBJ); - return $obj->loadData($row); - } - } - throw new InvalidReturnTypeException("Invalid return type for $className"); - } - } catch (Exception $e) { - // Log the exception if the class is not found - throw new InvalidReturnTypeException("Invalid return type for $className"); - } - } - } - - /** - * Extracts the return type from the docblock of the caller function. - * - * The method processes the `@return` annotation in the docblock of the caller function, - * and adjusts for `self` to return the actual caller class name. It also handles array type - * return values. - * - * @param string $docComment The docblock comment of the caller function. - * @param string $callerClassName The name of the class where the caller function is defined. - * @return string The processed return type, which could be a class name, `self`, or `void`. - */ - private function extractReturnType($docComment, $callerClassName) - { - // Get return type from the caller function - preg_match('/@return\s+([^\s]+)/', $docComment, $matches); - $returnType = $matches ? $matches[1] : 'void'; - - // Trim return type - $returnType = trim($returnType); - - // Change self to callerClassName - if ($returnType == "self[]") { - $returnType = $callerClassName . "[]"; - } else if ($returnType == "self") { - $returnType = $callerClassName; - } - - return $returnType; - } - - /** - * Extracts the query string from the docblock of the caller function. - * - * The method looks for the `@query` annotation in the docblock and extracts the query string. - * It tries to handle different formats for the annotation, throwing an exception if no query is found. - * - * @param string $docComment The docblock comment of the caller function. - * @return string The SQL query string extracted from the `@query` annotation. - * @throws InvalidQueryInputException If no query string is found in the docblock. - */ - private function extractQueryString($docComment) - { - // Get the query from the @query annotation - preg_match('/@query\s*\("([^"]+)"\)/', $docComment, $matches); - $queryString = $matches ? $matches[1] : ''; - - // Trim the query string of whitespace and line breaks - $queryString = trim($queryString, " \r\n\t "); - - if (empty($queryString)) { - // Try reading the query in another way - preg_match('/@query\s*\(\s*"(.*?)"\s*\)/s', $docComment, $matches); - $queryString = $matches ? $matches[1] : ''; - - if (empty($queryString)) { - throw new InvalidQueryInputException("No query found.\r\n" . $docComment); - } - } - - return $queryString; - } - - - /** - * Maps PHP types to PDO parameter types. - * - * This function determines the appropriate PDO parameter type based on the given value. - * It handles various PHP data types and converts them to the corresponding PDO parameter types - * required for executing prepared statements in PDO. - * - * @param mixed $value The value to determine the type for. This can be of any type, including - * null, boolean, integer, string, DateTime, or other types. - * @return stdClass An object containing: - * - type: The PDO parameter type (PDO::PARAM_STR, PDO::PARAM_NULL, - * PDO::PARAM_BOOL, PDO::PARAM_INT). - * - value: The corresponding value formatted as needed for the PDO parameter. - */ - private function mapToPdoParamType($value) - { - $type = PDO::PARAM_STR; // Default type is string - $finalValue = $value; // Initialize final value to the original value - - if ($value instanceof DateTime) { - $type = PDO::PARAM_STR; // DateTime should be treated as a string - $finalValue = $value->format("Y-m-d H:i:s"); - } else if (is_null($value)) { - $type = PDO::PARAM_NULL; // NULL type - $finalValue = null; // Set final value to null - } else if (is_bool($value)) { - $type = PDO::PARAM_BOOL; // Boolean type - $finalValue = $value; // Keep the boolean value - } else if (is_int($value)) { - $type = PDO::PARAM_INT; // Integer type - $finalValue = $value; // Keep the integer value - } - - // Create and return an object with the type and value - $result = new stdClass(); - $result->type = $type; - $result->value = $finalValue; - return $result; - } /** * Insert into the database. diff --git a/src/Util/Database/NativeQueryUtil.php b/src/Util/Database/NativeQueryUtil.php new file mode 100644 index 00000000..469ef04f --- /dev/null +++ b/src/Util/Database/NativeQueryUtil.php @@ -0,0 +1,321 @@ + $paramValue) { + if($paramValue instanceof PicoPageable) + { + $pageable = $paramValue; + } + else if($paramValue instanceof PicoSortable) + { + $sortable = $paramValue; + } + else if (isset($callerParams[$index])) { + // Format parameter name according to the query + $paramName = $callerParams[$index]->getName(); + if(is_array($paramValue)) + { + $queryString = str_replace(":".$paramName, PicoDatabaseUtil::toList($paramValue, true, true), $queryString); + } + } + } + + // Apply pagination and sorting if needed + if(isset($pageable) || isset($sortable)) + { + $queryBuilder = new PicoDatabaseQueryBuilder($databaseType); + $queryString = $queryBuilder->addPaginationAndSorting($queryString, $pageable, $sortable); + } + + return $queryString; + } + + /** + * Handles the return of data based on the specified return type. + * + * This method processes the data returned from a PDO statement and returns it in the format + * specified by the caller's `@return` docblock annotation. It supports various return types + * such as `void`, `PDOStatement`, `int`, `object`, `array`, `string`, or any specific class + * name (including array-type hinting). + * + * @param PDOStatement $stmt The executed PDO statement. + * @param string $returnType The return type as specified in the caller function's docblock. + * @return mixed The processed return data, which can be a single value, object, array, + * PDOStatement, or a JSON string, based on the return type. + * @throws InvalidReturnTypeException If the return type is invalid or unrecognized. + */ + public function handleReturnObject($stmt, $returnType) //NOSONAR + { + // Handle basic return types + switch ($returnType) { + case 'void': + return null; + + case 'PDOStatement': + return $stmt; + + case 'int': + case 'integer': + return $stmt->rowCount(); + + case 'object': + case 'stdClass': + return $stmt->fetch(PDO::FETCH_OBJ); + + case 'array': + return $stmt->fetchAll(PDO::FETCH_ASSOC); + + case 'string': + return json_encode($stmt->fetchAll(PDO::FETCH_OBJ)); + default: + break; + } + + // Handle array-type hinting (e.g., MagicObject[], MyClass[]) + if (strpos($returnType, "[") !== false) { + return $this->handleArrayReturnType($stmt, $returnType); + } + + // Handle single class-type return (e.g., MagicObject, MyClass) + return $this->handleSingleClassReturnType($stmt, $returnType); + } + + /** + * Handles return types with array hinting (e.g., `MagicObject[]`, `MyClass[]`). + * + * @param PDOStatement $stmt The executed PDO statement. + * @param string $returnType The array-type return type (e.g., `MagicObject[]`). + * @return array The processed result as an array of objects. + * @throws InvalidReturnTypeException If the return type is invalid or unrecognized. + */ + private function handleArrayReturnType($stmt, $returnType) + { + $className = trim(explode("[", $returnType)[0]); + + switch ($className) { + case 'stdClass': + return $stmt->fetchAll(PDO::FETCH_OBJ); + + case 'MagicObject': + return $this->mapRowsToMagicObject($stmt); + + default: + if (class_exists($className)) { + return $this->mapRowsToClass($stmt, $className); + } + + throw new InvalidReturnTypeException("Invalid return type for array of $className"); + } + } + + /** + * Handles return types that are a single object (e.g., `MagicObject`, `MyClass`). + * + * @param PDOStatement $stmt The executed PDO statement. + * @param string $returnType The single-class return type (e.g., `MagicObject`). + * @return mixed The processed result as a single object. + * @throws InvalidReturnTypeException If the return type is invalid or unrecognized. + */ + private function handleSingleClassReturnType($stmt, $returnType) + { + $className = trim($returnType); + + // Check if the return type is 'MagicObject' + if ($className === 'MagicObject') { + $row = $stmt->fetch(PDO::FETCH_OBJ); + return new MagicObject($row); + } + + // Check if the class exists + if (class_exists($className)) { + $obj = new $className(); + + // If the class is an instance of MagicObject, load data + if ($obj instanceof MagicObject) { + $row = $stmt->fetch(PDO::FETCH_OBJ); + return $obj->loadData($row); + } + + // Return the class instance (assuming it's valid) + return $obj; + } + + // Throw an exception if the class does not exist or the return type is invalid + throw new InvalidReturnTypeException("Invalid return type for $className"); + } + + /** + * Maps rows from the PDO statement to an array of MagicObject instances. + * + * @param PDOStatement $stmt The executed PDO statement. + * @return MagicObject[] An array of MagicObject instances. + */ + private function mapRowsToMagicObject($stmt) + { + $result = $stmt->fetchAll(PDO::FETCH_OBJ); + $objects = []; + + foreach ($result as $row) { + $objects[] = new MagicObject($row); + } + + return $objects; + } + + /** + * Maps rows from the PDO statement to an array of instances of a specified class. + * + * @param PDOStatement $stmt The executed PDO statement. + * @param string $className The class name to map rows to. + * @return object[] An array of instances of the specified class. + * @throws InvalidReturnTypeException If the class does not exist. + */ + private function mapRowsToClass($stmt, $className) + { + $result = $stmt->fetchAll(PDO::FETCH_OBJ); + $objects = []; + + foreach ($result as $row) { + $objects[] = new $className($row); + } + + return $objects; + } + + /** + * Extracts the return type from the docblock of the caller function. + * + * The method processes the `@return` annotation in the docblock of the caller function, + * and adjusts for `self` to return the actual caller class name. It also handles array type + * return values. + * + * @param string $docComment The docblock comment of the caller function. + * @param string $callerClassName The name of the class where the caller function is defined. + * @return string The processed return type, which could be a class name, `self`, or `void`. + */ + public function extractReturnType($docComment, $callerClassName) + { + // Get return type from the caller function + preg_match('/@return\s+([^\s]+)/', $docComment, $matches); + $returnType = $matches ? $matches[1] : 'void'; + + // Trim return type + $returnType = trim($returnType); + + // Change self to callerClassName + if ($returnType == "self[]") { + $returnType = $callerClassName . "[]"; + } else if ($returnType == "self") { + $returnType = $callerClassName; + } + + return $returnType; + } + + /** + * Extracts the query string from the docblock of the caller function. + * + * The method looks for the `@query` annotation in the docblock and extracts the query string. + * It tries to handle different formats for the annotation, throwing an exception if no query is found. + * + * @param string $docComment The docblock comment of the caller function. + * @return string The SQL query string extracted from the `@query` annotation. + * @throws InvalidQueryInputException If no query string is found in the docblock. + */ + public function extractQueryString($docComment) + { + // Get the query from the @query annotation + preg_match('/@query\s*\("([^"]+)"\)/', $docComment, $matches); + $queryString = $matches ? $matches[1] : ''; + + // Trim the query string of whitespace and line breaks + $queryString = trim($queryString, " \r\n\t "); + + if (empty($queryString)) { + // Try reading the query in another way + preg_match('/@query\s*\(\s*"(.*?)"\s*\)/s', $docComment, $matches); + $queryString = $matches ? $matches[1] : ''; + + if (empty($queryString)) { + throw new InvalidQueryInputException("No query found.\r\n" . $docComment); + } + } + + return $queryString; + } + + + /** + * Maps PHP types to PDO parameter types. + * + * This function determines the appropriate PDO parameter type based on the given value. + * It handles various PHP data types and converts them to the corresponding PDO parameter types + * required for executing prepared statements in PDO. + * + * @param mixed $value The value to determine the type for. This can be of any type, including + * null, boolean, integer, string, DateTime, or other types. + * @return stdClass An object containing: + * - type: The PDO parameter type (PDO::PARAM_STR, PDO::PARAM_NULL, + * PDO::PARAM_BOOL, PDO::PARAM_INT). + * - value: The corresponding value formatted as needed for the PDO parameter. + */ + public function mapToPdoParamType($value) + { + $type = PDO::PARAM_STR; // Default type is string + $finalValue = $value; // Initialize final value to the original value + + if ($value instanceof DateTime) { + $type = PDO::PARAM_STR; // DateTime should be treated as a string + $finalValue = $value->format("Y-m-d H:i:s"); + } else if (is_null($value)) { + $type = PDO::PARAM_NULL; // NULL type + $finalValue = null; // Set final value to null + } else if (is_bool($value)) { + $type = PDO::PARAM_BOOL; // Boolean type + $finalValue = $value; // Keep the boolean value + } else if (is_int($value)) { + $type = PDO::PARAM_INT; // Integer type + $finalValue = $value; // Keep the integer value + } + + // Create and return an object with the type and value + $result = new stdClass(); + $result->type = $type; + $result->value = $finalValue; + return $result; + } +} \ No newline at end of file diff --git a/tests/caller.php b/tests/caller.php index dd4c8472..73e994f1 100644 --- a/tests/caller.php +++ b/tests/caller.php @@ -321,10 +321,10 @@ public function native13($pageable, $sortable, $aktif) // For the MagicObject return type, users can utilize the features of the MagicObject except for interacting with the database again because native queries are designed for a different purpose. -echo "Alamat: " . $native8->getTelepon() . "\r\n"; -echo "Alamat: " . $native9[0]->getTelepon() . "\r\n"; -echo "Alamat: " . $native10->getTelepon() . "\r\n"; -echo "Alamat: " . $native11[0]->getTelepon() . "\r\n"; +echo "Telepon: " . $native8->getTelepon() . "\r\n"; +echo "Telepon: " . $native9[0]->getTelepon() . "\r\n"; +echo "Telepon: " . $native10->getTelepon() . "\r\n"; +echo "Telepon: " . $native11[0]->getTelepon() . "\r\n"; $sortable = new PicoSortable(); From 32f4cf1451199aa7ecf26ce31e5d021c61347a51 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 16:04:01 +0700 Subject: [PATCH 33/55] Bug fix --- src/MagicObject.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index bb90d470..80b10755 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -765,7 +765,7 @@ protected function executeNativeQuery() $paramName = $callerParams[$index]->getName(); if(!is_array($paramValue)) { - $maped = $this->mapToPdoParamType($paramValue); + $maped = $nativeQueryUtil->mapToPdoParamType($paramValue); $paramType = $maped->type; $paramValue = $maped->value; $params[$paramName] = $paramValue; From a06c83e1b5f3e1f5b37df55078bbc8f38ba02ba8 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 16:14:05 +0700 Subject: [PATCH 34/55] Update native query debugger --- src/MagicObject.php | 8 ++---- src/Util/Database/NativeQueryUtil.php | 41 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 80b10755..4efe5ce9 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -774,12 +774,8 @@ protected function executeNativeQuery() } } - // Send query to logger - $debugFunction = $this->_database->getCallbackDebugQuery(); - if(isset($debugFunction) && is_callable($debugFunction)) - { - call_user_func($debugFunction, PicoDatabaseUtil::getFinalQuery($stmt, $params)); - } + // Debug query + $nativeQueryUtil->debugQuery($this->_database, $stmt, $params); // Execute the query $stmt->execute(); diff --git a/src/Util/Database/NativeQueryUtil.php b/src/Util/Database/NativeQueryUtil.php index 469ef04f..6d4628cd 100644 --- a/src/Util/Database/NativeQueryUtil.php +++ b/src/Util/Database/NativeQueryUtil.php @@ -3,6 +3,7 @@ namespace MagicObject\Util\Database; use DateTime; +use MagicObject\Database\PicoDatabase; use MagicObject\Database\PicoDatabaseQueryBuilder; use MagicObject\Database\PicoPageable; use MagicObject\Database\PicoSortable; @@ -10,8 +11,25 @@ use MagicObject\Exceptions\InvalidReturnTypeException; use MagicObject\MagicObject; use PDO; +use PDOStatement; use stdClass; +/** + * Utility class for working with SQL queries in the context of MagicObject's database operations. + * + * The `NativeQueryUtil` class provides methods for handling SQL queries with dynamic parameters, + * pagination, and sorting. It includes functionality for generating modified query strings with + * array-type parameters, handling return types (e.g., `PDOStatement`, objects, arrays), + * extracting return types and queries from docblocks, and mapping PHP values to PDO parameter types. + * Additionally, it supports debugging by logging generated SQL queries. + * + * Key responsibilities include: + * - Extracting SQL queries and return types from docblocks. + * - Converting PHP types into appropriate PDO parameter types. + * - Modifying query strings to handle array parameters and apply pagination/sorting. + * - Processing data returned from PDO statements and converting it to the expected return types. + * - Debugging SQL queries by sending them to a logger function. + */ class NativeQueryUtil { /** @@ -318,4 +336,27 @@ public function mapToPdoParamType($value) $result->value = $finalValue; return $result; } + + /** + * Debugs an SQL query by sending it to a logger callback function. + * + * This method retrieves the callback function for debugging queries from the database object + * and invokes it with the final SQL query string, which is generated by combining the SQL + * statement with the provided parameters. + * + * @param PicoDatabase $database The database instance that contains the callback function. + * @param PDOStatement $stmt The PDO statement object that holds the prepared query. + * @param array $params The parameters that are bound to the SQL statement. + * + * @return void + */ + public function debugQuery($database, $stmt, $params) + { + // Send query to logger + $debugFunction = $database->getCallbackDebugQuery(); + if (isset($debugFunction) && is_callable($debugFunction)) { + call_user_func($debugFunction, PicoDatabaseUtil::getFinalQuery($stmt, $params)); + } + } + } \ No newline at end of file From cc3237c805628f90b61208af3ec375fab3a6d59d Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 16:19:44 +0700 Subject: [PATCH 35/55] Update MagicObject.php --- src/MagicObject.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 4efe5ce9..280541f3 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -729,7 +729,6 @@ protected function executeNativeQuery() { // Retrieve caller trace information $trace = debug_backtrace(); - $nativeQueryUtil = new NativeQueryUtil(); // Get parameters from the caller function $callerParamValues = isset($trace[1]['args']) ? $trace[1]['args'] : []; @@ -741,7 +740,10 @@ protected function executeNativeQuery() // Use reflection to get annotations from the caller function $reflection = new ReflectionMethod($callerClassName, $callerFunctionName); $docComment = $reflection->getDocComment(); - + + $nativeQueryUtil = new NativeQueryUtil(); + + // Extract query string and return type from docblock annotations $queryString = $nativeQueryUtil->extractQueryString($docComment); $returnType = $nativeQueryUtil->extractReturnType($docComment, $callerClassName); @@ -751,43 +753,41 @@ protected function executeNativeQuery() $params = []; try { + // Apply query parameters (pagination, sorting, etc.) $queryString = $nativeQueryUtil->applyQueryParameters($this->_database->getDatabaseType(), $queryString, $callerParams, $callerParamValues); // Get database connection $pdo = $this->_database->getDatabaseConnection(); $stmt = $pdo->prepare($queryString); - // Automatically bind each parameter + // Bind parameters to the prepared statement foreach ($callerParamValues as $index => $paramValue) { - if(isset($callerParams[$index]) && !($paramValue instanceof PicoPageable) && !($paramValue instanceof PicoSortable)) - { + if (isset($callerParams[$index]) && !($paramValue instanceof PicoPageable) && !($paramValue instanceof PicoSortable)) { // Format parameter name according to the query $paramName = $callerParams[$index]->getName(); - if(!is_array($paramValue)) - { - $maped = $nativeQueryUtil->mapToPdoParamType($paramValue); - $paramType = $maped->type; - $paramValue = $maped->value; + if (!is_array($paramValue)) { + $mapped = $nativeQueryUtil->mapToPdoParamType($paramValue); + $paramType = $mapped->type; + $paramValue = $mapped->value; $params[$paramName] = $paramValue; - $stmt->bindValue(":".$paramName, $paramValue, $paramType); + $stmt->bindValue(":" . $paramName, $paramValue, $paramType); } } } - - // Debug query + + // Debug query before execution $nativeQueryUtil->debugQuery($this->_database, $stmt, $params); // Execute the query $stmt->execute(); + // Handle the return value based on the specified return type return $nativeQueryUtil->handleReturnObject($stmt, $returnType); } - catch (PDOException $e) - { + catch (PDOException $e) { // Handle database errors with logging throw new PDOException($e->getMessage(), $e->getCode(), $e); } - return null; } /** From 772ca60497a16c7ba8348a8088d8940637c07d3c Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 16:24:01 +0700 Subject: [PATCH 36/55] Update NativeQueryUtil.php --- src/Util/Database/NativeQueryUtil.php | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Util/Database/NativeQueryUtil.php b/src/Util/Database/NativeQueryUtil.php index 6d4628cd..4e12d284 100644 --- a/src/Util/Database/NativeQueryUtil.php +++ b/src/Util/Database/NativeQueryUtil.php @@ -144,19 +144,14 @@ private function handleArrayReturnType($stmt, $returnType) { $className = trim(explode("[", $returnType)[0]); - switch ($className) { - case 'stdClass': - return $stmt->fetchAll(PDO::FETCH_OBJ); - - case 'MagicObject': - return $this->mapRowsToMagicObject($stmt); - - default: - if (class_exists($className)) { - return $this->mapRowsToClass($stmt, $className); - } - - throw new InvalidReturnTypeException("Invalid return type for array of $className"); + if ($className === 'stdClass') { + return $stmt->fetchAll(PDO::FETCH_OBJ); + } elseif ($className === 'MagicObject') { + return $this->mapRowsToMagicObject($stmt); + } elseif (class_exists($className)) { + return $this->mapRowsToClass($stmt, $className); + } else { + throw new InvalidReturnTypeException("Invalid return type for array of $className"); } } From c385f012a4710e70d132d5fb77319d5f98d327b6 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 22:00:28 +0700 Subject: [PATCH 37/55] Update MagicObject.php --- src/MagicObject.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 280541f3..787c8460 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -615,7 +615,7 @@ public function save($includeNull = false) */ public function saveQuery($includeNull = false) { - if($this->_database != null && ($this->_database->getDatabaseType() != null && $this->_database->getDatabaseType() != "")) + if($this->_databaseConnected()) { $persist = new PicoDatabasePersistence($this->_database, $this); return $persist->saveQuery($includeNull); @@ -684,7 +684,7 @@ public function selectAll() */ public function selectQuery() { - if($this->_database != null && ($this->_database->getDatabaseType() != null && $this->_database->getDatabaseType() != "")) + if($this->_databaseConnected()) { $persist = new PicoDatabasePersistence($this->_database, $this); return $persist->selectQuery(); @@ -819,7 +819,7 @@ public function insert($includeNull = false) */ public function insertQuery($includeNull = false) { - if($this->_database != null && ($this->_database->getDatabaseType() != null && $this->_database->getDatabaseType() != "")) + if($this->_databaseConnected()) { $persist = new PicoDatabasePersistence($this->_database, $this); return $persist->insertQuery($includeNull); @@ -859,7 +859,7 @@ public function update($includeNull = false) */ public function updateQuery($includeNull = false) { - if($this->_database != null && ($this->_database->getDatabaseType() != null && $this->_database->getDatabaseType() != "")) + if($this->_databaseConnected()) { $persist = new PicoDatabasePersistence($this->_database, $this); return $persist->updateQuery($includeNull); @@ -897,7 +897,7 @@ public function delete() */ public function deleteQuery() { - if($this->_database != null && ($this->_database->getDatabaseType() != null && $this->_database->getDatabaseType() != "")) + if($this->_databaseConnected()) { $persist = new PicoDatabasePersistence($this->_database, $this); return $persist->deleteQuery(); @@ -916,7 +916,7 @@ public function deleteQuery() */ public function where($specification) { - if($this->_database != null && ($this->_database->getDatabaseType() != null && $this->_database->getDatabaseType() != "")) + if($this->_databaseConnected()) { $persist = new PicoDatabasePersistenceExtended($this->_database, $this); return $persist->whereWithSpecification($specification); From 47cc5a66d737ef5a0a64c5156ab5f898862b5a68 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 22:00:58 +0700 Subject: [PATCH 38/55] Update MagicObject.php --- src/MagicObject.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 787c8460..db6e48b5 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -2,7 +2,6 @@ namespace MagicObject; -use DateTime; use Exception; use PDOException; use PDOStatement; From a788b5e3866b1d8d106ba0b11ee388135e98d519 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 22:11:11 +0700 Subject: [PATCH 39/55] Update MagicObject.php --- src/MagicObject.php | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index db6e48b5..5364596d 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -729,25 +729,27 @@ protected function executeNativeQuery() // Retrieve caller trace information $trace = debug_backtrace(); - // Get parameters from the caller function + // Extract the caller's parameters $callerParamValues = isset($trace[1]['args']) ? $trace[1]['args'] : []; - // Get the name of the caller function and class + // Get the caller's function and class names $callerFunctionName = $trace[1]['function']; $callerClassName = $trace[1]['class']; - // Use reflection to get annotations from the caller function + // Use reflection to retrieve docblock annotations from the caller function $reflection = new ReflectionMethod($callerClassName, $callerFunctionName); + + // Get parameter information from the caller function + $callerParams = $reflection->getParameters(); + + // Get the docblock comment for the caller function $docComment = $reflection->getDocComment(); $nativeQueryUtil = new NativeQueryUtil(); - // Extract query string and return type from docblock annotations + // Extract the query string and return type from the docblock $queryString = $nativeQueryUtil->extractQueryString($docComment); $returnType = $nativeQueryUtil->extractReturnType($docComment, $callerClassName); - - // Get parameter information from the caller function - $callerParams = $reflection->getParameters(); $params = []; @@ -755,36 +757,38 @@ protected function executeNativeQuery() // Apply query parameters (pagination, sorting, etc.) $queryString = $nativeQueryUtil->applyQueryParameters($this->_database->getDatabaseType(), $queryString, $callerParams, $callerParamValues); - // Get database connection + // Prepare the query using the database connection $pdo = $this->_database->getDatabaseConnection(); $stmt = $pdo->prepare($queryString); - // Bind parameters to the prepared statement + // Bind the parameters to the prepared statement foreach ($callerParamValues as $index => $paramValue) { if (isset($callerParams[$index]) && !($paramValue instanceof PicoPageable) && !($paramValue instanceof PicoSortable)) { - // Format parameter name according to the query + // Bind the parameter name and type to the statement $paramName = $callerParams[$index]->getName(); if (!is_array($paramValue)) { $mapped = $nativeQueryUtil->mapToPdoParamType($paramValue); $paramType = $mapped->type; $paramValue = $mapped->value; + + // Debugging: store parameter values for query inspection $params[$paramName] = $paramValue; $stmt->bindValue(":" . $paramName, $paramValue, $paramType); } } } - // Debug query before execution + // Log the query for debugging $nativeQueryUtil->debugQuery($this->_database, $stmt, $params); // Execute the query $stmt->execute(); - // Handle the return value based on the specified return type + // Handle and return the result based on the specified return type return $nativeQueryUtil->handleReturnObject($stmt, $returnType); } catch (PDOException $e) { - // Handle database errors with logging + // Log and rethrow the exception if a database error occurs throw new PDOException($e->getMessage(), $e->getCode(), $e); } } From d36edbb2785daf1fa89053213579698c172838d1 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 22:14:05 +0700 Subject: [PATCH 40/55] Update MagicObject.php --- src/MagicObject.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 5364596d..14741cd0 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -483,6 +483,16 @@ protected function readOnly($readonly) return $this; } + /** + * Check if database is connected or not + * + * @return bool + */ + private function _databaseConnected() + { + return $this->_database != null && $this->_database->isConnected(); + } + /** * Set the database connection. * @@ -1284,7 +1294,7 @@ public function valueObject($snakeCase = null) $obj->set($key, $value); } } - $upperCamel = $this->isUpperCamel(); + $upperCamel = $this->_upperCamel(); if($upperCamel) { return json_decode(json_encode($this->valueArrayUpperCamel())); @@ -1359,7 +1369,7 @@ protected function _snakeYaml() * * @return bool True if the naming strategy is upper camel case; otherwise, false */ - protected function isUpperCamel() + protected function _upperCamel() { return isset($this->_classParams[self::JSON]) && isset($this->_classParams[self::JSON][self::PROPERTY_NAMING_STRATEGY]) @@ -1456,16 +1466,6 @@ public function listAll($specification = null, $pageable = null, $sortable = nul return $this->findAll($specification, $pageable, $sortable, $passive, $subqueryMap); } - /** - * Check if database is connected or not - * - * @return bool - */ - private function _databaseConnected() - { - return $this->_database != null && $this->_database->isConnected(); - } - /** * Count the data based on specifications * @@ -2534,7 +2534,7 @@ public function __toString() $obj->set($key, $value); } } - $upperCamel = $this->isUpperCamel(); + $upperCamel = $this->_upperCamel(); if($upperCamel) { $value = $this->valueArrayUpperCamel(); From df95ab22ad92592689aa53a08f3bce2ba71bfe2a Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 22:30:11 +0700 Subject: [PATCH 41/55] Update MagicObject.php --- src/MagicObject.php | 98 ++++++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 14741cd0..7901fb5b 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -16,6 +16,7 @@ use MagicObject\Database\PicoSortable; use MagicObject\Database\PicoSpecification; use MagicObject\Database\PicoTableInfo; +use MagicObject\Exceptions\FileNotFoundException; use MagicObject\Exceptions\FindOptionException; use MagicObject\Exceptions\InvalidAnnotationException; use MagicObject\Exceptions\InvalidQueryInputException; @@ -421,50 +422,73 @@ public function loadJsonString($rawData, $systemEnv = false, $asObject = false, } /** - * Load data from a JSON file. + * Loads data from a JSON file and processes it based on the provided options. * - * @param string $path File path to the JSON file - * @param bool $systemEnv Replace all environment variable values - * @param bool $asObject Result as an object instead of an array - * @param bool $recursive Convert all objects to MagicObject + * This method reads the contents of a JSON file, decodes it, and applies transformations + * such as replacing environment variables, camelizing the keys, and recursively converting objects + * into MagicObject instances if necessary. + * + * @param string $path The file path to the JSON file. + * @param bool $systemEnv Whether to replace system environment variables in the data (default: `false`). + * @param bool $asObject Whether to return the result as an object instead of an associative array (default: `false`). + * @param bool $recursive Whether to recursively convert all objects into MagicObject instances (default: `false`). + * * @return self Returns the current instance for method chaining. + * + * @throws FileNotFoundException If the specified JSON file does not exist. */ public function loadJsonFile($path, $systemEnv = false, $asObject = false, $recursive = false) { - $data = json_decode(file_get_contents($path)); - if(isset($data) && !empty($data)) - { + // Check if the file exists + if (!file_exists($path)) { + throw new FileNotFoundException("Specified file not found [{$path}]"); + } + + // Decode the JSON file contents as an associative array + $data = json_decode(file_get_contents($path), true); // true to decode as associative array + + // If data is valid and not empty, process it + if (!empty($data)) { + // Replace Pico environment variables in the data $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); - if($systemEnv) - { + + // If systemEnv is true, replace system environment variables + if ($systemEnv) { $data = PicoEnvironmentVariable::replaceSysEnvAll($data, true); } + + // Camelize the data keys (e.g., 'user_name' to 'userName') $data = PicoArrayUtil::camelize($data); - if($asObject) - { - // convert to object - $obj = json_decode(json_encode((object) $data), false); - if($recursive) - { - $this->loadData(PicoObjectParser::parseRecursiveObject($obj)); - } - else - { - $this->loadData($obj); - } - } - else - { - if($recursive) - { - $this->loadData(PicoObjectParser::parseRecursiveObject($data)); - } - else - { - $this->loadData($data); - } - } + + // Load the processed data (object or array, recursively if needed) + return $this->loadJsonData($data, $asObject, $recursive); + } + + return $this; + } + + /** + * Loads processed JSON data and optionally converts it to objects or parses recursively. + * + * @param mixed $data The processed data to load (array or object). + * @param bool $asObject Whether to return the result as an object. + * @param bool $recursive Whether to recursively convert all objects into MagicObject instances. + * + * @return self Returns the current instance for method chaining. + */ + private function loadJsonData($data, $asObject, $recursive) + { + if ($asObject) { + // Convert data to object + $data = json_decode(json_encode($data), false); // Convert array to object } + + // Load data, applying recursion if needed + $dataToLoad = $recursive ? PicoObjectParser::parseRecursiveObject($data) : $data; + + // Call the loadData method to process the data + $this->loadData($dataToLoad); + return $this; } @@ -1562,9 +1586,9 @@ public function findAll($specification = null, $pageable = null, $sortable = nul $startTime = microtime(true); try { - $pageData = new PicoPageData(array(), $startTime); if($this->_databaseConnected()) { + $pageData = new PicoPageData(array(), $startTime); $persist = new PicoDatabasePersistence($this->_database, $this); if($findOption & self::FIND_OPTION_NO_FETCH_DATA) { @@ -1652,10 +1676,10 @@ public function findSpecific($selected, $specification = null, $pageable = null, { $startTime = microtime(true); try - { - $pageData = new PicoPageData(array(), $startTime); + { if($this->_databaseConnected()) { + $pageData = new PicoPageData(array(), $startTime); $persist = new PicoDatabasePersistence($this->_database, $this); if($findOption & self::FIND_OPTION_NO_FETCH_DATA) { From bf804efa14beb115d647e458db4bc30f35f7d5cb Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Mon, 11 Nov 2024 22:30:44 +0700 Subject: [PATCH 42/55] Update caller.php --- tests/caller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/caller.php b/tests/caller.php index 73e994f1..966e84d8 100644 --- a/tests/caller.php +++ b/tests/caller.php @@ -11,7 +11,7 @@ require_once dirname(__DIR__) . "/vendor/autoload.php"; $databaseCredential = new SecretObject(); -$databaseCredential->loadYamlFile(dirname(dirname(__DIR__)) . "/test.yml", false, true, true); +$databaseCredential->loadYamlFile(dirname(dirname(__DIR__)) . "/test.yml.txt", false, true, true); $databaseCredential->getDatabase()->setDatabaseName("sipro"); $database = new PicoDatabase($databaseCredential->getDatabase(), null, function($sql){ echo $sql.";\r\n\r\n"; From 6af5118b67a5d20651507524fd796e2016cf99e9 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Tue, 12 Nov 2024 09:20:16 +0700 Subject: [PATCH 43/55] Update MagicObject.php --- src/MagicObject.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index 7901fb5b..a8c3c17f 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -731,10 +731,10 @@ public function selectQuery() /** * Executes a database query based on the parameters and annotations from the caller function. * - * This method uses reflection to retrieve the query string from the caller's docblock, - * bind the parameters, and execute the query against the database. + * This method uses reflection to extract the query string and return type from the caller's + * docblock, binds the provided parameters, and executes the query against the database. * - * It analyzes the parameters and return type of the caller function, enabling dynamic query + * It analyzes the parameters and return type of the caller function to enable dynamic query * execution tailored to the specified return type. Supported return types include: * - `void`: Returns null. * - `int` or `integer`: Returns the number of affected rows. @@ -743,20 +743,22 @@ public function selectQuery() * - `array`: Returns all results as an associative array. * - `string`: Returns the JSON-encoded results. * - `PDOStatement`: Returns the prepared statement for further operations if needed. - * - `MagicObject` and its derived classes: If the return type is a class name or an array of class names, - * instances of that class will be created for each row fetched. + * - `MagicObject` and its derived classes: If the return type is a class name or an array of + * class names, instances of that class will be created for each row fetched. + * - `MagicObject[]` and its derived classes: If the return type is an array of class names, + * instances of the corresponding class will be created for each row fetched. * - * @return mixed Returns the result based on the return type of the caller function: + * @return mixed The result based on the return type of the caller function: * - null if the return type is void. * - integer for the number of affected rows if the return type is int. * - object for a single result if the return type is object. * - an array of associative arrays for multiple results if the return type is array. * - a JSON string if the return type is string. * - instances of a specified class if the return type matches a class name. - * + * * @throws PDOException If there is an error executing the database query. - * @throws InvalidQueryInputException If there is no query to be executed. - * @throws InvalidReturnTypeException If the return type specified is invalid. + * @throws InvalidQueryInputException If there is no query to be executed or if the input is invalid. + * @throws InvalidReturnTypeException If the return type specified in the docblock is invalid or unrecognized. */ protected function executeNativeQuery() { From a35dd077a9e71ab2c671d8e54ceb506122485eb8 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Tue, 12 Nov 2024 09:52:33 +0700 Subject: [PATCH 44/55] Update MagicObject.php --- src/MagicObject.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index a8c3c17f..4bf98612 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -745,8 +745,8 @@ public function selectQuery() * - `PDOStatement`: Returns the prepared statement for further operations if needed. * - `MagicObject` and its derived classes: If the return type is a class name or an array of * class names, instances of that class will be created for each row fetched. - * - `MagicObject[]` and its derived classes: If the return type is an array of class names, - * instances of the corresponding class will be created for each row fetched. + * - `MagicObject[]` and its derived classes: Instances of the corresponding class will be + * created for each row fetched. * * @return mixed The result based on the return type of the caller function: * - null if the return type is void. From 3c168e9b4d0601ec449bcaaf409b3fe17422100c Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Tue, 12 Nov 2024 12:30:21 +0700 Subject: [PATCH 45/55] Add transactional (start transaction, commit, rollback) --- CHANGELOG.md | 78 +++++++++++++++++++++++++++++++ manual/includes/_entity.md | 3 ++ src/MagicObject.php | 94 ++++++++++++++++++++++++++++++++++++++ tests/entity.php | 1 + 4 files changed, 176 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..5a59219e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,78 @@ +# MagicObject Version 2 + +## What is New + +1. **Native Query** + + - Introduced support for native SQL queries, allowing users to execute raw SQL statements directly within the framework. This feature enhances flexibility and provides greater control over complex queries that may not be easily constructed using the ORM layer. +2. **Multiple Database Connection** + + - Added the ability to configure and manage multiple database connections. This allows developers to connect to different databases within the same application seamlessly, facilitating multi-database architectures and more complex application requirements. +3. **Enable or Disable Entity Cache on Join** + + - Introduced a feature to enable or disable entity caching specifically for join operations. This gives developers fine-tuned control over caching strategies, improving performance while also allowing for fresh data retrieval when necessary. +4. **Enhanced Documentation** + + - Comprehensive updates to the documentation for classes, properties, functions, and annotations. This includes clearer explanations, examples, and usage guidelines, making it easier for developers to understand and utilize the framework effectively. +5. **Bug Fixes on Previous Version** + + - Addressed various bugs and issues reported in earlier versions. This includes performance improvements, stability enhancements, and corrections of minor errors that could affect the functionality of the framework. + +## Additional Features + +- **Improved Error Handling**: Enhanced mechanisms for error detection and handling, providing more informative messages to assist developers in troubleshooting. +- **Performance Optimizations**: Various internal optimizations that improve the overall performance of the framework, particularly in database interactions. +- **Backward Compatibility**: Ensured backward compatibility with version 1, allowing for a smooth transition for existing users to upgrade without significant changes to their codebase. + +## Migration Notes + +- When upgrading from version 1 to version 2, please review the migration notes for any breaking changes or required adjustments to your codebase. Detailed guidelines are provided to facilitate a smooth upgrade process. + +# MagicObject Version 2.1 + +## What is New + +MagicObject 2.1 introduces package annotations for entities, enhancing the process of joining them. These annotations are essential, as the namespace is required to properly join entities. The join class should be referenced by its base name only, without the namespace; otherwise, MagicObject may fail to recognize the class. + +PHP does not provide a native method to retrieve a class's namespace. Earlier versions of MagicObject attempted to obtain this information by reading the PHP script, a method that proved both unsafe and inefficient. + +With the addition of package annotations to each entity, MagicObject now offers a safer and more efficient way to join entities. However, if a package annotation is not available on an entity, version 2.1 will still revert to the old method. + +MagicObject 2.1 introduces a suite of powerful database utilities aimed at enhancing database management and interoperability. One of the key features is the ability to seamlessly convert databases between PostgreSQL and MySQL, enabling developers to migrate their data and applications with ease. This conversion tool ensures that data types, constraints, and structures are accurately translated, reducing the potential for errors during migration. + +Additionally, MagicObject 2.1 allows users to parse table structures directly from SQL statements without the need to first dump them into a database. This functionality streamlines the process of understanding and manipulating database schemas, making it easier for developers to work with existing SQL code or to integrate with third-party systems. + +These utilities not only enhance efficiency but also provide a robust foundation for database development, allowing users to focus on building applications rather than wrestling with database compatibility issues. With MagicObject 2.1, database management becomes more intuitive and accessible, empowering developers to harness the full potential of their data. + + +# MagicObject Version 2.7 + +## What is New + +### PDO Support + +With the release of **MagicObject 2.7**, a significant update has been introduced to allow users to leverage **PDO** (PHP Data Objects) for database connections. In previous versions, **MagicObject** required the use of **PicoDatabase**, its custom database handling class. However, recognizing that many developers are accustomed to establishing database connections via traditional PDO, this new version introduces flexibility by allowing PDO connections to be passed directly to the **MagicObject** constructor. + +This update aims to bridge the gap between traditional PDO-based database management and the advanced features provided by **MagicObject**, thus enhancing compatibility while retaining all the powerful functionality of the framework. + +#### Why PDO Support? + +The decision to support **PDO** was made to accommodate users who have already established database connections in their applications using PDO, instead of relying on **PicoDatabase** from the start. By supporting PDO, **MagicObject** allows users to continue working with their preferred method of connecting to the database while still benefiting from the full range of features and utilities **MagicObject** offers. + +While PDO is now an option for initializing **MagicObject**, it is used only in the constructor. Once the object is initialized, **MagicObject** continues to use **PicoDatabase** for all subsequent database interactions, ensuring that users can still benefit from **PicoDatabase**'s advanced features like automatic query building, database abstraction, and optimized query execution. + +#### How PDO Support Works + +In **MagicObject 2.7**, when you pass a **PDO** connection object to the constructor, it is automatically converted into a **PicoDatabase** instance using the `PicoDatabase::fromPdo()` static method. This ensures that even though PDO is used to establish the initial connection, the object will still operate using **PicoDatabase** for all subsequent database operations. The constructor of **MagicObject** ensures that the database connection is properly initialized and the type of database is correctly detected based on the PDO driver. + +### Pageable and Sortable in Native Query in MagicObject 2.7 + +In **MagicObject version 2.7**, support for **pageable** and **sortable** functionality has been added to native queries. Previously, native queries did not support pagination and sorting directly. Instead, users had to manually include `SORT BY` and `LIMIT OFFSET` clauses in their queries, which made them less flexible. This approach was problematic because each Database Management System (DBMS) has its own syntax for writing queries, making it cumbersome to adapt queries for different platforms. + +With the introduction of pageable and sortable support in version 2.7, users can now easily pass **pagination** parameters using the `PicoPageable` type and **sorting** parameters using the `PicoSortable` type directly into their native queries. These parameters can be placed anywhere within the query, but it is recommended to position them either at the beginning or the end of the query for optimal readability and organization. + +This enhancement makes native queries more flexible and easier to maintain, as the logic for pagination and sorting is handled automatically, without requiring manual intervention for each DBMS. As a result, users can now write cleaner, more efficient, and database-agnostic native queries. + +### Transactional + +MagicObject version 2.7 introduces new features for transactional database management, namely `startTransaction()`, `commit()`, and `rollback()`. These functions allow entities to directly initiate and manage transactions within their scope. The `startTransaction()` function begins a new transaction, while `commit()` ensures that all changes made during the transaction are permanently saved to the database. On the other hand, `rollback()` can be used to revert any changes made during the transaction in case of an error or interruption. These functions require an active database connection to operate, providing a streamlined way for entities to manage data consistency and integrity within their transactions. diff --git a/manual/includes/_entity.md b/manual/includes/_entity.md index 474efaa8..0cdcdbb4 100644 --- a/manual/includes/_entity.md +++ b/manual/includes/_entity.md @@ -2,6 +2,9 @@ Entity is class to access database. Entity is derived from MagicObject. Some annotations required to activated all entity features. +MagicObject version 2.7 introduces new features for transactional database management, namely `startTransaction()`, `commit()`, and `rollback()`. These functions allow entities to directly initiate and manage transactions within their scope. The `startTransaction()` function begins a new transaction, while `commit()` ensures that all changes made during the transaction are permanently saved to the database. On the other hand, `rollback()` can be used to revert any changes made during the transaction in case of an error or interruption. These functions require an active database connection to operate, providing a streamlined way for entities to manage data consistency and integrity within their transactions. + + **Constructor** Parameters: diff --git a/src/MagicObject.php b/src/MagicObject.php index 4bf98612..b63e6312 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -946,6 +946,100 @@ public function deleteQuery() throw new NoDatabaseConnectionException(self::MESSAGE_NO_DATABASE_CONNECTION); } } + + /** + * Starts a database transaction. + * + * This method begins a new database transaction. It delegates the actual transaction + * initiation to the `transactionalCommand` method, passing the "start" command. + * + * @return self The current instance of the class for method chaining. + * + * @throws NoDatabaseConnectionException If there is no active database connection. + * @throws PDOException If there is an error while starting the transaction. + */ + public function startTransaction() + { + $this->transactionalCommand("start"); + return $this; + } + + /** + * Commits the current database transaction. + * + * This method commits the current transaction. If successful, it makes all database + * changes made during the transaction permanent. It delegates to the `transactionalCommand` method + * with the "commit" command. + * + * @return self The current instance of the class for method chaining. + * + * @throws NoDatabaseConnectionException If there is no active database connection. + * @throws PDOException If there is an error during the commit process. + */ + public function commit() + { + $this->transactionalCommand("commit"); + return $this; + } + + /** + * Rolls back the current database transaction. + * + * This method rolls back the current transaction, undoing all database changes made + * during the transaction. It calls the `transactionalCommand` method with the "rollback" command. + * + * @return self The current instance of the class for method chaining. + * + * @throws NoDatabaseConnectionException If there is no active database connection. + * @throws PDOException If there is an error during the rollback process. + */ + public function rollback() + { + $this->transactionalCommand("rollback"); + return $this; + } + + /** + * Executes a transactional SQL command (start, commit, or rollback). + * + * This method executes a SQL command to manage the state of a database transaction. + * It checks the type of command (`start_transaction`, `commit`, or `rollback`) and + * delegates the corresponding SQL generation to the `PicoDatabaseQueryBuilder` class. + * The SQL statement is then executed on the active database connection. + * + * @param string $command The transactional command to execute. Possible values are: + * - "start" to begin a new transaction. + * - "commit" to commit the current transaction. + * - "rollback" to rollback the current transaction. + * + * @return void + * + * @throws NoDatabaseConnectionException If there is no active database connection. + * @throws PDOException If there is an error while executing the transactional command. + */ + private function transactionalCommand($command) + { + if ($this->_databaseConnected()) { + try { + $queryBuilder = new PicoDatabaseQueryBuilder($this->_database); + $sql = null; + if ($command == "start") { + $sql = $queryBuilder->startTransaction(); + } elseif ($command == "commit") { + $sql = $queryBuilder->commit(); + } elseif ($command == "rollback") { + $sql = $queryBuilder->rollback(); + } + if (isset($sql)) { + $this->_database->execute($sql); + } + } catch (Exception $e) { + throw new PDOException($e); + } + } else { + throw new NoDatabaseConnectionException(self::MESSAGE_NO_DATABASE_CONNECTION); + } + } /** * Get MagicObject with WHERE specification. diff --git a/tests/entity.php b/tests/entity.php index 79b2d7eb..e8bd30c7 100644 --- a/tests/entity.php +++ b/tests/entity.php @@ -629,6 +629,7 @@ class Artist extends MagicObject $database->connect(); $album = new EntityAlbum(null, $database); + try { /* From 1a846804535b80cdc06e0bf83902e313a6c806f0 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Tue, 12 Nov 2024 12:31:50 +0700 Subject: [PATCH 46/55] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a59219e..8b738d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,7 +65,7 @@ While PDO is now an option for initializing **MagicObject**, it is used only in In **MagicObject 2.7**, when you pass a **PDO** connection object to the constructor, it is automatically converted into a **PicoDatabase** instance using the `PicoDatabase::fromPdo()` static method. This ensures that even though PDO is used to establish the initial connection, the object will still operate using **PicoDatabase** for all subsequent database operations. The constructor of **MagicObject** ensures that the database connection is properly initialized and the type of database is correctly detected based on the PDO driver. -### Pageable and Sortable in Native Query in MagicObject 2.7 +### Pageable and Sortable in Native Query In **MagicObject version 2.7**, support for **pageable** and **sortable** functionality has been added to native queries. Previously, native queries did not support pagination and sorting directly. Instead, users had to manually include `SORT BY` and `LIMIT OFFSET` clauses in their queries, which made them less flexible. This approach was problematic because each Database Management System (DBMS) has its own syntax for writing queries, making it cumbersome to adapt queries for different platforms. From ee239834a35a8034173ec75447900e4ff31261b0 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Tue, 12 Nov 2024 12:51:35 +0700 Subject: [PATCH 47/55] Upade changelog --- CHANGELOG.md | 134 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b738d1b..08f7a1d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,78 +1,134 @@ + # MagicObject Version 2 -## What is New +## What's New -1. **Native Query** - - - Introduced support for native SQL queries, allowing users to execute raw SQL statements directly within the framework. This feature enhances flexibility and provides greater control over complex queries that may not be easily constructed using the ORM layer. -2. **Multiple Database Connection** - - - Added the ability to configure and manage multiple database connections. This allows developers to connect to different databases within the same application seamlessly, facilitating multi-database architectures and more complex application requirements. -3. **Enable or Disable Entity Cache on Join** +**MagicObject Version 2** brings several exciting new features and enhancements aimed at increasing flexibility, improving performance, and providing better control over database interactions. Below are the key updates introduced in this version: + +### 1. Native Query Support + +**Native SQL Queries** are now supported in **MagicObject**, allowing users to execute raw SQL statements directly within the framework. This enhancement gives developers greater control over complex queries, which may not be easily handled by the ORM layer. You can now execute any SQL command directly, enabling the use of advanced SQL features and custom queries that go beyond the capabilities of the built-in ORM. + +### 2. Multiple Database Connection Support + +The new version of **MagicObject** introduces the ability to configure and manage **multiple database connections** within a single application. This feature allows developers to easily connect to and manage different databases simultaneously, making it ideal for applications that need to interact with multiple databases or implement multi-database architectures. Whether you're working with multiple MySQL instances, different types of databases (e.g., PostgreSQL, SQLite), or managing different environments (development, production), this feature significantly simplifies database management. + +### 3. Entity Cache Control on Joins + +**MagicObject Version 2** gives developers greater control over **entity caching** when performing **join operations**. The new feature allows you to enable or disable caching specifically for joins, providing fine-tuned control over your caching strategy. This improves performance by reducing unnecessary database hits while still ensuring fresh data retrieval when needed. You can now optimize caching on a per-query basis, making it easier to manage large data sets efficiently. + +### 4. Enhanced Documentation + +The documentation for **MagicObject** has been thoroughly updated. In this release, we've made significant improvements to the documentation for **classes**, **properties**, **functions**, and **annotations**. The documentation now includes clearer explanations, improved examples, and comprehensive usage guidelines. These changes are designed to make it easier for developers to understand and fully leverage the power of the framework, reducing the learning curve and streamlining the development process. + +### 5. Bug Fixes and Stability Enhancements + +Several bugs and issues from previous versions have been addressed in **MagicObject Version 2**. This includes improvements to **performance**, **stability**, and the **correction of minor errors** that may have affected the functionality of the framework. With these fixes, users can expect a more reliable and robust framework that performs well across a variety of use cases. + +## Additional Features + +- **Improved Error Handling**: We've introduced enhanced mechanisms for detecting and handling errors. The error messages are now more informative, helping developers to troubleshoot and resolve issues faster. This improvement also includes better stack trace information and more specific error types. - - Introduced a feature to enable or disable entity caching specifically for join operations. This gives developers fine-tuned control over caching strategies, improving performance while also allowing for fresh data retrieval when necessary. -4. **Enhanced Documentation** +- **Performance Optimizations**: Internally, **MagicObject Version 2** has been optimized to improve overall performance. Key database interaction operations have been streamlined, leading to faster query execution times and better resource utilization. - - Comprehensive updates to the documentation for classes, properties, functions, and annotations. This includes clearer explanations, examples, and usage guidelines, making it easier for developers to understand and utilize the framework effectively. -5. **Bug Fixes on Previous Version** +- **Backward Compatibility**: **MagicObject Version 2** maintains **backward compatibility** with **Version 1**, ensuring that existing users can upgrade smoothly without having to make significant changes to their codebase. This allows for an easy transition to the new version while still maintaining compatibility with legacy systems. - - Addressed various bugs and issues reported in earlier versions. This includes performance improvements, stability enhancements, and corrections of minor errors that could affect the functionality of the framework. -## Additional Features +## Migration Notes -- **Improved Error Handling**: Enhanced mechanisms for error detection and handling, providing more informative messages to assist developers in troubleshooting. -- **Performance Optimizations**: Various internal optimizations that improve the overall performance of the framework, particularly in database interactions. -- **Backward Compatibility**: Ensured backward compatibility with version 1, allowing for a smooth transition for existing users to upgrade without significant changes to their codebase. +If you are upgrading from **MagicObject Version 1** to **Version 2**, please review the migration notes carefully. The documentation includes detailed guidelines and best practices for handling any potential breaking changes, as well as adjustments that may be necessary to ensure a smooth transition. By following these guidelines, you can ensure that your upgrade process is as seamless as possible, minimizing disruptions to your development workflow. -## Migration Notes -- When upgrading from version 1 to version 2, please review the migration notes for any breaking changes or required adjustments to your codebase. Detailed guidelines are provided to facilitate a smooth upgrade process. # MagicObject Version 2.1 -## What is New +## What's New -MagicObject 2.1 introduces package annotations for entities, enhancing the process of joining them. These annotations are essential, as the namespace is required to properly join entities. The join class should be referenced by its base name only, without the namespace; otherwise, MagicObject may fail to recognize the class. +**MagicObject 2.1** introduces several powerful new features aimed at improving entity management, database interoperability, and overall ease of use. This version builds on the foundational updates from previous releases, making database handling even more efficient and developer-friendly. Here’s a detailed overview of the new additions: -PHP does not provide a native method to retrieve a class's namespace. Earlier versions of MagicObject attempted to obtain this information by reading the PHP script, a method that proved both unsafe and inefficient. +### 1. Package Annotations for Entity Joins -With the addition of package annotations to each entity, MagicObject now offers a safer and more efficient way to join entities. However, if a package annotation is not available on an entity, version 2.1 will still revert to the old method. +One of the most notable features in **MagicObject 2.1** is the introduction of **package annotations** for entities. These annotations are essential when joining entities, as they provide the necessary namespace information that is critical for the framework to correctly recognize and associate entity classes. -MagicObject 2.1 introduces a suite of powerful database utilities aimed at enhancing database management and interoperability. One of the key features is the ability to seamlessly convert databases between PostgreSQL and MySQL, enabling developers to migrate their data and applications with ease. This conversion tool ensures that data types, constraints, and structures are accurately translated, reducing the potential for errors during migration. +#### Why Package Annotations? -Additionally, MagicObject 2.1 allows users to parse table structures directly from SQL statements without the need to first dump them into a database. This functionality streamlines the process of understanding and manipulating database schemas, making it easier for developers to work with existing SQL code or to integrate with third-party systems. +PHP does not natively provide a way to directly retrieve the namespace of a class, which presented a challenge for earlier versions of **MagicObject** when attempting to perform joins. To work around this, **MagicObject** previously attempted to infer namespaces by reading the PHP script, but this method proved to be both inefficient and prone to errors. -These utilities not only enhance efficiency but also provide a robust foundation for database development, allowing users to focus on building applications rather than wrestling with database compatibility issues. With MagicObject 2.1, database management becomes more intuitive and accessible, empowering developers to harness the full potential of their data. +With **MagicObject 2.1**, the introduction of package annotations on each entity allows the framework to safely and efficiently join entities by referencing the class's base name, without needing to manually specify or infer the namespace. This makes the process of joining entities more robust and reliable. + +#### Backwards Compatibility + +If a package annotation is not present on an entity, **MagicObject 2.1** will gracefully revert to the old method of namespace inference, ensuring backwards compatibility with previous versions. However, it is strongly recommended to utilize the new package annotations for better performance and accuracy when performing entity joins. + +### 2. Seamless Database Conversion Between PostgreSQL and MySQL + +**MagicObject 2.1** introduces a powerful utility that allows developers to seamlessly convert databases between **PostgreSQL** and **MySQL**. This feature greatly simplifies the process of migrating applications and data between these two popular database systems. + +#### Key Features of Database Conversion: + +- **Data Type Mapping**: MagicObject handles the conversion of data types between PostgreSQL and MySQL, ensuring that the equivalent types are correctly mapped. +- **Constraints and Structures**: The conversion tool also accounts for database constraints, indexes, and table structures, ensuring that the integrity of the database schema is maintained during migration. +- **Error Reduction**: By automating the conversion process, MagicObject reduces the chances of errors that can occur during manual migration, saving time and effort for developers. + +This new functionality provides developers with a simple and efficient way to migrate data between PostgreSQL and MySQL, which is particularly useful for projects that need to switch databases or support multiple database systems. + +### 3. Parsing Table Structures from SQL Statements + +Another significant enhancement in **MagicObject 2.1** is the ability to **parse table structures directly from SQL statements**. Developers no longer need to first dump the schema into a database before they can interact with it. Instead, MagicObject allows you to read and manipulate the structure of a database directly from SQL. + +#### Benefits of Table Structure Parsing: + +- **Streamlined Workflow**: This feature eliminates the need for a two-step process (first dumping, then reading the schema) and allows developers to work more directly with SQL code. +- **Integrate with Third-Party Systems**: Developers can now easily parse and manipulate schemas from third-party systems that provide SQL code, without needing to import the data into a database first. +- **Improved Efficiency**: This utility speeds up the process of understanding and working with complex database schemas, making it easier to integrate and maintain SQL-driven projects. + +By providing a direct way to parse table structures, **MagicObject 2.1** significantly simplifies database schema management and makes it more accessible, especially for developers working with raw SQL or third-party integrations. + +## Summary + +**MagicObject 2.1** brings a suite of powerful features designed to enhance database management, simplify entity relationships, and improve the overall development process. Key updates include: + +- **Package annotations** for safer and more efficient entity joins. +- A **seamless database conversion tool** between PostgreSQL and MySQL, simplifying migrations. +- **Direct parsing of SQL table structures**, eliminating the need for intermediate steps. + +These updates significantly improve the flexibility and efficiency of **MagicObject**, making it even easier for developers to manage databases and integrate with various systems. With **MagicObject 2.1**, developers can focus more on building applications and less on wrestling with database compatibility and entity management. # MagicObject Version 2.7 -## What is New +**MagicObject 2.7** brings a set of powerful updates to improve database interaction, query flexibility, and transaction management. The main highlights of this release include support for PDO connections, enhanced native query capabilities with pagination and sorting, and new transactional methods for improved data management. -### PDO Support +## Key Updates in MagicObject 2.7 -With the release of **MagicObject 2.7**, a significant update has been introduced to allow users to leverage **PDO** (PHP Data Objects) for database connections. In previous versions, **MagicObject** required the use of **PicoDatabase**, its custom database handling class. However, recognizing that many developers are accustomed to establishing database connections via traditional PDO, this new version introduces flexibility by allowing PDO connections to be passed directly to the **MagicObject** constructor. +### 1. PDO Support -This update aims to bridge the gap between traditional PDO-based database management and the advanced features provided by **MagicObject**, thus enhancing compatibility while retaining all the powerful functionality of the framework. +One of the most significant changes in **MagicObject 2.7** is the introduction of support for **PDO** (PHP Data Objects). In previous versions, **MagicObject** required the use of its custom database handler, **PicoDatabase**. However, to accommodate developers who prefer working with PDO connections, this new version allows users to pass a PDO connection directly to the **MagicObject** constructor. #### Why PDO Support? -The decision to support **PDO** was made to accommodate users who have already established database connections in their applications using PDO, instead of relying on **PicoDatabase** from the start. By supporting PDO, **MagicObject** allows users to continue working with their preferred method of connecting to the database while still benefiting from the full range of features and utilities **MagicObject** offers. +The decision to include PDO support was driven by the need to make **MagicObject** more versatile for developers who are already using PDO in their applications. By allowing PDO connections, **MagicObject** now supports a broader range of use cases and provides users with the flexibility to integrate with existing PDO-based database connections. -While PDO is now an option for initializing **MagicObject**, it is used only in the constructor. Once the object is initialized, **MagicObject** continues to use **PicoDatabase** for all subsequent database interactions, ensuring that users can still benefit from **PicoDatabase**'s advanced features like automatic query building, database abstraction, and optimized query execution. +While PDO is supported for the initial connection setup, **MagicObject** continues to use **PicoDatabase** for all subsequent database operations. This ensures that users still benefit from **PicoDatabase**'s advanced features, such as automatic query building, database abstraction, and optimized query execution. #### How PDO Support Works -In **MagicObject 2.7**, when you pass a **PDO** connection object to the constructor, it is automatically converted into a **PicoDatabase** instance using the `PicoDatabase::fromPdo()` static method. This ensures that even though PDO is used to establish the initial connection, the object will still operate using **PicoDatabase** for all subsequent database operations. The constructor of **MagicObject** ensures that the database connection is properly initialized and the type of database is correctly detected based on the PDO driver. +In **MagicObject 2.7**, when you pass a **PDO** connection object to the constructor, it is internally converted into a **PicoDatabase** instance via the `PicoDatabase::fromPdo()` static method. This ensures that although PDO is used for establishing the initial connection, **PicoDatabase** manages the actual database interactions. Additionally, **MagicObject** automatically detects the database type based on the PDO driver to ensure smooth operation. + +### 2. Pageable and Sortable in Native Queries + +Another important enhancement in **MagicObject 2.7** is the introduction of **pageable** and **sortable** support in native queries. Prior to this release, native queries lacked direct support for pagination and sorting. Developers had to manually include `ORDER BY` and `LIMIT OFFSET` clauses in their queries, leading to more cumbersome code that was difficult to maintain and adapt across different database platforms. -### Pageable and Sortable in Native Query +With **MagicObject 2.7**, you can now pass **pagination** parameters using the `PicoPageable` type and **sorting** parameters using the `PicoSortable` type directly into your native queries. These parameters can be placed at any point in the query, though it's recommended to position them either at the beginning or end for optimal readability and organization. -In **MagicObject version 2.7**, support for **pageable** and **sortable** functionality has been added to native queries. Previously, native queries did not support pagination and sorting directly. Instead, users had to manually include `SORT BY` and `LIMIT OFFSET` clauses in their queries, which made them less flexible. This approach was problematic because each Database Management System (DBMS) has its own syntax for writing queries, making it cumbersome to adapt queries for different platforms. +This improvement enhances the flexibility of native queries, as the logic for pagination and sorting is handled automatically, reducing the need for manual intervention. By supporting these features, **MagicObject 2.7** allows you to write cleaner, more efficient, and database-agnostic queries. You can now easily handle pagination and sorting logic regardless of the underlying database system. -With the introduction of pageable and sortable support in version 2.7, users can now easily pass **pagination** parameters using the `PicoPageable` type and **sorting** parameters using the `PicoSortable` type directly into their native queries. These parameters can be placed anywhere within the query, but it is recommended to position them either at the beginning or the end of the query for optimal readability and organization. +### 3. Transaction Management -This enhancement makes native queries more flexible and easier to maintain, as the logic for pagination and sorting is handled automatically, without requiring manual intervention for each DBMS. As a result, users can now write cleaner, more efficient, and database-agnostic native queries. +**MagicObject 2.7** introduces enhanced support for transactional database operations, including three new methods: `startTransaction()`, `commit()`, and `rollback()`. These methods provide an easy and efficient way to manage database transactions within **MagicObject**. -### Transactional +- **startTransaction()**: Begins a new database transaction. +- **commit()**: Commits the current transaction, saving all changes made during the transaction to the database. +- **rollback()**: Rolls back the current transaction, undoing any changes made since the transaction began. -MagicObject version 2.7 introduces new features for transactional database management, namely `startTransaction()`, `commit()`, and `rollback()`. These functions allow entities to directly initiate and manage transactions within their scope. The `startTransaction()` function begins a new transaction, while `commit()` ensures that all changes made during the transaction are permanently saved to the database. On the other hand, `rollback()` can be used to revert any changes made during the transaction in case of an error or interruption. These functions require an active database connection to operate, providing a streamlined way for entities to manage data consistency and integrity within their transactions. +These methods are designed to work seamlessly with an active database connection, allowing developers to handle transactions directly within the context of their application. Whether you're managing financial transactions or ensuring data consistency during batch processing, these functions streamline the management of transaction-based operations. From 6d91d02db16c7fa4d4e8fd83c4bbc51d2386f753 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Tue, 12 Nov 2024 12:54:13 +0700 Subject: [PATCH 48/55] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f7a1d3..0f922b1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,9 +97,9 @@ These updates significantly improve the flexibility and efficiency of **MagicObj # MagicObject Version 2.7 -**MagicObject 2.7** brings a set of powerful updates to improve database interaction, query flexibility, and transaction management. The main highlights of this release include support for PDO connections, enhanced native query capabilities with pagination and sorting, and new transactional methods for improved data management. +## What's New -## Key Updates in MagicObject 2.7 +**MagicObject 2.7** brings a set of powerful updates to improve database interaction, query flexibility, and transaction management. The main highlights of this release include support for PDO connections, enhanced native query capabilities with pagination and sorting, and new transactional methods for improved data management. ### 1. PDO Support From ccc27f65b74860caec9c51ab88ccee135721873e Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Tue, 12 Nov 2024 22:32:27 +0700 Subject: [PATCH 49/55] Refactor MagicObject --- src/MagicObject.php | 49 +++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index b63e6312..c204e56b 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -240,7 +240,7 @@ public function loadIniString($rawData, $systemEnv = false) { // Parse without sections $data = PicoIniUtil::parseIniString($rawData); - if(isset($data) && !empty($data)) + if($this->_notNullAndNotEmpty($data)) { $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); if($systemEnv) @@ -264,7 +264,7 @@ public function loadIniFile($path, $systemEnv = false) { // Parse without sections $data = PicoIniUtil::parseIniFile($path); - if(isset($data) && !empty($data)) + if($this->_notNullAndNotEmpty($data)) { $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); if($systemEnv) @@ -289,7 +289,7 @@ public function loadIniFile($path, $systemEnv = false) public function loadYamlString($rawData, $systemEnv = false, $asObject = false, $recursive = false) { $data = Yaml::parse($rawData); - if(isset($data) && !empty($data)) + if($this->_notNullAndNotEmpty($data)) { $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); if($systemEnv) @@ -337,7 +337,7 @@ public function loadYamlString($rawData, $systemEnv = false, $asObject = false, public function loadYamlFile($path, $systemEnv = false, $asObject = false, $recursive = false) { $data = Yaml::parseFile($path); - if(isset($data) && !empty($data)) + if($this->_notNullAndNotEmpty($data)) { $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); if($systemEnv) @@ -385,7 +385,7 @@ public function loadYamlFile($path, $systemEnv = false, $asObject = false, $recu public function loadJsonString($rawData, $systemEnv = false, $asObject = false, $recursive = false) { $data = json_decode($rawData); - if(isset($data) && !empty($data)) + if($this->_notNullAndNotEmpty($data)) { $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); if($systemEnv) @@ -764,13 +764,14 @@ protected function executeNativeQuery() { // Retrieve caller trace information $trace = debug_backtrace(); + $traceCaller = $trace[1]; // Extract the caller's parameters - $callerParamValues = isset($trace[1]['args']) ? $trace[1]['args'] : []; + $callerParamValues = isset($traceCaller['args']) ? $traceCaller['args'] : []; // Get the caller's function and class names - $callerFunctionName = $trace[1]['function']; - $callerClassName = $trace[1]['class']; + $callerFunctionName = $traceCaller['function']; + $callerClassName = $traceCaller['class']; // Use reflection to retrieve docblock annotations from the caller function $reflection = new ReflectionMethod($callerClassName, $callerFunctionName); @@ -1520,6 +1521,20 @@ protected function _pretty() ; } + /** + * Checks if the provided parameter is an array. + * + * This function verifies if the given parameter is set and is of type array. It is a helper method + * used to validate the type of data before performing any operations on it that require an array. + * + * @param mixed $params The parameter to check. + * @return bool Returns `true` if the parameter is set and is an array, otherwise returns `false`. + */ + private function _isArray($params) + { + return isset($params) && is_array($params); + } + /** * Check if a value is not null and not empty * @@ -2398,7 +2413,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) && is_array($params) && !empty($params) && !$this->_readonly) { + else if (strncasecmp($method, "set", 3) === 0 && $this->_isArray($params) && !empty($params) && !$this->_readonly) { $var = lcfirst(substr($method, 3)); $this->{$var} = $params[0]; $this->modifyNullProperties($var, $params[0]); @@ -2409,21 +2424,21 @@ public function __call($method, $params) // NOSONAR $this->removeValue($var); return $this; } - else if (strncasecmp($method, "push", 4) === 0 && isset($params) && is_array($params) && !$this->_readonly) { + else if (strncasecmp($method, "push", 4) === 0 && $this->_isArray($params) && !$this->_readonly) { $var = lcfirst(substr($method, 4)); - return $this->push($var, isset($params) && is_array($params) && isset($params[0]) ? $params[0] : null); + return $this->push($var, $this->_isArray($params) && isset($params[0]) ? $params[0] : null); } - else if (strncasecmp($method, "append", 6) === 0 && isset($params) && is_array($params) && !$this->_readonly) { + else if (strncasecmp($method, "append", 6) === 0 && $this->_isArray($params) && !$this->_readonly) { $var = lcfirst(substr($method, 6)); - return $this->append($var, isset($params) && is_array($params) && isset($params[0]) ? $params[0] : null); + return $this->append($var, $this->_isArray($params) && isset($params[0]) ? $params[0] : null); } - else if (strncasecmp($method, "unshift", 7) === 0 && isset($params) && is_array($params) && !$this->_readonly) { + else if (strncasecmp($method, "unshift", 7) === 0 && $this->_isArray($params) && !$this->_readonly) { $var = lcfirst(substr($method, 7)); - return $this->unshift($var, isset($params) && is_array($params) && isset($params[0]) ? $params[0] : null); + return $this->unshift($var, $this->_isArray($params) && isset($params[0]) ? $params[0] : null); } - else if (strncasecmp($method, "prepend", 7) === 0 && isset($params) && is_array($params) && !$this->_readonly) { + else if (strncasecmp($method, "prepend", 7) === 0 && $this->_isArray($params) && !$this->_readonly) { $var = lcfirst(substr($method, 7)); - return $this->prepend($var, isset($params) && is_array($params) && isset($params[0]) ? $params[0] : null); + return $this->prepend($var, $this->_isArray($params) && isset($params[0]) ? $params[0] : null); } else if (strncasecmp($method, "pop", 3) === 0) { $var = lcfirst(substr($method, 3)); From abeffba08b415b8e7c861a2d2f449540e6a32e2c Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Tue, 12 Nov 2024 22:49:41 +0700 Subject: [PATCH 50/55] Update MagicObject.php --- src/MagicObject.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/MagicObject.php b/src/MagicObject.php index c204e56b..d8122a3b 100644 --- a/src/MagicObject.php +++ b/src/MagicObject.php @@ -240,7 +240,7 @@ public function loadIniString($rawData, $systemEnv = false) { // Parse without sections $data = PicoIniUtil::parseIniString($rawData); - if($this->_notNullAndNotEmpty($data)) + if($this->_isNotNullAndNotEmpty($data)) { $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); if($systemEnv) @@ -264,7 +264,7 @@ public function loadIniFile($path, $systemEnv = false) { // Parse without sections $data = PicoIniUtil::parseIniFile($path); - if($this->_notNullAndNotEmpty($data)) + if($this->_isNotNullAndNotEmpty($data)) { $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); if($systemEnv) @@ -289,7 +289,7 @@ public function loadIniFile($path, $systemEnv = false) public function loadYamlString($rawData, $systemEnv = false, $asObject = false, $recursive = false) { $data = Yaml::parse($rawData); - if($this->_notNullAndNotEmpty($data)) + if($this->_isNotNullAndNotEmpty($data)) { $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); if($systemEnv) @@ -337,7 +337,7 @@ public function loadYamlString($rawData, $systemEnv = false, $asObject = false, public function loadYamlFile($path, $systemEnv = false, $asObject = false, $recursive = false) { $data = Yaml::parseFile($path); - if($this->_notNullAndNotEmpty($data)) + if($this->_isNotNullAndNotEmpty($data)) { $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); if($systemEnv) @@ -385,7 +385,7 @@ public function loadYamlFile($path, $systemEnv = false, $asObject = false, $recu public function loadJsonString($rawData, $systemEnv = false, $asObject = false, $recursive = false) { $data = json_decode($rawData); - if($this->_notNullAndNotEmpty($data)) + if($this->_isNotNullAndNotEmpty($data)) { $data = PicoEnvironmentVariable::replaceValueAll($data, $data, true); if($systemEnv) @@ -1541,7 +1541,7 @@ private function _isArray($params) * @param mixed $value The value to check * @return bool True if the value is not null and not empty; otherwise, false */ - private function _notNullAndNotEmpty($value) + private function _isNotNullAndNotEmpty($value) { return $value != null && !empty($value); } @@ -1917,7 +1917,7 @@ public function find($params) { $persist = new PicoDatabasePersistence($this->_database, $this); $result = $persist->find($params); - if($this->_notNullAndNotEmpty($result)) + if($this->_isNotNullAndNotEmpty($result)) { $this->loadData($result); return $this; @@ -2063,7 +2063,7 @@ public function findOneWithPrimaryKeyValue($primaryKeyVal, $subqueryMap = null) { $persist = new PicoDatabasePersistence($this->_database, $this); $result = $persist->findOneWithPrimaryKeyValue($primaryKeyVal, $subqueryMap); - if($this->_notNullAndNotEmpty($result)) + if($this->_isNotNullAndNotEmpty($result)) { $this->loadData($result); return $this; @@ -2095,7 +2095,7 @@ private function findOneBy($method, $params, $sortable = null) { $persist = new PicoDatabasePersistence($this->_database, $this); $result = $persist->findOneBy($method, $params, $sortable); - if($this->_notNullAndNotEmpty($result)) + if($this->_isNotNullAndNotEmpty($result)) { $this->loadData($result); return $this; @@ -2218,11 +2218,11 @@ private function booleanToTextBy($propertyName, $params) * @param bool $passive Flag indicating whether the objects are passive. * @return array An array of objects. */ - private function toArrayObject($result, $passive = false) // NOSONAR + private function toArrayObject($result, $passive = false) { $instance = array(); $index = 0; - if(isset($result) && is_array($result)) + if($this->_isArray($result)) { foreach($result as $value) { From bcfab69efccd413087792cf242bc3d3e993b8e07 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Wed, 13 Nov 2024 06:02:27 +0700 Subject: [PATCH 51/55] Update SecretObject.php --- src/SecretObject.php | 56 ++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/src/SecretObject.php b/src/SecretObject.php index e32281b3..9bb7d9e8 100644 --- a/src/SecretObject.php +++ b/src/SecretObject.php @@ -129,17 +129,19 @@ public function __construct($data = null, $secureCallback = null) } /** - * Processes object information to determine encryption and decryption requirements. + * Analyzes the class's parameters and properties to determine which should be + * encrypted or decrypted based on annotations. * - * This method retrieves the class parameters and properties using reflection, - * parsing annotations to identify which properties should be encrypted or - * decrypted. It populates the respective lists of properties based on the - * annotations found. + * This method uses reflection to retrieve the class's parameters and properties. + * It then parses annotations associated with these members to identify which + * properties should undergo encryption or decryption during specific stages + * (before storage or before retrieval). The appropriate lists of properties + * are populated accordingly. * * @return void * * @throws InvalidAnnotationException If an invalid annotation is encountered - * while processing class parameters. + * while processing class parameters or properties. */ private function _objectInfo() { @@ -148,42 +150,34 @@ private function _objectInfo() $params = $reflexClass->getParameters(); $props = $reflexClass->getProperties(); - foreach($params as $paramName=>$paramValue) - { - try - { + // Process each class parameter + foreach ($params as $paramName => $paramValue) { + try { $vals = $reflexClass->parseKeyValue($paramValue); $this->_classParams[$paramName] = $vals; - } - catch(InvalidQueryInputException $e) - { - throw new InvalidAnnotationException("Invalid annotation @".$paramName); + } catch (InvalidQueryInputException $e) { + throw new InvalidAnnotationException("Invalid annotation @" . $paramName); } } - // iterate each properties of the class - foreach($props as $prop) - { + // Process each class property + foreach ($props as $prop) { $reflexProp = new PicoAnnotationParser($className, $prop->name, 'property'); $parameters = $reflexProp->getParameters(); - // add property list to be encryped or decrypted - foreach($parameters as $param=>$val) - { - if(strcasecmp($param, self::ANNOTATION_ENCRYPT_IN) == 0) - { + // Check each property for encryption/decryption annotations + foreach ($parameters as $param => $val) { + if (strcasecmp($param, self::ANNOTATION_ENCRYPT_IN) == 0) { + // Property should be encrypted before storing $this->_encryptInProperties[] = $prop->name; - } - else if(strcasecmp($param, self::ANNOTATION_DECRYPT_OUT) == 0) - { + } else if (strcasecmp($param, self::ANNOTATION_DECRYPT_OUT) == 0) { + // Property should be decrypted before retrieval $this->_decryptOutProperties[] = $prop->name; - } - else if(strcasecmp($param, self::ANNOTATION_ENCRYPT_OUT) == 0) - { + } else if (strcasecmp($param, self::ANNOTATION_ENCRYPT_OUT) == 0) { + // Property should be encrypted before retrieval $this->_encryptOutProperties[] = $prop->name; - } - else if(strcasecmp($param, self::ANNOTATION_DECRYPT_IN) == 0) - { + } else if (strcasecmp($param, self::ANNOTATION_DECRYPT_IN) == 0) { + // Property should be decrypted before storing $this->_decryptInProperties[] = $prop->name; } } From de66d8c5fd54e031cac401bd2750ee7e12c26047 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Wed, 13 Nov 2024 06:05:00 +0700 Subject: [PATCH 52/55] Update SecretObject.php --- src/SecretObject.php | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/SecretObject.php b/src/SecretObject.php index 9bb7d9e8..f02d9e6c 100644 --- a/src/SecretObject.php +++ b/src/SecretObject.php @@ -18,12 +18,24 @@ use Symfony\Component\Yaml\Yaml; /** - * Secret object - * - * This class provides mechanisms for managing properties with encryption - * and decryption capabilities, using annotations to specify which properties - * should be secured. - * + * SecretObject class + * + * This class provides mechanisms to manage properties that require encryption + * and decryption during their lifecycle. It uses annotations to specify which + * properties should be encrypted or decrypted when they are set or retrieved. + * These annotations help identify when to apply encryption or decryption, + * either before saving (SET) or before fetching (GET). + * + * The class supports flexibility in data initialization, allowing data to be + * passed as an array, an object, or even left empty. Additionally, a secure + * callback function can be provided to handle key generation for encryption + * and decryption operations. + * + * Key features: + * - Encryption and decryption of object properties based on annotations. + * - Support for customizing property naming strategies. + * - Option to provide a secure function for key generation. + * * @author Kamshory * @package MagicObject * @link https://github.com/Planetbiru/MagicObject From 6c77d51a337ae959d879398af96bbbab9a4cfc5a Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Wed, 13 Nov 2024 07:18:53 +0700 Subject: [PATCH 53/55] Update PicoDatabase.php --- src/Database/PicoDatabase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php index d398a51e..b4ad0c5e 100644 --- a/src/Database/PicoDatabase.php +++ b/src/Database/PicoDatabase.php @@ -346,7 +346,7 @@ private function connectSqlite() $path = $this->databaseCredentials->getDatabaseFilePath(); if(!isset($path) || empty($path)) { - throw new InvalidDatabaseConfiguration("Database path may not be empty. Please check your database configuration!"); + throw new InvalidDatabaseConfiguration("Database path may not be empty. Please check your database configuration on {database_file_path}!"); } try { $this->databaseConnection = new PDO("sqlite:" . $path); From a9a0fe2485e648a701e4e662e188320a363086c9 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Wed, 13 Nov 2024 10:42:18 +0700 Subject: [PATCH 54/55] Fixing database driver --- src/Database/PicoDatabase.php | 30 ++++++++++++++++++-- src/Database/PicoDatabaseCredentials.php | 2 +- src/Util/Database/EntityUtil.php | 16 +++++------ src/Util/Database/PicoDatabaseUtil.php | 2 +- src/Util/Database/PicoDatabaseUtilBase.php | 16 +++++------ src/Util/Database/PicoDatabaseUtilMySql.php | 12 ++++---- src/Util/Database/PicoDatabaseUtilSqlite.php | 17 +++++------ src/Util/Database/PicoSqlParser.php | 10 +++---- 8 files changed, 66 insertions(+), 39 deletions(-) diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php index b4ad0c5e..250c0e9b 100644 --- a/src/Database/PicoDatabase.php +++ b/src/Database/PicoDatabase.php @@ -440,6 +440,32 @@ private function getDbType($databaseType) // NOSONAR } } + /** + * Determines the database driver based on the provided database type. + * + * This function takes a string representing the database type and returns + * the corresponding database driver constant from the `PicoDatabaseType` class. + * It supports SQLite, PostgreSQL, and MySQL/MariaDB types. + * + * @param string $databaseType The type of the database (e.g., 'sqlite', 'postgres', 'pgsql', 'mysql', 'mariadb'). + * + * @return string The corresponding database driver constant, one of: + * - `sqlite` + * - `pgsql` + * - `mysql` + */ + private function getDbDriver($databaseType) + { + if (stripos($databaseType, 'sqlite') !== false) { + return PicoDatabaseType::DATABASE_TYPE_SQLITE; + } else if (stripos($databaseType, 'postgre') !== false || stripos($databaseType, 'pgsql') !== false) { + return PicoDatabaseType::DATABASE_TYPE_PGSQL; + } else { + return PicoDatabaseType::DATABASE_TYPE_MYSQL; + } + } + + /** * Create a connection string. * @@ -464,12 +490,12 @@ private function constructConnectionString($withDatabase = true) $emptyValue .= $emptyName ? "{database_name}" : ""; throw new InvalidDatabaseConfiguration("Invalid database configuration. $emptyValue. Please check your database configuration!"); } - return $this->databaseCredentials->getDriver() . ':host=' . $this->databaseCredentials->getHost() . '; port=' . ((int) $this->databaseCredentials->getPort()) . '; dbname=' . $this->databaseCredentials->getDatabaseName(); + return $this->getDbDriver($this->databaseCredentials->getDriver()) . ':host=' . $this->databaseCredentials->getHost() . '; port=' . ((int) $this->databaseCredentials->getPort()) . '; dbname=' . $this->databaseCredentials->getDatabaseName(); } else { if ($invalidParam1) { throw new InvalidDatabaseConfiguration("Invalid database configuration. $emptyValue. Please check your database configuration!"); } - return $this->databaseCredentials->getDriver() . ':host=' . $this->databaseCredentials->getHost() . '; port=' . ((int) $this->databaseCredentials->getPort()); + return $this->getDbDriver($this->databaseCredentials->getDriver()) . ':host=' . $this->databaseCredentials->getHost() . '; port=' . ((int) $this->databaseCredentials->getPort()); } } diff --git a/src/Database/PicoDatabaseCredentials.php b/src/Database/PicoDatabaseCredentials.php index beb82957..03fd6219 100644 --- a/src/Database/PicoDatabaseCredentials.php +++ b/src/Database/PicoDatabaseCredentials.php @@ -31,7 +31,7 @@ class PicoDatabaseCredentials extends SecretObject { /** - * Database driver (e.g., 'mysql', 'postgresql', 'mariadb). + * Database driver (e.g., 'mysql', 'pgsql', 'mariadb', 'sqlite'). * * @var string */ diff --git a/src/Util/Database/EntityUtil.php b/src/Util/Database/EntityUtil.php index d92833c0..44b974c7 100644 --- a/src/Util/Database/EntityUtil.php +++ b/src/Util/Database/EntityUtil.php @@ -29,10 +29,10 @@ public static function getPropertyColumn($entity) $tableInfo = $entity->tableInfo(); if($tableInfo == null) { - return array(); + return []; } $columns = $tableInfo->getColumns(); - $propertyColumns = array(); + $propertyColumns = []; foreach($columns as $prop=>$column) { $propertyColumns[$prop] = $column['name']; @@ -51,10 +51,10 @@ public static function getPropertyJoinColumn($entity) $tableInfo = $entity->tableInfo(); if($tableInfo == null) { - return array(); + return []; } $joinColumns = $tableInfo->getJoinColumns(); - $propertyColumns = array(); + $propertyColumns = []; foreach($joinColumns as $prop=>$column) { $propertyColumns[$prop] = $column['name']; @@ -71,7 +71,7 @@ public static function getPropertyJoinColumn($entity) */ public static function getEntityData($data, $map) { - $newData = array(); + $newData = []; if(isset($data)) { if(is_array($data)) @@ -99,7 +99,7 @@ public static function getEntityData($data, $map) */ private static function fromArray($data, $map) { - $newData = array(); + $newData = []; foreach($map as $key=>$value) { if(isset($data[$value])) @@ -119,7 +119,7 @@ private static function fromArray($data, $map) */ private static function fromStdClass($data, $map) { - $newData = array(); + $newData = []; foreach($map as $key=>$value) { if(isset($data->{$value})) @@ -139,7 +139,7 @@ private static function fromStdClass($data, $map) */ private static function fromMagicObject($data, $map) { - $newData = array(); + $newData = []; foreach($map as $key=>$value) { $newData[$key] = $data->get($value); diff --git a/src/Util/Database/PicoDatabaseUtil.php b/src/Util/Database/PicoDatabaseUtil.php index 32a9909e..7eac8495 100644 --- a/src/Util/Database/PicoDatabaseUtil.php +++ b/src/Util/Database/PicoDatabaseUtil.php @@ -109,7 +109,7 @@ public static function sortableFromParams($params) */ public static function valuesFromParams($params) { - $ret = array(); + $ret = []; if(self::isArray($params)) { foreach($params as $param) diff --git a/src/Util/Database/PicoDatabaseUtilBase.php b/src/Util/Database/PicoDatabaseUtilBase.php index 18eea3c1..51eaff98 100644 --- a/src/Util/Database/PicoDatabaseUtilBase.php +++ b/src/Util/Database/PicoDatabaseUtilBase.php @@ -22,7 +22,7 @@ class PicoDatabaseUtilBase public function getAutoIncrementKey($tableInfo) { $autoIncrement = $tableInfo->getAutoIncrementKeys(); - $autoIncrementKeys = array(); + $autoIncrementKeys = []; if(is_array($autoIncrement) && !empty($autoIncrement)) { foreach($autoIncrement as $col) @@ -61,7 +61,7 @@ public function dumpData($columns, $picoTableName, $data, $maxRecord = 100, $cal // Handle case where fetching data is not required if($data->getFindOption() & MagicObject::FIND_OPTION_NO_FETCH_DATA && $maxRecord > 0 && isset($callbackFunction) && is_callable($callbackFunction)) { - $records = array(); + $records = []; $stmt = $data->getPDOStatement(); // Fetch records in batches while($data = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT)) @@ -81,7 +81,7 @@ public function dumpData($columns, $picoTableName, $data, $maxRecord = 100, $cal call_user_func($callbackFunction, $sql); } // Reset the records buffer - $records = array(); + $records = []; } } // Handle any remaining records @@ -211,7 +211,7 @@ public function createMapTemplate($databaseSource, $databaseTarget, $target) { $targetColumns = array_keys($this->showColumns($databaseTarget, $target)); $sourceColumns = array_keys($this->showColumns($databaseSource, $target)); - $map = array(); + $map = []; foreach($targetColumns as $column) { if(!in_array($column, $sourceColumns)) @@ -306,7 +306,7 @@ public function importDataTable($databaseSource, $databaseTarget, $tableNameSour ->select("*") ->from($sourceTable); $stmt = $databaseSource->query($queryBuilderSource); - $records = array(); + $records = []; while($data = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT)) { $data = $this->processDataMapping($data, $columns, $tableInfo->getMap()); @@ -322,7 +322,7 @@ public function importDataTable($databaseSource, $databaseTarget, $tableNameSour call_user_func($callbackFunction, $sql, $tableNameSource, $tableNameTarget); } // reset buffer - $records = array(); + $records = []; } } if(!empty($records) && isset($callbackFunction) && is_callable($callbackFunction)) @@ -542,7 +542,7 @@ public function fixFloatData($data, $name, $value) public function insert($tableName, $data) { // Collect all unique columns from the data records - $columns = array(); + $columns = []; foreach ($data as $record) { $columns = array_merge($columns, array_keys($record)); } @@ -557,7 +557,7 @@ public function insert($tableName, $data) implode(",\r\n", array_fill(0, count($data), $placeholders)); // Prepare values for binding - $values = array(); + $values = []; foreach ($data as $record) { foreach ($columns as $column) { // Use null if the value is not set diff --git a/src/Util/Database/PicoDatabaseUtilMySql.php b/src/Util/Database/PicoDatabaseUtilMySql.php index 82cc71dc..282a487c 100644 --- a/src/Util/Database/PicoDatabaseUtilMySql.php +++ b/src/Util/Database/PicoDatabaseUtilMySql.php @@ -76,8 +76,8 @@ public function getColumnList($database, $tableName) */ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false, $dropIfExists = false, $engine = 'InnoDB', $charset = 'utf8mb4') { - $query = array(); - $columns = array(); + $query = []; + $columns = []; if($dropIfExists) { $query[] = "-- DROP TABLE IF EXISTS `$tableName`;"; @@ -144,7 +144,7 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false */ public function createColumn($column) { - $col = array(); + $col = []; $col[] = "\t"; $col[] = "`".$column[parent::KEY_NAME]."`"; $col[] = $column['type']; @@ -208,7 +208,7 @@ public function fixDefaultValue($defaultValue, $type) public function dumpRecord($columns, $tableName, $record) { $value = $record->valueArray(); - $rec = array(); + $rec = []; foreach($value as $key=>$val) { if(isset($columns[$key])) @@ -242,7 +242,7 @@ public function showColumns($database, $tableName) $sql = "SHOW COLUMNS FROM $tableName"; $result = $database->fetchAll($sql, PDO::FETCH_ASSOC); - $columns = array(); + $columns = []; foreach($result as $row) { $columns[$row['Field']] = $row['Type']; @@ -273,7 +273,7 @@ public function autoConfigureImportData($config) $databaseTarget->connect(); $tables = $config->getTable(); - $existingTables = array(); + $existingTables = []; foreach($tables as $tb) { $existingTables[] = $tb->getTarget(); diff --git a/src/Util/Database/PicoDatabaseUtilSqlite.php b/src/Util/Database/PicoDatabaseUtilSqlite.php index 508e61f0..0a27a1c0 100644 --- a/src/Util/Database/PicoDatabaseUtilSqlite.php +++ b/src/Util/Database/PicoDatabaseUtilSqlite.php @@ -170,7 +170,7 @@ private function getPkeyArrayFinal($pKeyArr, $pKeyArrUsed) * @param array $pKeyArrUsed The array to store used primary key names. * @return array An array containing the determined SQL data type and the updated primary key array. */ - private function determineSqlType($column, $autoIncrementKeys = null, $length = 255, $pKeyArrUsed = array()) + private function determineSqlType($column, $autoIncrementKeys = null, $length = 255, $pKeyArrUsed = []) { $columnName = $column[parent::KEY_NAME]; $columnType = strtolower($column['type']); // Assuming 'type' holds the column type @@ -200,7 +200,8 @@ private function determineSqlType($column, $autoIncrementKeys = null, $length = $sqlType = strtoupper($columnType); if ($sqlType !== 'TINYINT(1)' && $sqlType !== 'FLOAT' && $sqlType !== 'TEXT' && $sqlType !== 'LONGTEXT' && $sqlType !== 'DATE' && $sqlType !== 'TIMESTAMP' && - $sqlType !== 'BLOB') { + $sqlType !== 'BLOB') + { $sqlType = 'VARCHAR(255)'; // Fallback type for unknown types } } @@ -234,7 +235,7 @@ public function getColumnList($database, $tableName) $stmt = $database->query("PRAGMA table_info($tableName)"); // Fetch and display the column details - $rows = array(); + $rows = []; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $rows[] = array( "Field" => $row['name'], @@ -265,8 +266,8 @@ public function getColumnList($database, $tableName) */ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false, $dropIfExists = false, $engine = 'InnoDB', $charset = 'utf8mb4') { - $query = array(); - $columns = array(); + $query = []; + $columns = []; if($dropIfExists) { $query[] = "-- DROP TABLE IF EXISTS $tableName;"; @@ -289,7 +290,7 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false $columns[] = $this->createColumn($column); } $query[] = implode(",\r\n", $columns); - $query[] = ") ENGINE=$engine DEFAULT CHARSET=$charset;"; + $query[] = ") "; $pk = $tableInfo->getPrimaryKeys(); if(isset($pk) && is_array($pk) && !empty($pk)) @@ -333,7 +334,7 @@ public function dumpStructure($tableInfo, $tableName, $createIfNotExists = false */ public function createColumn($column) { - $col = array(); + $col = []; $col[] = "\t"; $col[] = "".$column[parent::KEY_NAME].""; $col[] = $column['type']; @@ -444,7 +445,7 @@ public function autoConfigureImportData($config) $databaseTarget->connect(); $tables = $config->getTable(); - $existingTables = array(); + $existingTables = []; foreach($tables as $tb) { $existingTables[] = $tb->getTarget(); diff --git a/src/Util/Database/PicoSqlParser.php b/src/Util/Database/PicoSqlParser.php index 6593120f..c1675ac5 100644 --- a/src/Util/Database/PicoSqlParser.php +++ b/src/Util/Database/PicoSqlParser.php @@ -96,7 +96,7 @@ public function parseTable($sql) // NOSONAR preg_match($rg_tb, $sql, $result); $tableName = $result['tb']; - $fld_list = []; + $fldList = []; $primaryKey = null; $columnList = []; @@ -132,7 +132,7 @@ public function parseTable($sql) // NOSONAR { $def = null; } - $fld_list[] = [ + $fldList[] = [ self::KEY_COLUMN_NAME => $columnName, self::KEY_TYPE => trim($rg_fld2_result['ftype']), self::KEY_LENGTH => $length, @@ -148,7 +148,7 @@ public function parseTable($sql) // NOSONAR } if ($primaryKey !== null) { - foreach ($fld_list as &$column) //NOSONAR + foreach ($fldList as &$column) //NOSONAR { if ($column[self::KEY_COLUMN_NAME] === $primaryKey) { $column[self::KEY_PRIMARY_KEY] = true; @@ -160,7 +160,7 @@ public function parseTable($sql) // NOSONAR $x = preg_replace('/(PRIMARY|UNIQUE) KEY\s+[a-zA-Z_0-9\s]+/', '', $f); $x = str_replace(['(', ')'], '', $x); $pkeys = array_map('trim', explode(',', $x)); - foreach ($fld_list as &$column) { + foreach ($fldList as &$column) { if ($this->inArray($pkeys, $column[self::KEY_COLUMN_NAME])) { $column[self::KEY_PRIMARY_KEY] = true; } @@ -169,7 +169,7 @@ public function parseTable($sql) // NOSONAR } return [ 'tableName' => $tableName, - 'columns' => $fld_list, + 'columns' => $fldList, 'primaryKey' => $primaryKey ]; } From 33b60123dec677a919b6cf18439d3f3c014eddc9 Mon Sep 17 00:00:00 2001 From: "Kamshory, MT" Date: Wed, 13 Nov 2024 11:00:34 +0700 Subject: [PATCH 55/55] Update PicoDatabaseUtilSqlite.php --- src/Util/Database/PicoDatabaseUtilSqlite.php | 27 ++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Util/Database/PicoDatabaseUtilSqlite.php b/src/Util/Database/PicoDatabaseUtilSqlite.php index 0a27a1c0..616f7c2b 100644 --- a/src/Util/Database/PicoDatabaseUtilSqlite.php +++ b/src/Util/Database/PicoDatabaseUtilSqlite.php @@ -370,11 +370,20 @@ public function createColumn($column) */ public function fixDefaultValue($defaultValue, $type) { - if(strtolower($defaultValue) == 'true' || strtolower($defaultValue) == 'false' || strtolower($defaultValue) == 'null') + 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) + 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."'"; } @@ -402,17 +411,25 @@ public function fixImportData($data, $columns) { $type = $columns[$name]; - if(strtolower($type) == 'tinyint(1)' || strtolower($type) == 'boolean' || strtolower($type) == 'bool') + 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) + 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) + else if(stripos($type, 'float') !== false + || stripos($type, 'double') !== false + || stripos($type, 'decimal') !== false + ) { // Process float types $data = $this->fixFloatData($data, $name, $value);