Compare commits

..

16 Commits

Author SHA1 Message Date
Clemens Schwaighofer
59948c0f3d Name fix for MARK in SQLite DB IO 2025-07-22 18:16:43 +09:00
Clemens Schwaighofer
0022ea6b9a Comment update for table meta data read 2025-07-17 16:58:18 +09:00
Clemens Schwaighofer
1b387baea9 Actually ?1 is a valid SQLite set to assign the first parameter to both like $1 2025-07-17 16:55:59 +09:00
Clemens Schwaighofer
8574175e78 Placeholder fix for SQLite table meta data query 2025-07-17 16:32:39 +09:00
Clemens Schwaighofer
ec8c4d6e60 SQL lite from JIRA csv data import 2025-07-17 11:09:32 +09:00
Clemens Schwaighofer
4ed645bac3 Update email check with better domain name check (ASCII), logging class debug output update 2025-07-15 13:13:11 +09:00
Clemens Schwaighofer
908376c1a5 Array sort method doc update 2025-06-26 11:43:28 +09:00
Clemens Schwaighofer
c329e7a2da Add JSON_UNESCAPED_UNICODE as default flag for json convert to array calls 2025-06-26 11:39:38 +09:00
Clemens Schwaighofer
ad7b59e26a phan check swich from phive to composer package 2025-06-05 18:02:03 +09:00
Clemens Schwaighofer
c43bb0662d check scripts update: phan from phive is too old 2025-06-05 18:01:12 +09:00
Clemens Schwaighofer
c4e83f94e9 Check scripts update 2025-06-05 17:56:29 +09:00
Clemens Schwaighofer
a292abc2c5 phpstan updates 2025-06-05 15:52:43 +09:00
Clemens Schwaighofer
6c5af91386 Add new Logging option to turn on error_log write
On default the level for error_log write is Emergency.
This can be changed either on class creation or via set/get methods.

If logging is skipped because the logging level does not match the main logging level the error_log write is also skipped.

Added MARK fields in the Logging class
2025-06-05 15:32:39 +09:00
Clemens Schwaighofer
73fc74a43a Fix old basic class call for random key length init 2025-06-05 14:47:02 +09:00
Clemens Schwaighofer
b89238b922 Merge branch 'release/v9.34.0' into feature/TTD-2650/string-class-update-with-string-check-helpers 2025-06-05 14:44:26 +09:00
Clemens Schwaighofer
9115fc9557 phan and phpstan fixes 2025-06-05 14:43:18 +09:00
20 changed files with 772 additions and 56 deletions

View File

@@ -3,7 +3,7 @@
<phar name="phpunit" version="^10.3.5" installed="10.5.46" location="./tools/phpunit" copy="false"/>
<phar name="phpcbf" version="^3.7.2" installed="3.13.0" location="./tools/phpcbf" copy="false"/>
<phar name="phpcs" version="^3.10.3" installed="3.13.0" location="./tools/phpcs" copy="false"/>
<phar name="phpstan" version="^2.0" installed="2.1.16" location="./tools/phpstan" copy="false"/>
<phar name="phpstan" version="^2.0" installed="2.1.17" location="./tools/phpstan" copy="false"/>
<phar name="phan" version="^5.4.3" installed="5.4.3" location="./tools/phan" copy="false"/>
<phar name="psalm" version="^5.15.0" installed="5.24.0" location="./tools/psalm" copy="false"/>
<phar name="phpdox" version="^0.12.0" installed="0.12.0" location="./tools/phpdox" copy="false"/>

View File

@@ -1,5 +1,6 @@
base="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/";
base=$(pwd)"/";
# must be run in ${base}
cd $base || exit;
${base}tools/phan --progress-bar -C --analyze-twice;
#PHAN_DISABLE_XDEBUG_WARN=1;${base}tools/phan --progress-bar -C --analyze-twice
PHAN_DISABLE_XDEBUG_WARN=1;${base}vendor/bin/phan --progress-bar -C --analyze-twice
cd ~ || exit;

View File

@@ -1,4 +1,4 @@
base="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/";
base=$(pwd)"/";
# must be run in ${base}
cd $base || exit;
${base}tools/phpstan;

View File

@@ -23,7 +23,7 @@ EOF
}
# set base variables
BASE_PATH="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/";
BASE_PATH=$(pwd)"/";
PHPUNIT_CONFIG="${BASE_PATH}phpunit.xml";
PHP_BIN_PATH=$(which php);
if [ -z "${PHP_BIN_PATH}" ]; then

View File

