diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
new file mode 100644
index 0000000..c946212
--- /dev/null
+++ b/.github/workflows/php.yml
@@ -0,0 +1,41 @@
+name: PHP Tests
+
+on:
+ push:
+ branches: [ main, dev ]
+ pull_request:
+ branches: [ main, dev ]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php: ['8.0', '8.1', '8.2', '8.3', '8.4']
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup PHP ${{ matrix.php }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: mbstring, openssl
+ coverage: none
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - name: Cache Composer dependencies
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress
+
+ - name: Run PHPUnit
+ run: vendor/bin/phpunit --no-coverage
diff --git a/.gitignore b/.gitignore
index ae55217..27280e2 100755
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,6 @@ bin/phpDocumentor.phar
# Composer
vendor/
-composer.lock
# PHPUnit
.phpunit.result.cache
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..e802468
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,43 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+
+## [Unreleased]
+
+### Added
+
+- **Strict types**: `declare(strict_types=1);` in all PHP library and test files.
+- **Vars::arrayContainsSubstring()**: Alias for `arrayInString()` with clearer naming.
+- **Tests**: New test classes for Crypto, Files, SQLite, Times, and Random.
+- **CI**: GitHub Actions workflow (`.github/workflows/php.yml`) running PHPUnit on PHP 8.0–8.4 for `main` and `dev`.
+- **CHANGELOG.md**: This file.
+
+### Changed
+
+- **Error handling**: Replaced `die()` with exceptions in Network, Random, Files, and Debugger (bootstrap `_All.php` still uses `die()` for fatal startup failures).
+ - `Network::getUserIP()` / `getServerIP()`: throw `RuntimeException` when `$die_if_empty` is true and IP cannot be determined.
+ - `Random::genStr()`: throw `InvalidArgumentException` for invalid type instead of `die()`.
+ - `Files::preventDirect()` default callback: throw `RuntimeException("404 Not found")` after setting 404 response code.
+ - `Debugger::output(..., $die = true)`: echo then throw `RuntimeException` instead of `die()`.
+- **Network::ipInRange()**: Removed `echo` on invalid IPs; still returns `null` for invalid input. Fixed check to use `=== false` for `ip2long()` so `0.0.0.0` is valid.
+- **Vars::arrayInString()**: Documented behaviour (any element contains needle); added return type `bool`.
+- **Images**: Docblocks use `\Imagick` and `\ImagickException`; `blur()` has explicit return type `string`.
+- **Composer**: `composer.lock` is no longer ignored so dev and CI use reproducible dependency versions.
+
+### Documentation
+
+- **README**: Get started section documents both Composer and _All.php; module links point to `Docs/classes/*.html`.
+- **TEST_SUMMARY.md**: Updated to include new test classes and strict types / exception behaviour.
+- **REFACTORING_SUMMARY.md**: Can be updated to reference this changelog for recent improvements.
+
+---
+
+## Previous work (pre-changelog)
+
+- OOP refactor: namespaces, Base/DI, SQL connection injection, identifier validation, Debugger data/HTML separation, visibility and return types (see `REFACTORING_SUMMARY.md` and `OOP_REVIEW.md`).
+- Crypto: corrected `hash()` argument order, `verifyhash()` with `hash_equals()`, IV handling with embedded IV in ciphertext.
+- Images/SQLite: `parent::__construct()`; SQLite visibility and `$this->res()`.
+- SQL::search(): table and column names validated via `validateIdentifier()`.
+- README and index.php: fixed doc and test links; bootstrap prefers Composer autoload.
diff --git a/PHPUtils/API.php b/PHPUtils/API.php
index f760786..83e3461 100755
--- a/PHPUtils/API.php
+++ b/PHPUtils/API.php
@@ -1,5 +1,7 @@
genIV($method) : '');
- return openssl_encrypt($str,$method,$password,iv:$iv);
+ public function encryptwithpw(string $str, string $password, string $method = 'aes-256-cbc', bool $iv = false): string {
+ if ($iv) {
+ $ivRaw = hex2bin($this->genIV($method));
+ $encrypted = openssl_encrypt($str, $method, $password, iv: $ivRaw);
+ return base64_encode($ivRaw . $encrypted);
+ }
+ return openssl_encrypt($str, $method, $password);
}
-
/**
- * Decrypt a string using a password, and optionally an IV
+ * Decrypt a string using a password, and optionally an IV.
+ * When $iv is empty and the string was produced with iv=true, the embedded IV is used.
+ * When $iv is non-empty, it is treated as hex and used as the IV (ciphertext only in $str).
*
- * @param mixed $str The string to decrypt
- * @param mixed $password The password to use
- * @param mixed $method The encryption method to use. Defaults to aes-256-cbc
- * @param mixed $iv Whether to use an IV or not. Defaults to ''
- * @return string The decrypted string
+ * @param string $str The string to decrypt (base64 when IV was embedded, or raw/base64 ciphertext when $iv provided)
+ * @param string $password The password to use
+ * @param string $method The encryption method. Defaults to aes-256-cbc
+ * @param string $iv Optional hex-encoded IV when not embedded. Defaults to ''
+ * @return string|false The decrypted string or false on failure
*/
- function decryptwithpw(string $str, string $password, string $method = 'aes-256-cbc', string $iv = '') {
- return openssl_decrypt($str,$method,$password,iv:$iv);
+ public function decryptwithpw(string $str, string $password, string $method = 'aes-256-cbc', string $iv = ''): string|false {
+ $ivLen = openssl_cipher_iv_length($method);
+ if ($iv !== '') {
+ $ivRaw = hex2bin($iv);
+ $ciphertext = base64_decode($str, true) ?: $str;
+ return openssl_decrypt($ciphertext, $method, $password, iv: $ivRaw);
+ }
+ $decoded = base64_decode($str, true);
+ if ($decoded !== false && strlen($decoded) > $ivLen) {
+ $ivRaw = substr($decoded, 0, $ivLen);
+ $ciphertext = substr($decoded, $ivLen);
+ return openssl_decrypt($ciphertext, $method, $password, iv: $ivRaw);
+ }
+ return openssl_decrypt($str, $method, $password);
}
/**
- * hash
+ * Hash a string using the given algorithm.
*
- * @param mixed $str The string to hash
- * @param mixed $hash The hash method to use. Defaults to sha512
+ * @param string $str The string to hash
+ * @param string $hash The hash algorithm. Defaults to sha512
* @return string The hashed string
*/
- function hash(string $str, string $hash = 'sha512') {
- return hash($str, $hash);
+ public function hash(string $str, string $hash = 'sha512'): string {
+ return hash($hash, $str);
}
/**
- * verifyhash
- *
- * Verifies a hash
- *
- * @param mixed $str The string to verify
- * @param mixed $hash The hash to verify against
- * @param mixed $hashmethod The hash method to use. Defaults to sha512
- *
- * @return bool Whether the hash is valid or not
+ * Verify a string against a hash (timing-safe comparison).
+ *
+ * @param string $str The string to verify
+ * @param string $hash The hash to verify against
+ * @param string $hashmethod The hash method. Defaults to sha512
+ * @return bool Whether the hash is valid
*/
- function verifyhash(string $str, string $hash, string $hashmethod = 'sha512') {
- return hash($hashmethod, $str) == $hash;
+ public function verifyhash(string $str, string $hash, string $hashmethod = 'sha512'): bool {
+ return hash_equals($hash, hash($hashmethod, $str));
}
}
\ No newline at end of file
diff --git a/PHPUtils/Debugger.php b/PHPUtils/Debugger.php
index 9207375..c856202 100755
--- a/PHPUtils/Debugger.php
+++ b/PHPUtils/Debugger.php
@@ -1,5 +1,7 @@
format($txt, $type);
if ($die) {
- die($this->format($txt, $type));
+ echo $formatted;
+ throw new \RuntimeException("Debugger::output terminated with die=true.");
}
- echo $this->format($txt, $type);
+ echo $formatted;
}
diff --git a/PHPUtils/Files.php b/PHPUtils/Files.php
index 7476af0..f5a95b9 100755
--- a/PHPUtils/Files.php
+++ b/PHPUtils/Files.php
@@ -1,5 +1,7 @@
getMessage());
+ } catch (\ImagickException $e) {
+ throw new \RuntimeException("Error blurring image: " . $e->getMessage(), 0, $e);
}
}
@@ -83,7 +86,7 @@ function blur(string $imagePath, float $radius = 10, float $sigma = 25, int $cha
* @param string $color The color of the text.
* @throws ImagickException If an error occurs while adding the text to the image.
*/
- function textToImage(string $text, string $imagePath, string $fontPath, int $fontSize, int $x, int $y, string $color = 'black') {
+ public function textToImage(string $text, string $imagePath, string $fontPath, int $fontSize, int $x, int $y, string $color = 'black') {
$draw = new ImagickDraw();
$draw->setFont($fontPath);
$draw->setFontSize($fontSize);
@@ -106,7 +109,7 @@ function textToImage(string $text, string $imagePath, string $fontPath, int $fon
* @param int $width The width of the image.
* @return string The HTML snippet.
*/
- function renderImage(string $imagePath, int $height = 200, int $width = 200) {
- return '
';
+ public function renderImage(string $imagePath, int $height = 200, int $width = 200) {
+ return '
';
}
}
\ No newline at end of file
diff --git a/PHPUtils/Navigation.php b/PHPUtils/Navigation.php
index 3372684..c39a81f 100755
--- a/PHPUtils/Navigation.php
+++ b/PHPUtils/Navigation.php
@@ -1,5 +1,7 @@
-
+
';
}
@@ -27,7 +29,7 @@ function Bootstrap(string $version = '5.3.2') {
* @param string $version The version of jQuery to include. Default is '3.7.1'.
* @return void
*/
- function jQuery(string $version = '3.7.1') {
+ public function jQuery(string $version = '3.7.1') {
echo '
';
diff --git a/PHPUtils/SQL.php b/PHPUtils/SQL.php
index 93783c1..39c3717 100755
--- a/PHPUtils/SQL.php
+++ b/PHPUtils/SQL.php
@@ -1,5 +1,7 @@
validateIdentifier($tablename, 'table');
+ $validatedColumns = [];
+ foreach ($columns as $column) {
+ $validatedColumns[] = $this->validateIdentifier($column, 'column');
+ }
# Default options
$delimiter = (empty($options["delimiter"]) ? " " : $options["delimiter"]);
@@ -275,7 +282,7 @@ public function search(string $tablename, string $search, array $columns = ["nam
if ($strip_chars === True) {
$keyword = preg_replace("/[^a-zA-Z0-9]/", "", $keyword);
}
- foreach ($columns as $column) {
+ foreach ($validatedColumns as $column) {
if ($case_sensitive === True) {
$conditions[] = "(CASE WHEN REGEXP_REPLACE(`$column`, '[^a-zA-Z0-9]', '') LIKE ? THEN 2 ELSE 0 END)";
$searchParams[] = "%".$keyword."%";
@@ -286,7 +293,7 @@ public function search(string $tablename, string $search, array $columns = ["nam
}
}
$searchQuery .= implode(" + ", $conditions) . ") AS relevance";
- $searchQuery .= " FROM $tablename WHERE " . implode(" OR ", $conditions) . " HAVING relevance > 1";
+ $searchQuery .= " FROM `$validatedTable` WHERE " . implode(" OR ", $conditions) . " HAVING relevance > 1";
$searchQuery .= " ORDER BY relevance DESC";
if ($limit > 0) {
if ($offset > 0) {
@@ -398,5 +405,4 @@ public function countRows(string $table, ?string $column = null, ?string $value
- } # END CLASS
- ?>
\ No newline at end of file
+ } # END CLASS
\ No newline at end of file
diff --git a/PHPUtils/SQLite.php b/PHPUtils/SQLite.php
index 34ac90b..49a0534 100755
--- a/PHPUtils/SQLite.php
+++ b/PHPUtils/SQLite.php
@@ -1,5 +1,7 @@
$status,
"data" => $data,
@@ -36,43 +38,43 @@ function res(string $status = "UNKNOWN", mixed $data = "No data.") : array {
}
# FUNCTION: sqlite_create_db
- function sqlite_create_db(string $dbname = 'database') : array {
+ public function sqlite_create_db(string $dbname = 'database') : array {
// Create a new database, if the file doesn't exist and open it for reading/writing.
// The extension of the file is arbitrary.
if (!is_writable(dirname($dbname.'.sqlite'))) {
- return res("ERROR", "The directory is not writable.");
+ return $this->res("ERROR", "The directory is not writable.");
}
// if (file_exists($dbname.'.sqlite')) {
- // return res("ERROR", "The database already exists.");
+ // return $this->res("ERROR", "The database already exists.");
// }
- $db = new SQLite3($dbname.'.sqlite', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
+ $db = new \SQLite3($dbname.'.sqlite', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
$db->enableExceptions(true);
- return res("SUCCESS", $db);
+ return $this->res("SUCCESS", $db);
}
# FUNCTION: sqlite_select_db
- function sqlite_select_db(string $dbname = 'database') : array {
+ public function sqlite_select_db(string $dbname = 'database') : array {
// Open an existing database for reading/writing.
if (!file_exists($dbname.'.sqlite')) {
- return res("ERROR", "The database does not exist.");
+ return $this->res("ERROR", "The database does not exist.");
}
- $db = new SQLite3($dbname.'.sqlite', SQLITE3_OPEN_READWRITE);
+ $db = new \SQLite3($dbname.'.sqlite', SQLITE3_OPEN_READWRITE);
// Errors are emitted as warnings by default, enable proper error handling.
$db->enableExceptions(true);
- return res("SUCCESS", $db);
+ return $this->res("SUCCESS", $db);
}
# FUNCTION: sqlite_drop_db
- function sqlite_drop_db(string $dbname = 'database') : array {
+ public function sqlite_drop_db(string $dbname = 'database') : array {
// Delete the database file.
if (!file_exists($dbname.'.sqlite')) {
- return res("ERROR", "The database does not exist.");
+ return $this->res("ERROR", "The database does not exist.");
}
if (!is_writable($dbname.'.sqlite')) {
- return res("ERROR", "The database is not writable.");
+ return $this->res("ERROR", "The database is not writable.");
}
$result = unlink($dbname.'.sqlite');
- return res("SUCCESS", $result);
+ return $this->res("SUCCESS", $result);
}
# FUNCTION: sqlite_get_dbs
@@ -83,7 +85,7 @@ function sqlite_drop_db(string $dbname = 'database') : array {
*
* @return array An array of database names.
*/
- function sqlite_get_dbs(string $dir = '.') : array {
+ public function sqlite_get_dbs(string $dir = '.') : array {
$dbs = [];
$files = scandir($dir);
foreach ($files as $file) {
@@ -91,18 +93,18 @@ function sqlite_get_dbs(string $dir = '.') : array {
$dbs[] = pathinfo($file, PATHINFO_FILENAME);
}
}
- return res("SUCCESS", $dbs);
+ return $this->res("SUCCESS", $dbs);
}
# FUNCTION: sqlite_get_tables
/**
* Get a list of tables in the database.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
*
* @return array An array of table names.
*/
- function sqlite_get_tables(SQLite3 $db) : array {
+ public function sqlite_get_tables(\SQLite3 $db) : array {
try {
$query = 'SELECT name FROM sqlite_master WHERE type="table";';
$result = $db->query($query);
@@ -110,9 +112,9 @@ function sqlite_get_tables(SQLite3 $db) : array {
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$tables[] = $row['name'];
}
- return res("SUCCESS", $tables);
+ return $this->res("SUCCESS", $tables);
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -120,16 +122,16 @@ function sqlite_get_tables(SQLite3 $db) : array {
/**
* Create a table in the database.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
* @param string $table_name The name of the table.
* @param array $columns An associative array of column names and types. The key is the column name, the value is the column type.
* Example: ["column_name" => "column_type"]
* You don't need to specify the "id" column, it's automatically created as an INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL.
*/
- function sqlite_create_table(SQLite3 $db, string $table_name, array $columns = ["column_name" => "column_type"]) : array {
+ public function sqlite_create_table(\SQLite3 $db, string $table_name, array $columns = ["column_name" => "column_type"]) : array {
try {
if (sqlite_table_exists($db, $table_name)) {
- return res("ERROR", "Table $table_name already exists.");
+ return $this->res("ERROR", "Table $table_name already exists.");
}
$query = 'CREATE TABLE IF NOT EXISTS "'.$table_name.'" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@@ -139,9 +141,9 @@ function sqlite_create_table(SQLite3 $db, string $table_name, array $columns = [
}
$query = rtrim($query, ', ');
$query .= ');';
- return res("SUCCESS", $db->query($query));
+ return $this->res("SUCCESS", $db->query($query));
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -149,15 +151,15 @@ function sqlite_create_table(SQLite3 $db, string $table_name, array $columns = [
/**
* Drop a table from the database.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
* @param string $table_name The name of the table.
*/
- function sqlite_drop_table(SQLite3 $db, string $table_name) : array {
+ public function sqlite_drop_table(\SQLite3 $db, string $table_name) : array {
try {
$query = 'DROP TABLE IF EXISTS "'.$table_name.'";';
- return res("SUCCESS", $db->query($query));
+ return $this->res("SUCCESS", $db->query($query));
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -165,15 +167,15 @@ function sqlite_drop_table(SQLite3 $db, string $table_name) : array {
/**
* Empty a table in the database.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
* @param string $table_name The name of the table.
*/
- function sqlite_empty_table(SQLite3 $db, string $table_name) : array {
+ public function sqlite_empty_table(\SQLite3 $db, string $table_name) : array {
try {
$query = 'DELETE FROM "'.$table_name.'";';
- return res("SUCCESS", $db->query($query));
+ return $this->res("SUCCESS", $db->query($query));
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -181,18 +183,18 @@ function sqlite_empty_table(SQLite3 $db, string $table_name) : array {
/**
* Check if a table exists in the database.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
* @param string $table_name The name of the table.
*
* @return bool TRUE if the table exists, FALSE if it doesn't.
*/
- function sqlite_table_exists(SQLite3 $db, string $table_name) : array {
+ public function sqlite_table_exists(\SQLite3 $db, string $table_name) : array {
try {
$query = 'SELECT name FROM sqlite_master WHERE type="table" AND name="'.$table_name.'";';
$result = $db->querySingle($query);
- return res("SUCCESS", ($result === $table_name));
+ return $this->res("SUCCESS", ($result === $table_name));
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -200,12 +202,12 @@ function sqlite_table_exists(SQLite3 $db, string $table_name) : array {
/**
* Get a list of columns in a table.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
* @param string $table_name The name of the table.
*
* @return array An array of column names.
*/
- function sqlite_get_columns(SQLite3 $db, string $table_name) : array {
+ public function sqlite_get_columns(\SQLite3 $db, string $table_name) : array {
try {
$query = 'PRAGMA table_info("'.$table_name.'");';
$result = $db->query($query);
@@ -216,9 +218,9 @@ function sqlite_get_columns(SQLite3 $db, string $table_name) : array {
"type" => $row['type'],
];
}
- return res("SUCCESS", $columns);
+ return $this->res("SUCCESS", $columns);
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -226,23 +228,23 @@ function sqlite_get_columns(SQLite3 $db, string $table_name) : array {
/**
* Execute a query.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
* @param string $query The query to execute.
*/
- function sqlite_query(?SQLite3 $db, $query) : array {
+ public function sqlite_query(?\SQLite3 $db, $query) : array {
try {
if (!$db) {
- return res("ERROR", "Database connection not found.");
+ return $this->res("ERROR", "Database connection not found.");
}
// $start = $db->exec('BEGIN');
$query = $db->query($query);
// $commit = $db->exec('COMMIT');
if ($query === false) {
- return res("ERROR", $db->lastErrorMsg());
+ return $this->res("ERROR", $db->lastErrorMsg());
}
- return res("SUCCESS", $query);
+ return $this->res("SUCCESS", $query);
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -250,14 +252,14 @@ function sqlite_query(?SQLite3 $db, $query) : array {
/**
* Insert data into a table.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
* @param string $table The name of the table.
* @param array $data An associative array of column names and values. The key is the column name, the value is the value to insert.
* Example: ["column_name" => "value"]
*
* @return bool TRUE on success, FALSE on failure.
*/
- function sqlite_insert(SQLite3 $db, string $table, array $data) : array {
+ public function sqlite_insert(\SQLite3 $db, string $table, array $data) : array {
try {
$columns = implode(', ', array_keys($data));
$placeholders = implode(', ', array_fill(0, count($data), '?'));
@@ -272,11 +274,11 @@ function sqlite_insert(SQLite3 $db, string $table, array $data) : array {
$insert = $stmt->execute();
if ($insert === false) {
- return res("ERROR", $db->lastErrorMsg());
+ return $this->res("ERROR", $db->lastErrorMsg());
}
- return res("SUCCESS", $insert);
+ return $this->res("SUCCESS", $insert);
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -284,7 +286,7 @@ function sqlite_insert(SQLite3 $db, string $table, array $data) : array {
/**
* Select data from a table.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
* @param string $table The name of the table.
* @param array $columns An array of column names to select.
* @param string $where The WHERE clause.
@@ -292,10 +294,10 @@ function sqlite_insert(SQLite3 $db, string $table, array $data) : array {
* @param string $order The ORDER BY clause.
* @param string $limit The LIMIT clause.
*/
- function sqlite_select(SQLite3 $db, string $table, array $columns = ["*"], string $where = "", array $params = [], string $order = "", string $limit = "") : array {
+ public function sqlite_select(\SQLite3 $db, string $table, array $columns = ["*"], string $where = "", array $params = [], string $order = "", string $limit = "") : array {
try {
if (!sqlite_table_exists($db, $table)) {
- return res("ERROR", "Table $table does not exist.");
+ return $this->res("ERROR", "Table $table does not exist.");
}
$columns = implode(', ', $columns);
$query = "SELECT $columns FROM $table";
@@ -316,14 +318,14 @@ function sqlite_select(SQLite3 $db, string $table, array $columns = ["*"], strin
$result = $stmt->execute();
if ($result === false) {
- return res("ERROR", $db->lastErrorMsg());
+ return $this->res("ERROR", $db->lastErrorMsg());
}
if ($result->numColumns() == 0) {
- return res("ERROR", "No columns returned.");
+ return $this->res("ERROR", "No columns returned.");
}
- return res("SUCCESS", $result);
+ return $this->res("SUCCESS", $result);
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -331,7 +333,7 @@ function sqlite_select(SQLite3 $db, string $table, array $columns = ["*"], strin
/**
* Update data in a table.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
* @param string $table The name of the table.
* @param array $data An associative array of column names and values. The key is the column name, the value is the value to update.
* Example: ["column_name" => "value"]
@@ -340,7 +342,7 @@ function sqlite_select(SQLite3 $db, string $table, array $columns = ["*"], strin
*
* @return bool TRUE on success, FALSE on failure.
*/
- function sqlite_update(SQLite3 $db, string $table, array $data, string $where, array $params) : array {
+ public function sqlite_update(\SQLite3 $db, string $table, array $data, string $where, array $params) : array {
try {
$set = "";
foreach ($data as $column => $value) {
@@ -362,9 +364,9 @@ function sqlite_update(SQLite3 $db, string $table, array $data, string $where, a
}
$stmt->execute();
- return res("SUCCESS", $stmt->changes > 0);
+ return $this->res("SUCCESS", $stmt->changes > 0);
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -372,14 +374,14 @@ function sqlite_update(SQLite3 $db, string $table, array $data, string $where, a
/**
* Delete data from a table.
*
- * @param SQLite3 $db The database connection.
+ * @param \SQLite3 $db The database connection.
* @param string $table The name of the table.
* @param string $where The WHERE clause.
* @param array $params An array of parameters to bind to the WHERE clause.
*
* @return bool TRUE on success, FALSE on failure.
*/
- function sqlite_delete(SQLite3 $db, string $table, string $where, array $params) : array {
+ public function sqlite_delete(\SQLite3 $db, string $table, string $where, array $params) : array {
try {
$query = "DELETE FROM $table WHERE $where";
@@ -391,9 +393,9 @@ function sqlite_delete(SQLite3 $db, string $table, string $where, array $params)
}
$stmt->execute();
- return res("SUCCESS", $stmt->changes > 0);
+ return $this->res("SUCCESS", $stmt->changes > 0);
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
@@ -401,11 +403,11 @@ function sqlite_delete(SQLite3 $db, string $table, string $where, array $params)
/**
* Get the SQLite version.
*/
- function sqlite_version($filter = Null) : array {
+ public function sqlite_version($filter = Null) : array {
if ($filter) {
- return res("SUCCESS", !empty(SQLite3::version()[$filter]) ? SQLite3::version()[$filter] : "Unknown");
+ return $this->res("SUCCESS", !empty(\SQLite3::version()[$filter]) ? \SQLite3::version()[$filter] : "Unknown");
}
- return res("SUCCESS", json_encode(SQLite3::version()));
+ return $this->res("SUCCESS", json_encode(\SQLite3::version()));
}
# FUNCTION: sqlite_function
@@ -415,29 +417,29 @@ function sqlite_version($filter = Null) : array {
* @param func_name The sqlite_* function to run.
* @param params The parameters to pass to the function.
*/
- function sqlite_function($func_name, ...$params) : array {
+ public function sqlite_function($func_name, ...$params) : array {
try {
$func_name = "sqlite_".str_replace("sqlite_", "", $func_name);
if (!function_exists($func_name)) {
- return res("ERROR", "Function $func_name does not exist.");
+ return $this->res("ERROR", "Function $func_name does not exist.");
}
$call = call_user_func($func_name, ...$params);
- return res($call["status"], $call["data"]);
+ return $this->res($call["status"], $call["data"]);
$call_status = $call["status"];
$call_data = $call["data"];
if (is_string($call_data)) {
$call_data = clean($call_data);
}
if ($call_status == "ERROR") {
- return res("ERROR", $call_data);
+ return $this->res("ERROR", $call_data);
}
if ($call_data["status"] == "ERROR") {
- return res("ERROR", $call_data);
+ return $this->res("ERROR", $call_data);
}
- return res($call_status, $call_data);
+ return $this->res($call_status, $call_data);
} catch (Exception $e) {
- return res("ERROR", $e->getMessage());
+ return $this->res("ERROR", $e->getMessage());
}
}
diff --git a/PHPUtils/Session.php b/PHPUtils/Session.php
index 21c758f..9bb9eb7 100755
--- a/PHPUtils/Session.php
+++ b/PHPUtils/Session.php
@@ -1,5 +1,7 @@
setTimeZone($tz);
$return = $dt->format($format);
@@ -40,8 +42,8 @@ public function getCurrentTime(string $format, string $timezone) : string {
* @return string The relative time
*/
public function relativeTime($time, $format = null) {
- $then = new DateTime('now');
- $now = new DateTime($time);
+ $now = new \DateTime('now');
+ $then = new \DateTime($time);
$diff = $now->diff($then);
# Translate the format
diff --git a/PHPUtils/Vars.php b/PHPUtils/Vars.php
index 33beab4..18b7708 100755
--- a/PHPUtils/Vars.php
+++ b/PHPUtils/Vars.php
@@ -1,5 +1,7 @@
arrayInString($haystack, $needle);
+ }
/* ───────────────────────────────────────────────────────────────────── */
/* stringify */
diff --git a/PHPUtils/_All.php b/PHPUtils/_All.php
index a6477d0..f836799 100755
--- a/PHPUtils/_All.php
+++ b/PHPUtils/_All.php
@@ -1,11 +1,12 @@
-
8 in order to work.");
+ die("PHPUtils requires PHP 8.0 or later.");
}
diff --git a/README.md b/README.md
index c18d95e..9b2c58f 100755
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# php-utils
-Disclaimer: This is so early in development it doesn't even have any useful stuff yet. Please come back later.
+General-purpose PHP utility classes. Requires PHP 8.0+.
# ────────────────────────────────────────────────────────── #
@@ -28,32 +28,56 @@ Simply open your PHP project and clone this repo.
git clone git@github.com:Darknetzz/php-utils.git
```
-Then include the `_All.php` if you want to be able to use anything from this library on demand.
+Then include the library. You can use Composer autoload (recommended) or `_All.php`:
+
+**Using Composer (recommended):**
+```bash
+composer require darknetzz/php-utils
+```
+```php
+require_once __DIR__ . '/vendor/autoload.php';
+
+use PHPUtils\Crypto;
+$crypto = new Crypto();
+$hashedPassword = $crypto->hash("MyPassword123");
+```
+
+**Using _All.php (e.g. when cloned as a subfolder):**
```php
-include_once("php-utils/_All.php");
+include_once("php-utils/PHPUtils/_All.php"); // adjust path if your folder name differs
-# Then you can instantiate any of the classes and start using them.
-$crypto = new Crypto;
+use PHPUtils\Crypto;
+$crypto = new Crypto();
$hashedPassword = $crypto->hash("MyPassword123");
```
# Modules
-Here is a list of all the modules (classes) and their methods.
-
-* [API](Docs/API.md)
-* [Auth](Docs/Auth.md)
-* [Calendar](Docs/Calendar.md)
-* [Crypto](Docs/Crypto.md)
-* [Debugger](Docs/Debugger.md)
-* [Files](Docs/Files.md)
-* [Funcs](Docs/Funcs.md)
-* [Images](Docs/Images.md)
-* [Navigation](Docs/Navigation.md)
-* [Network](Docs/Network.md)
-* [Random](Docs/Random.md)
-* [Resources](Docs/Resources.md)
-* [Session](Docs/Session.md)
-* [SQL](Docs/SQL.md)
-* [Strings](Docs/Strings.md)
-* [Times](Docs/Times.md)
-* [Vars](Docs/Vars.md)
+API documentation is generated in the `Docs/` folder. Class reference:
+
+* [API](Docs/classes/API.html)
+* [Auth](Docs/classes/Auth.html)
+* [Calendar](Docs/classes/Calendar.html)
+* [Crypto](Docs/classes/Crypto.html)
+* [Debugger](Docs/classes/Debugger.html)
+* [Files](Docs/classes/Files.html)
+* [Funcs](Docs/classes/Funcs.html)
+* [Images](Docs/classes/Images.html)
+* [Navigation](Docs/classes/Navigation.html)
+* [Network](Docs/classes/Network.html)
+* [Random](Docs/classes/Random.html)
+* [Resources](Docs/classes/Resources.html)
+* [Session](Docs/classes/Session.html)
+* [SQL](Docs/classes/SQL.html)
+* [SQLite](Docs/classes/SQLite.html)
+* [Strings](Docs/classes/Strings.html)
+* [Times](Docs/classes/Times.html)
+* [Vars](Docs/classes/Vars.html)
+
+## Changelog
+
+See [CHANGELOG.md](CHANGELOG.md) for recent improvements (strict types, exception-based error handling, new tests, CI).
+
+## Development
+
+- **Tests:** `composer install && vendor/bin/phpunit`
+- **CI:** GitHub Actions run PHPUnit on PHP 8.0–8.4 (see [.github/workflows/php.yml](.github/workflows/php.yml)).
diff --git a/REFACTORING_SUMMARY.md b/REFACTORING_SUMMARY.md
index 9999ad4..caf6123 100755
--- a/REFACTORING_SUMMARY.md
+++ b/REFACTORING_SUMMARY.md
@@ -1,5 +1,7 @@
# OOP Refactoring Summary
+For the latest improvements (strict types, exceptions, new tests, CI), see [CHANGELOG.md](CHANGELOG.md).
+
## Changes Implemented
This document summarizes all the OOP improvements made to the PHPUtils codebase.
diff --git a/TEST_SUMMARY.md b/TEST_SUMMARY.md
index ddae27c..086b18a 100755
--- a/TEST_SUMMARY.md
+++ b/TEST_SUMMARY.md
@@ -18,6 +18,11 @@ A comprehensive PHPUnit test suite has been created for the PHPUtils library.
| StringsTest | 10 | ✅ Passing |
| NetworkTest | 17 | ✅ Passing |
| SQLTest | 11 | ⚠️ Requires MySQL |
+| CryptoTest | 7 | ✅ Passing |
+| FilesTest | 5 | ✅ Passing |
+| SQLiteTest | 4 | ✅ Passing |
+| TimesTest | 4 | ✅ Passing |
+| RandomTest | 8 | ✅ Passing |
## Test Files Created
@@ -150,15 +155,31 @@ composer install
4. **Constants** - SQL constants are tested
5. **Separation of Concerns** - Debugger's data/HTML separation is tested
-## Future Test Additions
+### CryptoTest
+- Hash and verifyhash (correct algorithm order, timing-safe comparison)
+- genIV format
+- Encrypt/decrypt with and without IV
-Consider adding tests for:
-- Files class
-- Random class
-- Times class
-- Images class
-- Crypto class
-- Other utility classes
+### FilesTest
+- is_file with existing file, non-existent path, directory
+- file_read with temp file and non-existent file (expects exception)
+
+### SQLiteTest
+- res() structure and defaults
+- clean() strips newlines and encodes
+- sqlite_create_db / sqlite_select_db / sqlite_drop_db with temp path
+
+### TimesTest
+- getCurrentTime format and timezone
+- relativeTime with format and default
+
+### RandomTest
+- array_pick_random, roll (range), percentage (0/50/100), genStr length
+
+## Code Quality
+
+- All library and test files use `declare(strict_types=1);`.
+- Network, Random, Files, and Debugger use exceptions instead of `die()` where appropriate (see CHANGELOG.md).
## Notes
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..f4d9366
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,1801 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "716dea4375bf4cd425189ebfb3ca3cf4",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.13.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-01T08:46:24+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
+ },
+ "time": "2025-12-06T11:56:16+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "11.0.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56",
+ "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^5.7.0",
+ "php": ">=8.2",
+ "phpunit/php-file-iterator": "^5.1.0",
+ "phpunit/php-text-template": "^4.0.1",
+ "sebastian/code-unit-reverse-lookup": "^4.0.1",
+ "sebastian/complexity": "^4.0.1",
+ "sebastian/environment": "^7.2.1",
+ "sebastian/lines-of-code": "^3.0.1",
+ "sebastian/version": "^5.0.2",
+ "theseer/tokenizer": "^1.3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.5.46"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "11.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-12-24T07:01:01+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "5.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903",
+ "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-02T13:52:54+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "5.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:07:44+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:08:43+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "7.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:09:35+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "11.5.50",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3",
+ "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.13.4",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=8.2",
+ "phpunit/php-code-coverage": "^11.0.12",
+ "phpunit/php-file-iterator": "^5.1.0",
+ "phpunit/php-invoker": "^5.0.1",
+ "phpunit/php-text-template": "^4.0.1",
+ "phpunit/php-timer": "^7.0.1",
+ "sebastian/cli-parser": "^3.0.2",
+ "sebastian/code-unit": "^3.0.3",
+ "sebastian/comparator": "^6.3.3",
+ "sebastian/diff": "^6.0.2",
+ "sebastian/environment": "^7.2.1",
+ "sebastian/exporter": "^6.3.2",
+ "sebastian/global-state": "^7.0.2",
+ "sebastian/object-enumerator": "^6.0.1",
+ "sebastian/type": "^5.1.3",
+ "sebastian/version": "^5.0.2",
+ "staabm/side-effects-detector": "^1.0.5"
+ },
+ "suggest": {
+ "ext-soap": "To be able to generate mocks based on WSDL files"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "11.5-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-27T05:59:18+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:41:36+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64",
+ "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "security": "https://github.com/sebastianbergmann/code-unit/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-03-19T07:56:08+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:45:54+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "6.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9",
+ "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/diff": "^6.0",
+ "sebastian/exporter": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.4"
+ },
+ "suggest": {
+ "ext-bcmath": "For comparing BcMath\\Number objects"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.3-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "security": "https://github.com/sebastianbergmann/comparator/security/policy",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-24T09:26:40+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "security": "https://github.com/sebastianbergmann/complexity/security/policy",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:49:50+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "security": "https://github.com/sebastianbergmann/diff/security/policy",
+ "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:53:05+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "7.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4",
+ "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "https://github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "security": "https://github.com/sebastianbergmann/environment/security/policy",
+ "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/environment",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-21T11:55:47+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "6.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "70a298763b40b213ec087c51c739efcaa90bcd74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74",
+ "reference": "70a298763b40b213ec087c51c739efcaa90bcd74",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.3-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-09-24T06:12:51+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "7.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "https://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:57:36+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:58:38+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "6.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:00:13+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:01:32+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "6.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc",
+ "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-13T04:42:22+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "5.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449",
+ "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "security": "https://github.com/sebastianbergmann/type/security/policy",
+ "source": "https://github.com/sebastianbergmann/type/tree/5.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/type",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-09T06:55:48+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "5.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874",
+ "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/5.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-09T05:16:32+00:00"
+ },
+ {
+ "name": "staabm/side-effects-detector",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/staabm/side-effects-detector.git",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.6",
+ "phpunit/phpunit": "^9.6.21",
+ "symfony/var-dumper": "^5.4.43",
+ "tomasvotruba/type-coverage": "1.0.0",
+ "tomasvotruba/unused-public": "1.0.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "lib/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A static analysis tool to detect side effects in PHP code",
+ "keywords": [
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/staabm/side-effects-detector/issues",
+ "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/staabm",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-20T05:08:20+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.3.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2025-11-17T20:03:58+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=8.0"
+ },
+ "platform-dev": {},
+ "plugin-api-version": "2.9.0"
+}
diff --git a/index.php b/index.php
index a13d4b5..963b753 100755
--- a/index.php
+++ b/index.php
@@ -27,22 +27,23 @@