@@ -249,7 +249,7 @@ final class CoreLibsLoggingLoggingTest extends TestCase
$this->assertFalse(
$log->loggingLevelIsDebug()
);
// not set, should be debug]
// not set, should be debug
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testSetLoggingLevel',
'log_folder' => self::LOG_FOLDER,
@@ -297,6 +297,71 @@ final class CoreLibsLoggingLoggingTest extends TestCase
$log->setLoggingLevel('NotGood');
}
/**
* Undocumented function
*
* @covers ::setErrorLogWriteLevel
* @covers ::getErrorLogWriteLevel
* @testdox setErrorLogWriteLevel set/get checks
*
* @return void
*/
public function testSetErrorLogWriteLevel(): void
{
// valid that is not Debug
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testSetErrorLogWriteLevel',
'log_folder' => self::LOG_FOLDER,
'error_log_write_level' => Level::Error
]);
$this->assertEquals(
Level::Error,
$log->getErrorLogWriteLevel()
);
// not set on init
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testSetErrorLogWriteLevel',
'log_folder' => self::LOG_FOLDER,
]);
$this->assertEquals(
Level::Emergency,
$log->getErrorLogWriteLevel()
);
// invalid, should be Emergency, will throw excpetion too
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage(
'Option: "error_log_write_level" is not of instance \CoreLibs\Logging\Logger\Level'
);
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testSetLoggingLevel',
'log_folder' => self::LOG_FOLDER,
'error_log_write_level' => 'I'
]);
$this->assertEquals(
Level::Emergency,
$log->getErrorLogWriteLevel()
);
// set valid then change
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testSetErrorLogWriteLevel',
'log_folder' => self::LOG_FOLDER,
'error_log_write_level' => Level::Error
]);
$this->assertEquals(
Level::Error,
$log->getErrorLogWriteLevel()
);
$log->setErrorLogWriteLevel(Level::Notice);
$this->assertEquals(
Level::Notice,
$log->getErrorLogWriteLevel()
);
// illegal logging level
$this->expectException(\Psr\Log\InvalidArgumentException::class);
$this->expectExceptionMessageMatches("/^Level \"NotGood\" is not defined, use one of: /");
$log->setErrorLogWriteLevel('NotGood');
}
// setLogFileId
// getLogFileId

View File

@@ -333,7 +333,7 @@ print "(kosrt, lower case, reverse): "
print "<hr>";
$nested = [
'B' => 'foo', 'a', '0', 9,
1 => ['z', 'b', 'a'],
'1' => ['z', 'b', 'a'],
'd' => ['zaip', 'bar', 'baz']
];
print "Nested: " . DgS::printAr($nested) . "<br>";

View File

@@ -83,6 +83,9 @@ function mtParseCSV(
'UTF-8',
$encoding
);
if ($string === false) {
return $lines;
}
}
if ($flag == 'INTERN') {
// split with PHP function

View File

@@ -82,6 +82,7 @@ $log->error('Cannot process data', ['error' => 'log']);
$log->critical('Critical message', ['critical' => 'log']);
$log->alert('Alert message', ['Alert' => 'log']);
$log->emergency('Emergency message', ['Emergency' => 'log']);
error_log('TRIGGER ERROR LOG MANUAL: Emergency');
print "Log File: " . $log->getLogFile() . "<br>";
$log->setLogFlag(Flag::per_run);

View File

@@ -383,7 +383,8 @@ class Basic
public function initRandomKeyLength(int $key_length): bool
{
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\RandomKey::setRandomKeyLength()', E_USER_DEPRECATED);
return \CoreLibs\Create\RandomKey::setRandomKeyLength($key_length);
// no op, we do no longer pre set the random key length
return true;
}
/**
@@ -988,10 +989,10 @@ class Basic
* @param bool $auto_check default true, if source encoding is set
* check that the source is actually matching
* to what we sav the source is
* @return string encoding converted string
* @return string|false encoding converted string
* @deprecated use \CoreLibs\Convert\Encoding::convertEncoding() instead
*/
public static function convertEncoding(string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true): string
public static function convertEncoding(string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true): string|false
{
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Encoding::convertEncoding()', E_USER_DEPRECATED);
return \CoreLibs\Convert\Encoding::convertEncoding($string, $to_encoding, $source_encoding, $auto_check);

View File

@@ -10,12 +10,16 @@ class Email
/** @var array<int,string> */
private static array $email_regex_check = [
0 => "^[A-Za-z0-9!#$%&'*+\-\/=?^_`{|}~][A-Za-z0-9!#$%:\(\)&'*+\-\/=?^_`{|}~\.]{0,63}@"
. "[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]{1,})*\.([a-zA-Z]{2,}){1}$", // MASTER
// . "[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]{1,})*\.([a-zA-Z]{2,}){1}$", // MASTER
// fixed pattern matching for domain
. "(?!-)[A-Za-z0-9-]{1,63}(?<!-)(?:\.[A-Za-z0-9-]{1,63}(?<!-))*\.[a-zA-Z]{2,6}$", // MASTER
1 => "@(.*)@(.*)", // double @
2 => "^[A-Za-z0-9!#$%&'*+\-\/=?^_`{|}~][A-Za-z0-9!#$%:\(\)&'*+\-\/=?^_`{|}~\.]{0,63}@", // wrong part before @
3 => "@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]{1,})*\.([a-zA-Z]{2,}){1}$", // wrong part after @
4 => "@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]{1,})*\.", // wrong domain name part
5 => "\.([a-zA-Z]{2,6}){1}$", // wrong top level part
// 3 => "@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]{1,})*\.([a-zA-Z]{2,}){1}$", // wrong part after @
3 => "@(?!-)[A-Za-z0-9-]{1,63}(?<!-)(?:\.[A-Za-z0-9-]{1,63}(?<!-))*\.[a-zA-Z]{2,6}$", // wrong part after @
// 4 => "@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]{1,})*\.", // wrong domain name part
4 => "@@(?!-)[A-Za-z0-9-]{1,63}(?<!-)(?:\.[A-Za-z0-9-]{1,63}(?<!-))*\.", // wrong domain name part
5 => "\.[a-zA-Z]{2,6}$", // wrong top level part
6 => "@(.*)\.{2,}", // double .. in domain name part
7 => "@.*\.$" // ends with a dot, top level, domain missing
];

View File

@@ -56,7 +56,11 @@ class Encoding
{
// return mb_substitute_character();
if ($return_substitute_func === true) {
return mb_substitute_character();
// if false abort with error
if (($return = mb_substitute_character()) === false) {
return self::$mb_error_char;
}
return $return;
} else {
return self::$mb_error_char;
}
@@ -88,7 +92,13 @@ class Encoding
): array|false {
// convert to target encoding and convert back
$temp = mb_convert_encoding($string, $to_encoding, $from_encoding);
if ($temp === false) {
return false;
}
$compare = mb_convert_encoding($temp, $from_encoding, $to_encoding);
if ($compare === false) {
return false;
}
// if string does not match anymore we have a convert problem
if ($string == $compare) {
return false;

View File

@@ -251,10 +251,10 @@ class ArrayHandler
* @param array<mixed> $array
* @param string|int|float|bool $search_value
* @param string|array<string> $required_key
* @param ?string $serach_key [null]
* @param ?string $search_key [null]
* @param string $path_separator [DATA_SEPARATOR]
* @param string $current_path
* @return array<array{content?:array<mixed>,path?:string,missing_key?:string}>
* @return array<array{content?:array<mixed>,path?:string,missing_key?:array<string>}>
*/
public static function findArraysMissingKey(
array $array,
@@ -748,8 +748,11 @@ class ArrayHandler
* sort ascending or descending with or without lower case convert
* value only, will loose key connections unless preserve_keys is set to true
*
* @param array<mixed> $array array to sort by values
* @param int $params sort flags
* @param array<mixed> $array Array to sort by values
* @param bool $case_insensitive [false] Sort case insensitive
* @param bool $reverse [false] Reverse sort
* @param bool $maintain_keys [false] Maintain keys
* @param int $flag [SORT_REGULAR] Sort flags
* @return array<mixed>
*/
public static function sortArray(
@@ -757,7 +760,7 @@ class ArrayHandler
bool $case_insensitive = false,
bool $reverse = false,
bool $maintain_keys = false,
int $params = SORT_REGULAR
int $flag = SORT_REGULAR
): array {
$fk_sort_lower_case = function (string $a, string $b): int {
return strtolower($a) <=> strtolower($b);
@@ -772,8 +775,8 @@ class ArrayHandler
) :
(
$maintain_keys ?
($reverse ? arsort($array, $params) : asort($array, $params)) :
($reverse ? rsort($array, $params) : sort($array, $params))
($reverse ? arsort($array, $flag) : asort($array, $flag)) :
($reverse ? rsort($array, $flag) : sort($array, $flag))
);
return $array;
}
@@ -781,9 +784,9 @@ class ArrayHandler
/**
* sort by key ascending or descending and return
*
* @param array<mixed> $array
* @param bool $case_insensitive [false]
* @param bool $reverse [false]
* @param array<mixed> $array Array to srt
* @param bool $case_insensitive [false] Sort keys case insenstive
* @param bool $reverse [false] Reverse key sort
* @return array<mixed>
*/
public static function ksortArray(array $array, bool $case_insensitive = false, bool $reverse = false): array

View File

@@ -23,14 +23,14 @@ class Encoding
* @param bool $auto_check default true, if source encoding is set
* check that the source is actually matching
* to what we sav the source is
* @return string encoding converted string
* @return string|false encoding converted string or false on error
*/
public static function convertEncoding(
string $string,
string $to_encoding,
string $source_encoding = '',
bool $auto_check = true
): string {
): string|false {
// set if not given
if (!$source_encoding) {
$source_encoding = mb_detect_encoding($string);

View File

@@ -55,10 +55,10 @@ class Json
* Deos not throw errors
*
* @param array<mixed> $data
* @param int $flags json_encode flags as is
* @param int $flags [JSON_UNESCAPED_UNICODE] json_encode flags as is
* @return string JSON string or '{}' if false
*/
public static function jsonConvertArrayTo(array $data, int $flags = 0): string
public static function jsonConvertArrayTo(array $data, int $flags = JSON_UNESCAPED_UNICODE): string
{
$json_string = json_encode($data, $flags) ?: '{}';
self::$json_last_error = json_last_error();

View File

@@ -38,6 +38,7 @@ class Email
* @param string $encoding Encoding, if not set UTF-8
* @param bool $kv_folding If set to true and a valid encoding, do KV folding
* @return string Correctly encoded and build email string
* @throws \IntlException if email name cannot be converted to UTF-8
*/
public static function encodeEmailName(
string $email,
@@ -52,6 +53,10 @@ class Email
if ($encoding != 'UTF-8') {
$email_name = mb_convert_encoding($email_name, $encoding, 'UTF-8');
}
// if we cannot transcode the name, return just the email
if ($email_name === false) {
throw new \IntlException('Cannot convert email_name to UTF-8');
}
$email_name =
mb_encode_mimeheader(
in_array($encoding, self::$encoding_kv_allowed) && $kv_folding ?
@@ -77,6 +82,8 @@ class Email
* @param bool $kv_folding If set to true and a valid encoding,
* do KV folding
* @return array<string> Pos 0: Subject, Pos 1: Body
* @throws \IntlException if subject cannot be converted to UTF-8
* @throws \IntlException if body cannot be converted to UTF-8
*/
private static function replaceContent(
string $subject,
@@ -102,6 +109,12 @@ class Email
$subject = mb_convert_encoding($subject, $encoding, 'UTF-8');
$body = mb_convert_encoding($body, $encoding, 'UTF-8');
}
if ($subject === false) {
throw new \IntlException('Cannot convert subject to UTF-8');
}
if ($body === false) {
throw new \IntlException('Cannot convert body to UTF-8');
}
// we need to encodde the subject
$subject = mb_encode_mimeheader(
in_array($encoding, self::$encoding_kv_allowed) && $kv_folding ?

View File

@@ -0,0 +1,90 @@
<?php
/**
* AUTHOR: Clemens Schwaighofer
* CREATED: 2025/7/17
* DESCRIPTION:
* SQLite IO basic interface
*/
declare(strict_types=1);
namespace CoreLibs\DB\SQL\Interface;
interface DatabaseInterface
{
/**
* Table meta data
* Note that if columns have multi
*
* @param string $table
* @return array<array<string,mixed>>|false
*/
public function dbShowTableMetaData(string $table): array|false;
/**
* for reading or simple execution, no return data
*
* @param string $query
* @return int|false
*/
public function dbExec(string $query): int|false;
/**
* Run a simple query and return its statement
*
* @param string $query
* @return \PDOStatement|false
*/
public function dbQuery(string $query): \PDOStatement|false;
/**
* Execute one query with params
*
* @param string $query
* @param array<mixed> $params
* @return \PDOStatement|false
*/
public function dbExecParams(string $query, array $params): \PDOStatement|false;
/**
* Prepare query
*
* @param string $query
* @return \PDOStatement|false
*/
public function dbPrepare(string $query): \PDOStatement|false;
/**
* execute a cursor
*
* @param \PDOStatement $cursor
* @param array<mixed> $params
* @return bool
*/
public function dbCursorExecute(\PDOStatement $cursor, array $params): bool;
/**
* return array with data, when finshed return false
* also returns false on error
*
* TODO: This is currently a one time run
* if the same query needs to be run again, the cursor_ext must be reest
* with dbCacheReset
*
* @param string $query
* @param array<mixed> $params
* @return array<mixed>|false
*/
public function dbReturnArray(string $query, array $params = []): array|false;
/**
* get current db handler
* this is for raw access
*
* @return \PDO
*/
public function getDbh(): \PDO;
}
// __END__

View File

@@ -0,0 +1,433 @@
<?php
/**
* AUTHOR: Clemens Schwaighofer
* CREATED: 2024/8/21
* DESCRIPTION:
* SQL Lite interface
* Note: This is a very simple library and in future should perhaps merge with the master
* CoreLibs SQL interface
*
* TODO: This should move to the CoreLibs\DB\IO class as a sub type for "sqlite" next to "pgsql"
*/
declare(strict_types=1);
namespace CoreLibs\DB;
use CoreLibs\Create\Hash;
class SqLite implements SQL\Interface\DatabaseInterface
{
/** @var \CoreLibs\Logging\Logging logging */
public \CoreLibs\Logging\Logging $log;
/** @var string database connection string */
private string $dsn;
/** @var \PDO database handler */
private \PDO $dbh;
/** @var PDOStatement|false one cursor, for internal handling */
// private \PDOStatement|false $cursor;
/** @var array<string,mixed> extended cursoers string index with content */
private array $cursor_ext = [];
/**
* init database system
*
* @param \CoreLibs\Logging\Logging $log
* @param string $dsn
*/
public function __construct(
\CoreLibs\Logging\Logging $log,
string $dsn
) {
$this->log = $log;
// open new connection
if ($this->__connectToDB($dsn) === false) {
throw new \ErrorException("Cannot load database: " . $dsn, 1);
}
}
// *********************************************************************
// MARK: PRIVATE METHODS
// *********************************************************************
/**
* Get a cursor dump with all info
*
* @param \PDOStatement $cursor
* @return string|false
*/
private function __dbGetCursorDump(\PDOStatement $cursor): string|false
{
// get the cursor info
ob_start();
$cursor->debugDumpParams();
$cursor_dump = ob_get_contents();
ob_end_clean();
return $cursor_dump;
}
/**
* fetch rows from a cursor (post execute)
*
* @param \PDOStatement $cursor
* @return array<mixed>|false
*/
private function __dbFetchArray(\PDOStatement $cursor): array|false
{
try {
// on empty array return false
// TODO make that more elegant?
return empty($row = $cursor->fetch(mode:\PDO::FETCH_NAMED)) ? false : $row;
} catch (\PDOException $e) {
$this->log->error(
"Cannot fetch from cursor",
[
"dsn" => $this->dsn,
"DumpParams" => $this->__dbGetCursorDump($cursor),
"PDOException" => $e
]
);
return false;
}
}
// MARK: open database
/**
* Open database
* reports errors for wrong DSN or failed connection
*
* @param string $dsn
* @return bool
*/
private function __connectToDB(string $dsn): bool
{
// check if dsn starts with ":"
if (!str_starts_with($dsn, "sqlite:")) {
$this->log->error(
"Invalid dsn string",
[
"dsn" => $dsn
]
);
return false;
}
// TODO: if not ":memory:" check if path to file is writeable by system
// avoid double open
if (!empty($this->dsn) && $dsn == $this->dsn) {
$this->log->info(
"Connection already establisehd with this dsn",
[
"dsn" => $dsn,
]
);
return true;
}
// TODO: check that folder is writeable
// set DSN and open connection
$this->dsn = $dsn;
try {
$this->dbh = new \PDO($this->dsn);
} catch (\PDOException $e) {
$this->log->error(
"Cannot open database",
[
"dsn" => $this->dsn,
"PDOException" => $e
]
);
return false;
}
return true;
}
// *********************************************************************
// MARK: PUBLIC METHODS
// *********************************************************************
// MARK: db meta data (table info)
/**
* Table meta data
* Note that if columns have multi entries multiple lines are returned
* ?1 is equal to $1 in this query
*
* @param string $table
* @return array<array<string,mixed>>|false
*/
public function dbShowTableMetaData(string $table): array|false
{
$table_info = [];
$query = <<<SQL
SELECT
ti.cid, ti.name, ti.type, ti.'notnull', ti.dflt_value, ti.pk,
il_ii.idx_name, il_ii.idx_unique, il_ii.idx_origin, il_ii.idx_partial
FROM
sqlite_schema AS m,
pragma_table_info(m.name) AS ti
LEFT JOIN (
SELECT
il.name AS idx_name, il.'unique' AS idx_unique, il.origin AS idx_origin, il.partial AS idx_partial,
ii.cid AS tbl_cid
FROM
sqlite_schema AS m,
pragma_index_list(m.name) AS il,
pragma_index_info(il.name) AS ii
WHERE m.name = ?1
) AS il_ii ON (ti.cid = il_ii.tbl_cid)
WHERE
m.name = ?1
SQL;
while (is_array($row = $this->dbReturnArray($query, [$table]))) {
$table_info[] = [
'cid' => $row['cid'],
'name' => $row['name'],
'type' => $row['type'],
'notnull' => $row['notnull'],
'dflt_value' => $row['dflt_value'],
'pk' => $row['pk'],
'idx_name' => $row['idx_name'],
'idx_unique' => $row['idx_unique'],
'idx_origin' => $row['idx_origin'],
'idx_partial' => $row['idx_partial'],
];
}
if (!$table_info) {
return false;
}
return $table_info;
}
// MARK: db exec
/**
* for reading or simple execution, no return data
*
* @param string $query
* @return int|false
*/
public function dbExec(string $query): int|false
{
try {
return $this->dbh->exec($query);
} catch (\PDOException $e) {
$this->log->error(
"Cannot execute query",
[
"dsn" => $this->dsn,
"query" => $query,
"PDOException" => $e
]
);
return false;
}
}
// MARK: db query
/**
* Run a simple query and return its statement
*
* @param string $query
* @return \PDOStatement|false
*/
public function dbQuery(string $query): \PDOStatement|false
{
try {
return $this->dbh->query($query, \PDO::FETCH_NAMED);
} catch (\PDOException $e) {
$this->log->error(
"Cannot run query",
[
"dsn" => $this->dsn,
"query" => $query,
"PDOException" => $e
]
);
return false;
}
}
// MARK: db prepare & execute calls
/**
* Execute one query with params
*
* @param string $query
* @param array<mixed> $params
* @return \PDOStatement|false
*/
public function dbExecParams(string $query, array $params): \PDOStatement|false
{
// prepare query
if (($cursor = $this->dbPrepare($query)) === false) {
return false;
}
// execute the query, on failure return false
if ($this->dbCursorExecute($cursor, $params) === false) {
return false;
}
return $cursor;
}
/**
* Prepare query
*
* @param string $query
* @return \PDOStatement|false
*/
public function dbPrepare(string $query): \PDOStatement|false
{
try {
// store query with cursor so we can reference?
return $this->dbh->prepare($query);
} catch (\PDOException $e) {
$this->log->error(
"Cannot open cursor",
[
"dsn" => $this->dsn,
"query" => $query,
"PDOException" => $e
]
);
return false;
}
}
/**
* execute a cursor
*
* @param \PDOStatement $cursor
* @param array<mixed> $params
* @return bool
*/
public function dbCursorExecute(\PDOStatement $cursor, array $params): bool
{
try {
return $cursor->execute($params);
} catch (\PDOException $e) {
// write error log
$this->log->error(
"Cannot execute prepared query",
[
"dsn" => $this->dsn,
"params" => $params,
"DumpParams" => $this->__dbGetCursorDump($cursor),
"PDOException" => $e
]
);
return false;
}
}
// MARK: db return array
/**
* Returns hash for query
* Hash is used in all internal storage systems for return data
*
* @param string $query The query to create the hash from
* @param array<mixed> $params If the query is params type we need params
* data to create a unique call one, optional
* @return string Hash, as set by hash long
*/
public function dbGetQueryHash(string $query, array $params = []): string
{
return Hash::hashLong(
$query . (
$params !== [] ?
'#' . json_encode($params) : ''
)
);
}
/**
* resets all data stored to this query
* @param string $query The Query whose cache should be cleaned
* @param array<mixed> $params If the query is params type we need params
* data to create a unique call one, optional
* @return bool False if query not found, true if success
*/
public function dbCacheReset(string $query, array $params = []): bool
{
$query_hash = $this->dbGetQueryHash($query, $params);
// clears cache for this query
if (empty($this->cursor_ext[$query_hash]['query'])) {
$this->log->error('Cannot reset cursor_ext with given query and params', [
"query" => $query,
"params" => $params,
]);
return false;
}
unset($this->cursor_ext[$query_hash]);
return true;
}
/**
* return array with data, when finshed return false
* also returns false on error
*
* TODO: This is currently a one time run
* if the same query needs to be run again, the cursor_ext must be reest
* with dbCacheReset
*
* @param string $query
* @param array<mixed> $params
* @return array<mixed>|false
*/
public function dbReturnArray(string $query, array $params = []): array|false
{
$query_hash = $this->dbGetQueryHash($query, $params);
if (!isset($this->cursor_ext[$query_hash])) {
$this->cursor_ext[$query_hash] = [
// cursor null: unset, if set \PDOStatement
'cursor' => null,
// the query used in this call
'query' => $query,
// parameter
'params' => $params,
// how many rows have been read from db
'read_rows' => 0,
// when fetch array or cache read returns false
// in loop read that means dbReturn retuns false without error
'finished' => false,
];
if (!empty($params)) {
if (($cursor = $this->dbExecParams($query, $params)) === false) {
return false;
}
} else {
if (($cursor = $this->dbQuery($query)) === false) {
return false;
}
}
$this->cursor_ext[$query_hash]['cursor'] = $cursor;
}
// flag finished if row is false
$row = $this->__dbFetchArray($this->cursor_ext[$query_hash]['cursor']);
if ($row === false) {
$this->cursor_ext[$query_hash]['finished'] = true;
} else {
$this->cursor_ext[$query_hash]['read_rows']++;
}
return $row;
}
// MARK: other interface
/**
* get current db handler
* this is for raw access
*
* @return \PDO
*/
public function getDbh(): \PDO
{
return $this->dbh;
}
}
// __END__

View File

@@ -112,7 +112,7 @@ enum Level: int
}
/**
* Returns true if the passed $level is higher or equal to $this
* Returns true if the passed $level is included in set level
*
* @param Level $level
* @return bool

View File

@@ -35,6 +35,7 @@ class Logging
/** @var string log file block separator, not changeable */
private const LOG_FILE_BLOCK_SEPARATOR = '.';
// MARK: OPTION array
// NOTE: the second party array{} hs some errors
/** @var array<string,array<string,string|bool|Level>>|array{string:array{type:string,type_info?:string,mandatory:true,alias?:string,default:string|bool|Level,deprecated:bool,use?:string}} */
private const OPTIONS = [
@@ -50,6 +51,7 @@ class Logging
'type' => 'string', 'mandatory' => false,
'default' => '', 'deprecated' => true, 'use' => 'log_file_id'
],
// log level
'log_level' => [
'type' => 'instance',
'type_info' => '\CoreLibs\Logging\Logger\Level',
@@ -57,6 +59,14 @@ class Logging
'default' => Level::Debug,
'deprecated' => false
],
// level to trigger write to error_log
'error_log_write_level' => [
'type' => 'instance',
'type_info' => '\CoreLibs\Logging\Logger\Level',
'mandatory' => false,
'default' => Level::Emergency,
'deprecated' => false,
],
// options
'log_per_run' => [
'type' => 'bool', 'mandatory' => false,
@@ -92,8 +102,10 @@ class Logging
/** @var array<mixed> */
private array $options = [];
/** @var Level set level */
/** @var Level set logging level */
private Level $log_level;
/** @var Level set level for writing to error_log, will not write if log level lower than error log write level */
private Level $error_log_write_level;
// page and host name
/** @var string */
@@ -145,12 +157,13 @@ class Logging
];
/**
* Init logger
* MARK: Init logger
*
* options array layout
* - log_folder:
* - log_file_id / file_id (will be deprecated):
* - log_level:
* - error_log_write_level: at what level we write to error_log
*
* - log_per_run:
* - log_per_date: (was print_file_date)
@@ -172,6 +185,8 @@ class Logging
// set log level
$this->initLogLevel();
// set error log write level
$this->initErrorLogWriteLevel();
// set log folder from options
$this->initLogFolder();
// set per run UID for logging
@@ -190,8 +205,10 @@ class Logging
// PRIVATE METHODS
// *********************************************************************
// MARK: options check
/**
* Undocumented function
* validate options
*
* @param array<mixed> $options
* @return bool
@@ -263,6 +280,8 @@ class Logging
return true;
}
// MARK: init log elvels
/**
* init log level, just a wrapper to auto set from options
*
@@ -280,6 +299,24 @@ class Logging
$this->setLoggingLevel($this->options['log_level']);
}
/**
* init error log write level
*
* @return void
*/
private function initErrorLogWriteLevel()
{
if (
empty($this->options['error_log_write_level']) ||
!$this->options['error_log_write_level'] instanceof Level
) {
$this->options['error_log_write_level'] = Level::Emergency;
}
$this->setErrorLogWriteLevel($this->options['error_log_write_level']);
}
// MARK: set log folder
/**
* Set the log folder
* If folder is not writeable the script will throw an E_USER_ERROR
@@ -321,6 +358,8 @@ class Logging
return $status;
}
// MARK: set host name
/**
* Set the hostname and port
* If port is not defaul 80 it will be added to the host name
@@ -337,6 +376,8 @@ class Logging
}
}
// MARK: set log file id (file)
/**
* set log file prefix id
*
@@ -395,6 +436,8 @@ class Logging
return $status;
}
// MARK init log flags and levels
/**
* set flags from options and option flags connection internal settings
*
@@ -423,6 +466,19 @@ class Logging
return $this->log_level->includes($level);
}
/**
* Checks that given level is matchins error_log write level
*
* @param Level $level
* @return bool
*/
private function checkErrorLogWriteLevel(Level $level): bool
{
return $this->error_log_write_level->includes($level);
}
// MARK: build log ifle name
/**
* Build the file name for writing
*
@@ -490,6 +546,8 @@ class Logging
return $fn;
}
// MARK: master write log to file
/**
* writes error msg data to file for current level
*
@@ -507,6 +565,10 @@ class Logging
if (!$this->checkLogLevel($level)) {
return false;
}
// if we match level then write to error_log
if ($this->checkErrorLogWriteLevel($level)) {
error_log((string)$message);
}
// build logging file name
// fn is log folder + file name
@@ -531,6 +593,8 @@ class Logging
return true;
}
// MARK: master prepare log
/**
* Prepare the log message with all needed info blocks:
* [timestamp] [host name] [file path + file::row number] [running uid] {class::/->method}
@@ -610,6 +674,7 @@ class Logging
// PUBLIC STATIC METHJODS
// *********************************************************************
// MARK: set log level
/**
* set the log level
*
@@ -670,7 +735,7 @@ class Logging
// **** GET/SETTER
// log level set and get
// MARK: log level
/**
* set new log level
@@ -705,7 +770,30 @@ class Logging
);
}
// log file id set (file name prefix)
// MARK: error log write level
/**
* set the error_log write level
*
* @param string|int|Level $level
* @return void
*/
public function setErrorLogWriteLevel(string|int|Level $level): void
{
$this->error_log_write_level = $this->processLogLevel($level);
}
/**
* get the current level for error_log write
*
* @return Level
*/
public function getErrorLogWriteLevel(): Level
{
return $this->error_log_write_level;
}
// MARK: log file id set (file name prefix)
/**
* sets the internal log file prefix id
@@ -733,7 +821,7 @@ class Logging
return $this->log_file_id;
}
// log unique id set (for per run)
// MARK: log unique id set (for per run)
/**
* Sets a unique id based on current date (y/m/d, h:i:s) and a unique id (8 chars)
@@ -768,7 +856,7 @@ class Logging
return $this->log_file_unique_id;
}
// general log date
// MARK: general log date
/**
* set the log file date to Y-m-d
@@ -791,7 +879,7 @@ class Logging
return $this->log_file_date;
}
// general flag set
// MARK: general flag set
/**
* set one of the basic flags
@@ -846,7 +934,7 @@ class Logging
return $this->log_flags;
}
// log folder/file
// MARK: log folder/file
/**
* set new log folder, check that folder is writeable
@@ -890,7 +978,7 @@ class Logging
return $this->log_file_name;
}
// max log file size
// MARK: max log file size
/**
* set mag log file size
@@ -921,7 +1009,7 @@ class Logging
}
// *********************************************************************
// OPTIONS CALLS
// MARK: OPTIONS CALLS
// *********************************************************************
/**
@@ -939,6 +1027,8 @@ class Logging
// MAIN CALLS
// *********************************************************************
// MARK: main log call
/**
* Commong log interface
*
@@ -976,7 +1066,7 @@ class Logging
}
/**
* DEBUG: 100
* MARK: DEBUG: 100
*
* write debug data to error_msg array
*
@@ -1008,7 +1098,7 @@ class Logging
}
/**
* INFO: 200
* MARK: INFO: 200
*
* @param string|Stringable $message
* @param mixed[] $context
@@ -1027,7 +1117,7 @@ class Logging
}
/**
* NOTICE: 250
* MARK: NOTICE: 250
*
* @param string|Stringable $message
* @param mixed[] $context
@@ -1046,7 +1136,7 @@ class Logging
}
/**
* WARNING: 300
* MARK: WARNING: 300
*
* @param string|Stringable $message
* @param mixed[] $context
@@ -1065,7 +1155,7 @@ class Logging
}
/**
* ERROR: 400
* MARK: ERROR: 400
*
* @param string|Stringable $message
* @param mixed[] $context
@@ -1084,7 +1174,7 @@ class Logging
}
/**
* CTRITICAL: 500
* MARK: CTRITICAL: 500
*
* @param string|Stringable $message
* @param mixed[] $context
@@ -1103,7 +1193,7 @@ class Logging
}
/**
* ALERT: 550
* MARK: ALERT: 550
*
* @param string|Stringable $message
* @param mixed[] $context
@@ -1122,7 +1212,7 @@ class Logging
}
/**
* EMERGENCY: 600
* MARK: EMERGENCY: 600
*
* @param string|Stringable $message
* @param mixed[] $context
@@ -1141,7 +1231,7 @@ class Logging
}
// *********************************************************************
// DEPRECATED SUPPORT CALLS
// MARK: DEPRECATED SUPPORT CALLS
// *********************************************************************
// legacy, but there are too many implemented
@@ -1199,7 +1289,7 @@ class Logging
}
// *********************************************************************
// DEPRECATED METHODS
// MARK: DEPRECATED METHODS
// *********************************************************************
/**
@@ -1365,7 +1455,7 @@ class Logging
}
// *********************************************************************
// DEBUG METHODS
// MARK: DEBUG METHODS
// *********************************************************************
/**
@@ -1385,19 +1475,21 @@ class Logging
Level::Error, Level::Critical, Level::Alert, Level::Emergency
] as $l
) {
print "Check: " . $this->log_level->getName() . " | " . $l->getName() . "<br>";
if ($this->log_level->isHigherThan($l)) {
print "L: " . $this->log_level->getName() . " > " . $l->getName() . "<br>";
print "L(gt): " . $this->log_level->getName() . " > " . $l->getName() . "<br>";
}
if ($this->log_level->includes($l)) {
print "L: " . $this->log_level->getName() . " <= " . $l->getName() . "<br>";
print "L(le): " . $this->log_level->getName() . " <= " . $l->getName() . "<br>";
}
if ($this->log_level->isLowerThan($l)) {
print "L: " . $this->log_level->getName() . " < " . $l->getName() . "<br>";
print "L(lt): " . $this->log_level->getName() . " < " . $l->getName() . "<br>";
}
echo "<br>";
}
// back to options level
$this->initLogLevel();
$this->initErrorLogWriteLevel();
print "OPT set level: " . $this->getLoggingLevel()->getName() . "<br>";
}
}

View File

@@ -599,7 +599,7 @@ class Curl implements Interface\RequestsInterface
// for post we set POST option
if ($type == "post") {
curl_setopt($handle, CURLOPT_POST, true);
} elseif (!empty($type) && in_array($type, self::CUSTOM_REQUESTS)) {
} elseif (in_array($type, self::CUSTOM_REQUESTS)) {
curl_setopt($handle, CURLOPT_CUSTOMREQUEST, strtoupper($type));
}
// set body data if not null, will send empty [] for empty data