Compare commits

...

22 Commits

Author SHA1 Message Date
Clemens Schwaighofer
428c10b547 new Support::getCallStack, new DateTime::intervalStringFormat, exception updates Convert\Byte, Output\Image 2023-10-23 17:13:23 +09:00
Clemens Schwaighofer
dd705be420 Release: v9.9.1 2023-10-16 16:15:09 +09:00
Clemens Schwaighofer
a1ba1257ac DB\IO dbWarning context updates 2023-10-16 16:14:10 +09:00
Clemens Schwaighofer
9569145054 Release: v9.9.0 2023-10-16 15:07:36 +09:00
Clemens Schwaighofer
13602bdd73 DB\IO update with better error reporting and auto update for placeholders in queries 2023-10-16 15:02:30 +09:00
Clemens Schwaighofer
1ef91305d4 Release: v9.8.3 2023-10-12 17:13:32 +09:00
Clemens Schwaighofer
4ab382990e Output\Image Exceptions update 2023-10-12 17:12:15 +09:00
Clemens Schwaighofer
c61a68b131 Release: v9.8.2 2023-10-06 16:53:33 +09:00
Clemens Schwaighofer
c7254c45b7 Update DB\IO with fixed regex for query params detection 2023-10-06 16:52:42 +09:00
Clemens Schwaighofer
4edacc0d13 phpunit tools symlink fix, phpunit boostrap update 2023-10-04 15:01:23 +09:00
Clemens Schwaighofer
128d6533fc Remove composer install phan/phpstan/etc and switch to phive install 2023-10-04 14:49:49 +09:00
Clemens Schwaighofer
c740fb1af1 Release: v9.8.1 2023-10-02 17:34:37 +09:00
Clemens Schwaighofer
64e60d97e7 Logging\ErrorMsg fix for jump target list return array 2023-10-02 17:33:49 +09:00
Clemens Schwaighofer
f414224c54 Release: v9.8.0 2023-10-02 14:06:32 +09:00
Clemens Schwaighofer
71c9fd401d Logging\ErrorMsg add jump target list 2023-10-02 14:04:59 +09:00
Clemens Schwaighofer
0a885f215c Release: v9.7.9 2023-10-02 12:31:21 +09:00
Clemens Schwaighofer
dad6b797e0 ErrorMsg auto log if log level is debug, Form\Generate split out DBArrayIO class 2023-10-02 12:29:50 +09:00
Clemens Schwaighofer
2d7c3c2bba Release: v9.7.8 2023-09-27 11:44:09 +09:00
Clemens Schwaighofer
fb8216ae86 ErrorMessage add notice level 2023-09-27 11:43:12 +09:00
Clemens Schwaighofer
df5070ffbb Release: v9.7.7 2023-09-27 11:26:07 +09:00
Clemens Schwaighofer
872409ef54 All null primary keys in dbWriteDataExt in DB\IO 2023-09-27 11:25:18 +09:00
Clemens Schwaighofer
c4d5cad9e8 Release: v9.7.6 2023-09-27 09:44:52 +09:00
32 changed files with 2826 additions and 1043 deletions

View File

@@ -360,10 +360,7 @@ return [
'directory_list' => [
'src',
'vendor/egrajp/smarty-extended/src',
'vendor/phan/phan/src/Phan',
'vendor/phpunit/phpunit/src',
'vendor/psr/log/src',
'vendor/vimeo/psalm/src/Psalm',
'vendor/gullevek/dotenv',
],

9
.phive/phars.xml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpunit" version="^9.6" installed="9.6.13" location="./tools/phpunit" copy="false"/>
<phar name="phpcs" version="^3.7.2" installed="3.7.2" location="./tools/phpcs" copy="false"/>
<phar name="phpcbf" version="^3.7.2" installed="3.7.2" location="./tools/phpcbf" copy="false"/>
<phar name="psalm" version="^5.15.0" installed="5.15.0" location="./tools/psalm" copy="false"/>
<phar name="phpstan" version="^1.10.37" installed="1.10.37" location="./tools/phpstan" copy="false"/>
<phar name="phan" version="^5.4.2" installed="5.4.2" location="./tools/phan" copy="false"/>
</phive>

View File

@@ -23,3 +23,21 @@ Alternative setup composer local zip file repot:
## Install package
`composer require egrajp/corelibs-composer-all:^8.0`
## Tests
All tests must be run from the base folder
### phan
`phan --progress-bar -C --analyze-twic`
### phpstan
`phpstan`
### phpunit
PHP unit is installed via "phiev"
`tools/phpunit test/phpunit`

View File

@@ -20,11 +20,7 @@
"psr/log": "^3.0@dev"
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phan/phan": "v5.x-dev",
"phpunit/phpunit": "^9",
"egrajp/smarty-extended": "^4.3",
"vimeo/psalm": "^5.0@dev",
"gullevek/dotenv": "dev-master"
},
"repositories": {

View File

@@ -2,5 +2,6 @@
cacheResultFile="/tmp/phpunit-corelibs-composer.result.cache"
colors="true"
verbose="true"
bootstrap="test/phpunit/bootstrap.php"
>
</phpunit>

View File

@@ -1 +1 @@
9.7.5
9.9.1

View File

@@ -44,7 +44,7 @@ class EditBase
* construct form generator
*
* phpcs:ignore
* @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config db config array, mandatory
* @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[],db_convert_placeholder?:bool,db_convert_placeholder_target?:string,db_debug_replace_placeholder?:bool} $db_config db config array, mandatory
* @param \CoreLibs\Logging\Logging $log Logging class, null auto set
* @param \CoreLibs\Language\L10n $l10n l10n language class, null auto set
* @param \CoreLibs\ACL\Login $login login class for ACL settings
@@ -154,7 +154,7 @@ class EditBase
$q = "UPDATE " . $table_name
. " SET order_number = " . $row_data_order[$i]
. " WHERE " . $table_name . "_id = " . $row_data_id[$i];
$q = $this->form->dbExec($q);
$q = $this->form->dba->dbExec($q);
}
} // for all article ids ...
} // if write
@@ -173,7 +173,7 @@ class EditBase
$options_name = [];
$options_selected = [];
// DB read data for menu
while (is_array($res = $this->form->dbReturn($q))) {
while (is_array($res = $this->form->dba->dbReturn($q))) {
$row_data[] = [
"id" => $res[$table_name . "_id"],
"name" => $res["name"],
@@ -431,9 +431,9 @@ class EditBase
$elements[] = $this->form->formCreateElement('template');
break;
case 'edit_pages':
if (!isset($this->form->table_array['edit_page_id']['value'])) {
if (!isset($this->form->dba->getTableArray()['edit_page_id']['value'])) {
$q = "DELETE FROM temp_files";
$this->form->dbExec($q);
$this->form->dba->dbExec($q);
// gets all files in the current dir and dirs given ending with .php
$folders = ['../admin/', '../frontend/'];
$files = ['*.php'];
@@ -461,16 +461,16 @@ class EditBase
if ($t_q) {
$t_q .= ', ';
}
$t_q .= "('" . $this->form->dbEscapeString($pathinfo['dirname']) . "', '"
. $this->form->dbEscapeString($pathinfo['basename']) . "')";
$t_q .= "('" . $this->form->dba->dbEscapeString($pathinfo['dirname']) . "', '"
. $this->form->dba->dbEscapeString($pathinfo['basename']) . "')";
}
$this->form->dbExec($q . $t_q, 'NULL');
$this->form->dba->dbExec($q . $t_q, 'NULL');
$elements[] = $this->form->formCreateElement('filename');
} else {
// show file menu
// just show name of file ...
$this->DATA['filename_exist'] = 1;
$this->DATA['filename'] = $this->form->table_array['filename']['value'];
$this->DATA['filename'] = $this->form->dba->getTableArray()['filename']['value'];
} // File Name View IF
$elements[] = $this->form->formCreateElement('hostname');
$elements[] = $this->form->formCreateElement('name');

View File

@@ -108,7 +108,12 @@ class DateTime
if (preg_match("/(h|m|s|ms)/", (string)$timestamp)) {
return (string)$timestamp;
}
list($timestamp, $ms) = array_pad(explode('.', (string)round((float)$timestamp, 4)), 2, null);
// split to 6 (nano seconds)
list($timestamp, $ms) = array_pad(explode('.', (string)round((float)$timestamp, 6)), 2, null);
// if micro seconds is on and we have none, set to 0
if ($show_micro && $ms === null) {
$ms = 0;
}
// if negative remember
$negative = false;
if ((int)$timestamp < 0) {
@@ -120,6 +125,10 @@ class DateTime
$time_string = '';
// if timestamp is zero, return zero string
if ($timestamp == 0) {
// if no seconds and we have no microseconds either, show no micro seconds
if ($ms == 0) {
$ms = null;
}
$time_string = '0s';
} else {
for ($i = 0, $iMax = count($timegroups); $i < $iMax; $i++) {
@@ -133,11 +142,8 @@ class DateTime
}
// only add ms if we have an ms value
if ($ms !== null) {
// if we have ms and it has leading zeros, remove them, but only if it is nut just 0
$ms = preg_replace("/^0+(\d+)$/", '${1}', $ms);
if (!is_string($ms) || empty($ms)) {
$ms = '0';
}
// prefix the milliseoncds with 0. and round it max 3 digits and then convert to int
$ms = round((float)('0.' . $ms), 3) * 1000;
// add ms if there
if ($show_micro) {
$time_string .= ' ' . $ms . 'ms';
@@ -151,6 +157,240 @@ class DateTime
return (string)$time_string;
}
/**
* update timeStringFormat with year and month support
*
* The following flags have to be set to be timeStringFormat compatible.
* Not that on seconds overflow this method will throw an exception, timeStringFormat returned -1s
* show_only_days: true,
* skip_zero: false,
* skip_last_zero: false,
* truncate_nanoseconds: true,
* truncate_zero_seconds_if_microseconds: false
*
* @param int|float $seconds Seconds to convert, maxium 6 decimals,
* else \UnexpectedValueException will be thrown
* if days too large or years too large \LengthException is thrown
* @param string $truncate_after [=''] Truncate after which time name, will not round, hard end
* values are parts names or interval short names (y, d, f, ...)
* if illegal value \UnexpectedValueException is thrown
* @param bool $natural_seperator [=false] use ',' and 'and', if off use space
* @param bool $name_space_seperator [=false] add a space between the number and the time name
* @param bool $show_microseconds [=true] show microseconds
* @param bool $short_time_name [=true] use the short time names (eg s instead of seconds)
* @param bool $skip_last_zero [=true] skip all trailing zero values, eg 5m 0s => 5m
* @param bool $skip_zero [=true] do not show zero values anywhere, eg 1h 0m 20s => 1h 20s
* @param bool $show_only_days [=false] do not show years or months, show only days
* if truncate after is set to year or month
* throws \UnexpectedValueException
* @param bool $auto_fix_microseconds [=false] if the micro seconds decimals are more than 6, round them
* on defaul throw \UnexpectedValueException
* @param bool $truncate_nanoseconds [=false] if microseconds decimals >3 then normal we show 123.4ms
* cut the .4 is set to true
* @param bool $truncate_zero_seconds_if_microseconds [=true] if we have 0.123 seconds then if true no seconds
* will be shown
* @return string
* @throws \UnexpectedValueException if seconds has more than 6 decimals
* if truncate has an illegal value
* if truncate is set to year or month and show_only_days is turned on
* @throws \LengthException if seconds is too large and show_days_only is selected and days is negetive
* or if years is negativ
*/
public static function intervalStringFormat(
int|float $seconds,
string $truncate_after = '',
bool $natural_seperator = false,
bool $name_space_seperator = false,
bool $show_microseconds = true,
bool $short_time_name = true,
bool $skip_last_zero = true,
bool $skip_zero = true,
bool $show_only_days = false,
bool $auto_fix_microseconds = false,
bool $truncate_nanoseconds = false,
bool $truncate_zero_seconds_if_microseconds = true,
): string {
// auto fix long seconds, else \UnexpectedValueException will be thrown on error
// check if we have float and -> round to 6
if ($auto_fix_microseconds === true && is_float($seconds)) {
$seconds = round($seconds, 6);
}
// flag negative + set abs
$negative = $seconds < 0 ? '-' : '';
$seconds = abs($seconds);
// create base time
$date_now = new \DateTime("@0");
try {
$date_seconds = new \DateTime("@$seconds");
} catch (\Exception $e) {
throw new \UnexpectedValueException(
'Seconds value is invalid, too large or more than six decimals: ' . $seconds,
1,
$e
);
}
$interval = date_diff($date_now, $date_seconds);
// if show_only_days and negative but input postive alert that this has to be done in y/m/d ...
if ($interval->y < 0) {
throw new \LengthException('Input seconds value is too large for years output: ' . $seconds, 2);
} elseif ($interval->days < 0 && $show_only_days === true) {
throw new \LengthException('Input seconds value is too large for days output: ' . $seconds, 3);
}
$parts = [
'years' => 'y',
'months' => 'm',
'days' => 'd',
'hours' => 'h',
'minutes' => 'i',
'seconds' => 's',
'microseconds' => 'f',
];
$short_name = [
'years' => 'y', 'months' => 'm', 'days' => 'd',
'hours' => 'h', 'minutes' => 'm', 'seconds' => 's',
'microseconds' => 'ms'
];
// $skip = false;
if (!empty($truncate_after)) {
// if truncate after not in key or value in parts
if (!in_array($truncate_after, array_keys($parts)) && !in_array($truncate_after, array_values($parts))) {
throw new \UnexpectedValueException(
'truncate_after has an invalid value: ' . $truncate_after,
4
);
}
// if truncate after is y or m and we have show_only_days, throw exception
if ($show_only_days === true && in_array($truncate_after, ['y', 'years', 'm', 'months'])) {
throw new \UnexpectedValueException(
'If show_only_days is turned on, the truncate_after cannot be years or months: '
. $truncate_after,
5
);
}
// $skip = true;
}
$formatted = [];
$zero_formatted = [];
$value_set = false;
$add_zero_seconds = false;
foreach ($parts as $time_name => $part) {
if (
// skip for micro seconds
($show_microseconds === false && $part == 'f') ||
// skip for if days only and we have year or month
($show_only_days === true && in_array($part, ['y', 'm']))
) {
continue;
}
$add_value = 0;
if ($show_only_days === true && $part == 'd') {
$value = $interval->days;
} else {
$value = $interval->$part;
}
// print "-> V: $value | $part, $time_name"
// . " | Set: " . ($value_set ? 'Y' : 'N') . ", SkipZ: " . ($skip_zero ? 'Y' : 'N')
// . " | SkipLZ: " . ($skip_last_zero ? 'Y' : 'N')
// . " | " . ($value != 0 ? 'Not zero' : 'ZERO') . "<br>";
if ($value != 0) {
if ($part == 'f') {
if ($truncate_nanoseconds === true) {
$value = round($value, 3);
}
$value *= 1000;
// anything above that is nano seconds?
}
if ($value) {
$value_set = true;
}
$add_value = 1;
} elseif (
$value == 0 &&
$value_set === true && (
$skip_last_zero === false ||
$skip_zero === false
)
) {
$add_value = 2;
}
// echo "ADD VALUE: $add_value<br>";
if ($add_value) {
// build format
$format = "$value";
if ($name_space_seperator) {
$format .= " ";
}
if ($short_time_name) {
$format .= $short_name[$time_name];
} elseif ($value == 1) {
$format .= substr($time_name, 0, -1);
} else {
$format .= $time_name;
}
if ($add_value == 1) {
if (count($zero_formatted) && $skip_zero === false) {
$formatted = array_merge($formatted, $zero_formatted);
}
$zero_formatted = [];
$formatted[] = $format;
} elseif ($add_value == 2) {
$zero_formatted[] = $format;
}
}
// if seconds is zero
if (
$part == 's' && $value == 0 &&
$show_microseconds === true &&
$truncate_zero_seconds_if_microseconds === false
) {
$add_zero_seconds = true;
}
// stop after a truncate is matching
if ($part == $truncate_after || $truncate_after == $time_name) {
break;
}
}
// add all zero entries if we have skip last off
if (count($zero_formatted) && $skip_last_zero === false) {
$formatted = array_merge($formatted, $zero_formatted);
}
// print "=> F: " . print_r($formatted, true)
// . " | Z: " . print_r($zero_list, true)
// . " | ZL: " . print_r($zero_last_list, true)
// . "<br>";
if (count($formatted) == 0) {
// if we have truncate on, then we assume nothing was found
if (!empty($truncate_after)) {
if (in_array($truncate_after, array_values($parts))) {
$truncate_after = array_flip($parts)[$truncate_after];
}
$time_name = $truncate_after;
} else {
$time_name = 'seconds';
}
return '0' . ($name_space_seperator ? ' ' : '')
. ($short_time_name ? $short_name[$time_name] : $time_name);
} elseif (count($formatted) == 1) {
return $negative .
($add_zero_seconds ?
'0'
. ($name_space_seperator ? ' ' : '')
. ($short_time_name ? $short_name['seconds'] : 'seconds')
. ' '
: ''
)
. $formatted[0];
} elseif ($natural_seperator === false) {
return $negative . implode(' ', $formatted);
} else {
$str = implode(', ', array_slice($formatted, 0, -1));
if (!empty($formatted[count($formatted) - 1])) {
$str .= ' and ' . $formatted[count($formatted) - 1];
}
return $negative . $str;
}
}
/**
* does a reverse of the timeStringFormat and converts the string from
* xd xh xm xs xms to a timestamp.microtime format

View File

@@ -37,7 +37,7 @@ class Byte
* BYTE_FORMAT_ADJUST: sprintf adjusted two 2 decimals
* BYTE_FORMAT_SI: use 1000 instead of 1024
* @return string converted byte number (float) with suffix
* @throws \Exception 1: no valid flag set
* @throws \InvalidArgumentException 1: no valid flag set
*/
public static function humanReadableByteFormat(string|int|float $bytes, int $flags = 0): string
{
@@ -63,7 +63,7 @@ class Byte
$si = false;
}
if ($flags > 7) {
throw new \Exception("Invalid flags parameter: $flags", 1);
throw new \InvalidArgumentException("Invalid flags parameter: $flags", 1);
}
// si or normal
@@ -119,7 +119,7 @@ class Byte
* @param int $flags bitwise flag with use space turned on
* BYTE_FORMAT_SI: use 1000 instead of 1024
* @return string|int|float converted value or original value
* @throws \Exception 1: no valid flag set
* @throws \InvalidArgumentException 1: no valid flag set
*/
public static function stringByteFormat(string|int|float $number, int $flags = 0): string|int|float
{
@@ -130,7 +130,7 @@ class Byte
$si = false;
}
if ($flags != 0 && $flags != 4) {
throw new \Exception("Invalid flags parameter: $flags", 1);
throw new \InvalidArgumentException("Invalid flags parameter: $flags", 1);
}
// matches in regex
$matches = [];

View File

@@ -39,13 +39,13 @@ class ArrayIO extends \CoreLibs\DB\IO
{
// main calss variables
/** @var array<mixed> */
public array $table_array; // the array from the table to work on
private array $table_array; // the array from the table to work on
/** @var string */
public string $table_name; // the table_name
private string $table_name; // the table_name
/** @var string */
public string $pk_name = ''; // the primary key from this table
private string $pk_name = ''; // the primary key from this table
/** @var int|string|null */
public int|string|null $pk_id; // the PK id
private int|string|null $pk_id; // the PK id
// security values
/** @var int base acl for current page */
private int $base_acl_level = 0;
@@ -55,7 +55,7 @@ class ArrayIO extends \CoreLibs\DB\IO
* primary key name automatically (from array)
*
* phpcs:ignore
* @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config db connection config
* @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[],db_convert_placeholder?:bool,db_convert_placeholder_target?:string,db_debug_replace_placeholder?:bool} $db_config db connection config
* @param array<mixed> $table_array table array config
* @param string $table_name table name string
* @param \CoreLibs\Logging\Logging $log Logging class
@@ -74,24 +74,21 @@ class ArrayIO extends \CoreLibs\DB\IO
// instance db_io class
parent::__construct($db_config, $log);
// more error vars for this class
$this->error_string['1999'] = 'No table array or table name set';
$this->error_string['1998'] = 'No table name set';
$this->error_string['1999'] = 'No table array set';
$this->error_string['1021'] = 'No Primary Key given';
$this->error_string['1022'] = 'Could not run Array Query';
$this->table_array = $table_array;
$this->table_name = $table_name;
// error abort if no table array or no table name
if (empty($table_array) || empty($table_name)) {
$this->__dbError(1999, false, 'MAJOR ERROR: Core settings missing');
throw new \RuntimeException('MAJOR ERROR: Core settings missing', 1999);
}
$this->setTableArray($table_array);
$this->setTableName($table_name);
// set primary key for given table_array
foreach ($this->table_array as $key => $value) {
if (!empty($value['pk'])) {
$this->pk_name = $key;
if (empty($value['pk'])) {
continue;
}
$this->setPkName($key);
break;
}
$this->dbArrayIOSetAcl($base_acl_level, $acl_admin);
}
@@ -104,6 +101,144 @@ class ArrayIO extends \CoreLibs\DB\IO
parent::__destruct();
}
/**
* Set the overall table array
*
* @param array<mixed> $table_array
* @return void
* @throws \RuntimeException 1999 for empty table array
*/
public function setTableArray(array $table_array): void
{
$this->table_array = $table_array;
if (empty($this->table_array)) {
$this->__dbError(1999, false, 'MAJOR ERROR: Core settings missing: table_arrry');
throw new \RuntimeException('MAJOR ERROR: Core settings missing: table_array', 1999);
}
}
/**
* return full table array, or [] if empty
* of reset is set to true, will reset array first
*
* @param bool $reset [=false] run a reset before returning
* @return array<mixed>
*/
public function getTableArray(bool $reset = false): array
{
if (!$reset) {
return $this->table_array ?? [];
}
$table_array = $this->table_array ?? [];
reset($table_array);
return $table_array;
}
/**
* get a table array entry under the key with element pos
*
* @param string $key
* @param string $pos
* @return mixed
*/
public function getTableArrayEntry(string $key, string $pos): mixed
{
return $this->table_array[$key][$pos] ?? null;
}
/**
* set a new value at key with pos
*
* @param mixed $value
* @param string $key
* @param string $pos
* @return void
*/
public function setTableArrayEntry(mixed $value, string $key, string $pos): void
{
$this->table_array[$key][$pos] = $value;
}
/**
* unset entry at key with pos
*
* @param string $key
* @param string $pos
* @return void
*/
public function unsetTableArrayEntry(string $key, string $pos): void
{
unset($this->table_array[$key][$pos]);
}
/**
* Set table name
*
* @param string $table_name
* @return void
* @throws \RuntimeException 1998 for empty table name
*/
public function setTableName(string $table_name): void
{
$this->table_name = $table_name;
if (empty($this->table_name)) {
$this->__dbError(1998, false, 'MAJOR ERROR: Core settings missing: table_name');
throw new \RuntimeException('MAJOR ERROR: Core settings missing: table_name', 1998);
}
}
/**
* Return table name or empty string if not net
*
* @return string
*/
public function getTableName(): string
{
return $this->table_name ?? '';
}
/**
* Set primary key name
*
* @param string $pk_name
* @return void
*/
public function setPkName(string $pk_name): void
{
$this->pk_name = $pk_name;
}
/**
* get primary key name
*
* @return string
*/
public function getPkName(): string
{
return $this->pk_name;
}
/**
* set primary key id, can be null for not yet set
*
* @param int|string|null $pk_id
* @return void
*/
public function setPkId(int|string|null $pk_id): void
{
$this->pk_id = $pk_id;
}
/**
* return primary key id, or null if not set
*
* @return int|string|null
*/
public function getPkId(): int|string|null
{
return $this->pk_id;
}
/**
* set the base acl level and admin acl flag
* This is needed for table array ACL checks
@@ -198,8 +333,8 @@ class ArrayIO extends \CoreLibs\DB\IO
public function dbCheckPkSet(): bool
{
// if pk_id is set, overrule ...
if (!empty($this->pk_id)) {
$this->table_array[$this->pk_name]['value'] = $this->pk_id;
if (!empty($this->getPkId())) {
$this->table_array[$this->pk_name]['value'] = $this->getPkId();
}
// if not set ... produce error
if (!$this->table_array[$this->pk_name]['value']) {
@@ -287,7 +422,7 @@ class ArrayIO extends \CoreLibs\DB\IO
$q .= ' AND ' . $q_where;
}
// if 0, error
$this->pk_id = null;
$this->setPkId(null);
if (!$this->dbExec($q)) {
$this->__dbError(1022);
}
@@ -374,7 +509,7 @@ class ArrayIO extends \CoreLibs\DB\IO
}
}
// possible dbFetchArray errors ...
$this->pk_id = $this->table_array[$this->pk_name]['value'];
$this->setPkId($this->table_array[$this->pk_name]['value']);
} else {
$this->__dbError(1022);
}
@@ -397,10 +532,6 @@ class ArrayIO extends \CoreLibs\DB\IO
if (count($table_array)) {
$this->table_array = $table_array;
}
// PK ID check
// if ($this->pk_id && !$this->table_array[$this->pk_name]["value"]) {
// $this->table_array[$this->pk_name]["value"]=$this->pk_id;
// }
// checken ob PKs gesetzt, wenn alle -> update, wenn keiner -> insert, wenn ein paar -> ERROR!
if (!$this->table_array[$this->pk_name]['value']) {
$insert = 1;
@@ -624,16 +755,11 @@ class ArrayIO extends \CoreLibs\DB\IO
$q .= ' AND ' . $q_where;
}
// set pk_id ... if it has changed or so
$this->pk_id = $this->table_array[$this->pk_name]['value'];
$this->setPkId($this->table_array[$this->pk_name]['value']);
} else {
$q = 'INSERT INTO ' . $this->table_name . ' ';
$q .= '(' . $q_vars . ') ';
$q .= 'VALUES (' . $q_data . ')';
// write primary key too
// if ($q_data)
// $q .= ", ";
// $q .= $this->pk_name." = ".$this->table_array[$this->pk_name]['value']." ";
// $this->pk_id = $this->table_array[$this->pk_name]['value'];
}
// return success or not
if (!$this->dbExec($q)) {
@@ -646,7 +772,7 @@ class ArrayIO extends \CoreLibs\DB\IO
$insert_id = 0;
}
$this->table_array[$this->pk_name]['value'] = $insert_id;
$this->pk_id = $insert_id;
$this->setPkId($insert_id);
}
// return the table if needed
return $this->table_array;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,220 @@
<?php
/**
* AUTOR: Clemens Schwaighofer
* CREATED: 2023/10/10
* DESCRIPTION:
* Convert placeholders in query from PDO style ? or :named to \PG style $number
* pr the other way around
*/
declare(strict_types=1);
namespace CoreLibs\DB\Support;
class ConvertPlaceholder
{
/**
* Convert PDO type query with placeholders to \PG style and vica versa
* For PDO to: ? and :named
* For \PG to: $number
*
* If the query has a mix of ?, :named or $numbrer the \OutOfRangeException exception
* will be thrown
*
* If the convert_to is either pg or pdo, nothing will be changed
*
* found has -1 if an error occoured in the preg_match_all call
*
* @param string $query Query with placeholders to convert
* @param array<mixed> $params The parameters that are used for the query, and will be updated
* @param string $convert_to Either pdo or pg, will be converted to lower case for check
* @return array{original:array{query:string,params:array<mixed>},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>}
* @throws \OutOfRangeException 200
*/
public static function convertPlaceholderInQuery(
string $query,
array $params,
string $convert_to = 'pg'
): array {
$convert_to = strtolower($convert_to);
$matches = [];
$pattern = '/'
// prefix string part, must match towards
. '(?:\'.*?\')?\s*(?:\?\?|[(=,])\s*'
// match for replace part
. '(?:'
// digit -> ignore
. '\d+|'
// other string -> ignore
. '(?:\'.*?\')|'
// :name named part (PDO)
. '(:\w+)|'
// ? question mark part (PDO)
. '(?:(?:\?\?)?\s*(\?{1}))|'
// $n numbered part (\PG php)
. '(\$[1-9]{1}(?:[0-9]{1,})?)'
// end match
. ')'
// single line -> add line break to matches in "."
. '/s';
// matches:
// 1: :named
// 2: ? question mark
// 3: $n numbered
$found = preg_match_all($pattern, $query, $matches, PREG_UNMATCHED_AS_NULL);
// if false or null set to -1
// || $found === null
if ($found === false) {
$found = -1;
}
/** @var array<string> 1: named */
$named_matches = array_filter($matches[1]);
/** @var array<string> 2: open ? */
$qmark_matches = array_filter($matches[2]);
/** @var array<string> 3: $n matches */
$numbered_matches = array_filter($matches[3]);
// count matches
$count_named = count($named_matches);
$count_qmark = count($qmark_matches);
$count_numbered = count($numbered_matches);
// throw if mixed
if (
($count_named && $count_qmark) ||
($count_named && $count_numbered) ||
($count_qmark && $count_numbered)
) {
throw new \OutOfRangeException('Cannot have named, question mark and numbered in the same query', 200);
}
// rebuild
$matches_return = [];
$type = '';
$query_new = '';
$params_new = [];
$params_lookup = [];
if ($count_named && $convert_to == 'pg') {
$type = 'named';
$matches_return = $named_matches;
// only check for :named
$pattern_replace = '/((?:\'.*?\')?\s*(?:\?\?|[(=,])\s*)(\d+|(?:\'.*?\')|(:\w+))/s';
// 0: full
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part :named
$pos = 0;
$query_new = preg_replace_callback(
$pattern_replace,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params) {
// only count up if $match[3] is not yet in lookup table
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) {
$pos++;
$params_lookup[$matches[3]] = '$' . $pos;
$params_new[] = $params[$matches[3]] ??
throw new \RuntimeException(
'Cannot lookup ' . $matches[3] . ' in params list',
210
);
}
// add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . (
empty($matches[3]) ?
$matches[2] :
$params_lookup[$matches[3]] ??
throw new \RuntimeException(
'Cannot lookup ' . $matches[3] . ' in params lookup list',
211
)
);
},
$query
);
} elseif ($count_qmark && $convert_to == 'pg') {
$type = 'question_mark';
$matches_return = $qmark_matches;
// order and data stays the same
$params_new = $params;
// only check for ?
$pattern_replace = '/((?:\'.*?\')?\s*(?:\?\?|[(=,])\s*)(\d+|(?:\'.*?\')|(?:(?:\?\?)?\s*(\?{1})))/s';
// 0: full
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part ?
$pos = 0;
$query_new = preg_replace_callback(
$pattern_replace,
function ($matches) use (&$pos, &$params_lookup) {
// only count pos up for actual replacements we will do
if (!empty($matches[3])) {
$pos++;
$params_lookup[] = '$' . $pos;
}
// add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . (
empty($matches[3]) ?
$matches[2] :
'$' . $pos
);
},
$query
);
// for each ?:DTN: -> replace with $1 ... $n, any remaining :DTN: remove
} elseif ($count_numbered && $convert_to == 'pdo') {
// convert numbered to named
$type = 'numbered';
$matches_return = $numbered_matches;
// only check for $n
$pattern_replace = '/((?:\'.*?\')?\s*(?:\?\?|[(=,])\s*)(\d+|(?:\'.*?\')|(\$[1-9]{1}(?:[0-9]{1,})?))/s';
// 0: full
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part $numbered
$pos = 0;
$query_new = preg_replace_callback(
$pattern_replace,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params) {
// only count up if $match[3] is not yet in lookup table
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) {
$pos++;
$params_lookup[$matches[3]] = ':' . $pos . '_named';
$params_new[] = $params[($pos - 1)] ??
throw new \RuntimeException(
'Cannot lookup ' . ($pos - 1) . ' in params list',
220
);
}
// add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . (
empty($matches[3]) ?
$matches[2] :
$params_lookup[$matches[3]] ??
throw new \RuntimeException(
'Cannot lookup ' . $matches[3] . ' in params lookup list',
221
)
);
},
$query
);
}
// return, old query is always set
return [
// original
'original' => [
'query' => $query,
'params' => $params,
],
// type found, empty if nothing was done
'type' => $type,
// int: found, not found; -1: problem (set from false)
'found' => (int)$found,
'matches' => $matches_return,
// old to new lookup check
'params_lookup' => $params_lookup,
// new
'query' => $query_new ?? '',
'params' => $params_new,
];
}
}
// __END__

View File

@@ -295,8 +295,7 @@ class Support
* Will start with start_level to skip unwanted from stack
* Defaults to skip level 0 wich is this methid
*
* @param integer $start_level From what level on, as defaul starts with 1
* to exclude self
* @param integer $start_level [=1] From what level on, starts with 1 to exclude self
* @return array<mixed> All method names in list where max is last called
*/
public static function getCallerMethodList(int $start_level = 1): array
@@ -304,15 +303,46 @@ class Support
$traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$methods = [];
foreach ($traces as $level => $data) {
if ($level >= $start_level) {
if (!empty($data['function'])) {
array_unshift($methods, $data['function']);
}
if ($level < $start_level) {
continue;
}
if (!empty($data['function'])) {
array_unshift($methods, $data['function']);
}
}
return $methods;
}
/**
* Get the full call stack from a certain starting level
* The return string is
* file:line:class->method
*
* Note that '::' is used for static calls
*
* @param int $start_level [=1] starts with 1 to exclude itself
* @return array<string> string with file, line, class and method
*/
public static function getCallStack(int $start_level = 1): array
{
$call_stack = [];
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
foreach ($backtrace as $level => $call_trace) {
if ($level < $start_level) {
continue;
}
$call_stack[] =
($call_trace['file'] ?? 'n/f') . ':'
. ($call_trace['line'] ?? '-') . ':'
. (!empty($call_trace['class']) ?
$call_trace['class'] . ($call_trace['type'] ?? '') :
''
)
. $call_trace['function'];
}
return $call_stack;
}
/**
* Get the current class where this function is called
* Is mostly used in debug log statements to get the class where the debug

View File

@@ -17,6 +17,8 @@ class ErrorMessage
{
/** @var array<int,array{id:string,level:string,str:string,target:string,target_style:string,highlight:string[]}> */
private array $error_str = [];
/** @var array<string,array{info:string,level:string}> */
private array $jump_targets = [];
/** @var \CoreLibs\Logging\Logging $log */
public \CoreLibs\Logging\Logging $log;
@@ -27,13 +29,19 @@ class ErrorMessage
* init ErrorMessage
*
* @param \CoreLibs\Logging\Logging $log
* @param bool $log_error [=false]
* @param null|bool $log_error [=null], defaults to false if log is not level debug
*/
public function __construct(
\CoreLibs\Logging\Logging $log,
bool $log_error = false
?bool $log_error = null
) {
$this->log = $log;
// if log default logging is debug then log_error is default set to true
if ($this->log->loggingLevelIsDebug() && $log_error === null) {
$log_error = true;
} else {
$log_error = $log_error ?? false;
}
$this->log_error = $log_error;
}
@@ -42,6 +50,7 @@ class ErrorMessage
* error_id: internal Error ID (should be unique)
* level: error level, can only be ok, info, warn, error, abort, crash
* ok and info are positive response: success
* notice: a debug message for information only
* warn: success, but there might be some things that are not 100% ok
* error: input error or error in executing request
* abort: an internal error happened as mandatory information that normally is
@@ -54,6 +63,8 @@ class ErrorMessage
* highlight is a list of other target points to highlight
* for highlight targets css names are $level without a prefix and should be
* nested in the target element "input .error { ... }"
* jump_target: a target id for to jump and message, is stored in separate jump array
* where the target is unique, first one set is used for info message
* target_style: if not set uses 'error-' $level as css style. applies to targets or main only
*
* @param string $error_id Any internal error ID for this error
@@ -63,6 +74,9 @@ class ErrorMessage
* @param string $target_style Alternate color style for the error message
* @param array<string> $highlight Any additional error data as error OR
* highlight points for field highlights
* @param array{}|array{target:string,info?:string} $jump_target with "target" for where to jump and
* "info" for string to show in jump list
* target must be set, if info not set, default message used
* @param string|null $message If abort/crash, non localized $str
* @param array<mixed> $context Additionl info for abort/crash messages
* @param bool|null $log_error [=null] log level 'error' to error, if null use global,
@@ -75,6 +89,7 @@ class ErrorMessage
string $target = '',
string $target_style = '',
array $highlight = [],
array $jump_target = [],
?string $message = null,
array $context = [],
?bool $log_error = null,
@@ -96,8 +111,16 @@ class ErrorMessage
'target_style' => $target_style,
'highlight' => $highlight,
];
// set a jump target
$this->setJumpTarget($jump_target['target'] ?? null, $jump_target['info'] ?? null, $level);
// write to log for abort/crash
switch ($level) {
case 'notice':
$this->log->notice($message ?? $str, array_merge([
'id' => $error_id,
'level' => $original_level,
], $context));
break;
case 'error':
if ($log_error) {
$this->log->error($message ?? $str, array_merge([
@@ -139,6 +162,9 @@ class ErrorMessage
* @param string $target_style Alternate color style for the error message
* @param array<string> $highlight Any additional error data as error OR
* highlight points for field highlights
* @param array{}|array{target:string,info?:string} $jump_target with "target" for where to jump and
* "info" for string to show in jump list
* target must be set, if info not set, default message used
* @param string|null $message If abort/crash, non localized $str
* @param array<mixed> $context Additionl info for abort/crash messages
* @param bool|null $log_error [=null] log level 'error' to error, if null use global,
@@ -151,6 +177,7 @@ class ErrorMessage
string $target = '',
string $target_style = '',
array $highlight = [],
array $jump_target = [],
?string $message = null,
array $context = [],
?bool $log_error = null,
@@ -162,12 +189,45 @@ class ErrorMessage
$target,
$target_style,
$highlight,
$jump_target,
$message,
$context,
$log_error
);
}
/**
* Set a jump target. This can be used to jump directly a frontend html block
* with the target id set
*
* @param string|null $target
* @param string|null $info
* @param string $level [='error']
* @return void
*/
public function setJumpTarget(
?string $target,
?string $info,
string $level = 'error',
): void {
if (
empty($target) ||
array_key_exists($target, $this->jump_targets)
// !empty($this->jump_targets[$target])
// also check if this is an alphanumeric string? css id compatible?
) {
return;
}
if (empty($info)) {
$info = 'Jump to: ' . $target;
}
$level = MessageLevel::fromName($level)->name;
$this->jump_targets[$target] = [
'info' => $info,
'level' => $level,
];
}
// *********************************************************************
// GETTERS
// *********************************************************************
@@ -210,6 +270,26 @@ class ErrorMessage
];
}
/**
* Return the jump target list
*
* @return array{}|array<int,array{target:string,info:string,level:string}> List of jump targets with info text,
* or empty array if not set
*/
public function getJumpTarget(): array
{
$_jump_target = [];
foreach ($this->jump_targets as $target => $jump) {
$_jump_target[] = array_merge(
$jump,
[
'target' => $target,
]
);
}
return $_jump_target;
}
// *********************************************************************
// FLAG SETTERS
// *********************************************************************

View File

@@ -15,6 +15,7 @@ enum MessageLevel: int
{
case ok = 100;
case info = 200;
case notice = 250;
case warn = 300;
case error = 400;
case abort = 500;
@@ -30,6 +31,7 @@ enum MessageLevel: int
return match (strtolower($name)) {
'ok' => self::ok,
'info' => self::info,
'notice' => self::notice,
'warn', 'warning' => self::warn,
'error' => self::error,
'abort' => self::abort,

File diff suppressed because it is too large Load Diff

View File

@@ -78,7 +78,7 @@ class Image
if (!empty($dummy) && file_exists($filename) && is_file($filename)) {
$return_data = $filename;
} else {
throw new \Exception('Could not set dummy return file: ' . $dummy . ' in ' . $filename);
throw new \RuntimeException('Could not set dummy return file: ' . $dummy . ' in ' . $filename);
}
} else {
$return_data = $dummy;
@@ -470,12 +470,20 @@ class Image
*
* @param string $filename path + filename to rotate. This file must be writeable
* @return void
* @throws \RuntimeException if exit_read_data is not found
* @throws \UnexpectedValueException if file name not writeable or file name not found
*/
public static function correctImageOrientation(string $filename): void
{
// function exists & file is writeable, else do nothing
if (!function_exists('exif_read_data') || !is_writeable($filename)) {
return;
if (!function_exists('exif_read_data')) {
throw new \RuntimeException('Function \'exit_read_data\' does not exist');
}
if (!file_exists($filename) || !is_file($filename)) {
throw new \UnexpectedValueException('Missing image file: ' . $filename);
}
if (!is_writeable($filename)) {
throw new \UnexpectedValueException('File name is not writeable: ' . $filename);
}
[$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: [0, 0, null];
// add @ to avoid "file not supported error"

View File

@@ -66,6 +66,34 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
];
}
/**
* date string convert test
*
* @covers ::dateStringFormat
* @dataProvider timestampProvider
* @testdox dateStringFormat $input (microtime $flag) will be $expected [$_dataName]
*
* @param int|float $input
* @param bool $flag
* @param string $expected
* @return void
*/
public function testDateStringFormat(
$input,
bool $flag_show_micro,
bool $flag_micro_as_float,
string $expected
): void {
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::dateStringFormat(
$input,
$flag_show_micro,
$flag_micro_as_float
)
);
}
/**
* interval for both directions
*
@@ -74,6 +102,11 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
public function intervalProvider(): array
{
return [
'on hour' => [
3600,
false,
'1h 0m 0s'
],
'interval no microtime' => [
1641515890,
false,
@@ -82,7 +115,7 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
'interval with microtime' => [
1641515890,
true,
'18999d 0h 38m 10s',
'18999d 0h 38m 10s 0ms',
],
'micro interval no microtime' => [
1641515890.123456,
@@ -92,7 +125,7 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
'micro interval with microtime' => [
1641515890.123456,
true,
'18999d 0h 38m 10s 1235ms',
'18999d 0h 38m 10s 124ms',
],
'negative interval no microtime' => [
-1641515890,
@@ -103,27 +136,27 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
'microtime only' => [
0.123456,
true,
'0s 1235ms',
'0s 123ms',
],
'seconds only' => [
30.123456,
true,
'30s 1235ms',
'30s 123ms',
],
'minutes only' => [
90.123456,
true,
'1m 30s 1235ms',
'1m 30s 123ms',
],
'hours only' => [
3690.123456,
true,
'1h 1m 30s 1235ms',
'1h 1m 30s 123ms',
],
'days only' => [
90090.123456,
true,
'1d 1h 1m 30s 1235ms',
'1d 1h 1m 30s 123ms',
],
'already set' => [
'1d 1h 1m 30s 1235ms',
@@ -143,6 +176,306 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
];
}
/**
* time seconds convert test
*
* @covers ::timeStringFormat
* @dataProvider intervalProvider
* @testdox timeStringFormat $input (microtime $flag) will be $expected [$_dataName]
*
* @param string|int|float $input
* @param bool $flag
* @param string $expected
* @return void
*/
public function testTimeStringFormat(string|int|float $input, bool $flag, string $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::timeStringFormat($input, $flag)
);
}
/**
* interval seconds convert
*
* @covers ::intervalStringFormat
* @dataProvider intervalProvider
* @testdox intervalStringFormat $input (microtime $show_micro) will be $expected [$_dataName]
*
* @param string|int|float $input
* @param bool $show_micro
* @param string $expected
* @return void
*/
public function testIntervalStringFormat(string|int|float $input, bool $show_micro, string $expected): void
{
// we skip string input, that is not allowed
if (is_string($input)) {
$this->assertTrue(true, 'Skip strings');
return;
}
// invalid values throw exception in default
if ($input == 999999999999999) {
$this->expectException(\LengthException::class);
}
// below is equal to timeStringFormat
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::intervalStringFormat(
$input,
show_microseconds: $show_micro,
show_only_days: true,
skip_zero: false,
skip_last_zero: false,
truncate_nanoseconds: true,
truncate_zero_seconds_if_microseconds: false
)
);
}
/**
* Undocumented function
*
* @return array
*/
public function intervalExtendedProvider(): array
{
return [
// A
'(60) default value' => [
[
'seconds' => 60,
],
'expected' => '1m',
'exception' => null
],
'(60) default value, skip_last_zero:false' => [
[
'seconds' => 60,
'skip_last_zero' => false,
],
'expected' => '1m 0s 0ms',
'exception' => null
],
// B
'(120.1) default value' => [
[
'seconds' => 120.1,
],
'expected' => '2m 100ms',
'exception' => null
],
'(120.1) default value, skip_zero:false' => [
[
'seconds' => 120.1,
'skip_zero' => false,
],
'expected' => '2m 0s 100ms',
'exception' => null
],
'(120.1) default value, skip_last_zero:false' => [
[
'seconds' => 120.1,
'skip_last_zero' => false,
],
'expected' => '2m 100ms',
'exception' => null
],
// C
'(3601) default value' => [
[
'seconds' => 3601,
],
'expected' => '1h 1s',
'exception' => null
],
'(3601) default value, skip_zero:false' => [
[
'seconds' => 3601,
'skip_zero' => false,
],
'expected' => '1h 0m 1s',
'exception' => null
],
'(3601) default value, skip_last_zero:false' => [
[
'seconds' => 3601,
'skip_last_zero' => false,
],
'expected' => '1h 1s 0ms',
'exception' => null
],
// TODO create unit tests for ALL edge cases
// CREATE abort tests, simple, all others are handled in exception tests
'exception: \UnexpectedValueException:1' => [
[
'seconds' => 99999999999999999999999
],
'expected' => null,
'exception' => [
'class' => \UnexpectedValueException::class,
'code' => 1,
],
]
];
}
/**
* test all options for interval conversion
*
* @covers ::intervalStringFormat
* @dataProvider intervalExtendedProvider
* @testdox intervalStringFormat $input will be $expected / $exception [$_dataName]
*
* @param array<string,null|int|float|bool> $parameter_list
* @param string $expected
* @param array<string,mixed> $exception
* @return void
*/
public function testExtendedIntervalStringFormat(
array $parameter_list,
?string $expected,
?array $exception
): void {
if ($expected === null && $exception === null) {
$this->assertFalse(true, 'Cannot have expected and exception null in test data');
}
$parameters = [];
foreach (
[
'seconds' => null,
'truncate_after' => '',
'natural_seperator' => false,
'name_space_seperator' => false,
'show_microseconds' => true,
'short_time_name' => true,
'skip_last_zero' => true,
'skip_zero' => true,
'show_only_days' => false,
'auto_fix_microseconds' => false,
'truncate_nanoseconds' => false,
'truncate_zero_seconds_if_microseconds' => true,
] as $param => $default
) {
if (empty($parameter_list[$param]) && $default === null) {
$this->assertFalse(true, 'Parameter ' . $param . ' is mandatory ');
} elseif (!isset($parameter_list[$param]) || $parameter_list[$param] === null) {
$parameters[] = $default;
} else {
$parameters[] = $parameter_list[$param];
}
}
if ($expected !== null) {
$this->assertEquals(
$expected,
call_user_func_array('CoreLibs\Combined\DateTime::intervalStringFormat', $parameters)
);
} else {
if (empty($exception['class']) || empty($exception['code'])) {
$this->assertFalse(true, 'Exception tests need Exception name and Code');
}
$this->expectException($exception['class']);
$this->expectExceptionCode($exception['code']);
// if we have a message, must be regex
if (!empty($exception['message'])) {
$this->expectExceptionMessageMatches($exception['message']);
}
call_user_func_array('CoreLibs\Combined\DateTime::intervalStringFormat', $parameters);
}
}
/**
* Undocumented function
*
* @return array<mixed>
*/
public function exceptionsIntervalProvider(): array
{
return [
'UnexpectedValueException: 1 A' => [
'seconds' => 99999999999999999999999,
'params' => [],
'exception' => \UnexpectedValueException::class,
'exception_message' => "/^Seconds value is invalid, too large or more than six decimals: /",
'excpetion_code' => 1,
],
'UnexpectedValueException: 1 B' => [
'seconds' => 123.1234567,
'params' => [],
'exception' => \UnexpectedValueException::class,
'exception_message' => "/^Seconds value is invalid, too large or more than six decimals: /",
'excpetion_code' => 1,
],
// exception 2 is very likely covered by exception 1
'LengthException: 3' => [
'seconds' => 999999999999999999,
'params' => [
'show_only_days',
],
'exception' => \LengthException::class,
'exception_message' => "/^Input seconds value is too large for days output: /",
'excpetion_code' => 3,
],
'UnexpectedValueException: 4' => [
'seconds' => 1234567,
'params' => [
'truncate_after'
],
'exception' => \UnexpectedValueException::class,
'exception_message' => "/^truncate_after has an invalid value: /",
'excpetion_code' => 4,
],
'UnexpectedValueException: 5' => [
'seconds' => 1234567,
'params' => [
'show_only_days:truncate_after'
],
'exception' => \UnexpectedValueException::class,
'exception_message' =>
"/^If show_only_days is turned on, the truncate_after cannot be years or months: /",
'excpetion_code' => 5,
]
];
}
/**
* Test all exceptions
*
* @covers ::intervalStringFormat
* @dataProvider exceptionsIntervalProvider
* @testdox intervalStringFormat: test Exceptions
*
* @param int|float $seconds
* @param array<string> $params
* @param string $exception
* @param string $exception_message
* @param int $excpetion_code
* @return void
*/
public function testExceptionsIntervalStringFormat(
int|float $seconds,
array $params,
string $exception,
string $exception_message,
int $excpetion_code,
): void {
$this->expectException($exception);
$this->expectExceptionMessageMatches($exception_message);
$this->expectExceptionCode($excpetion_code);
if (empty($params)) {
\CoreLibs\Combined\DateTime::intervalStringFormat($seconds);
} else {
if (in_array('show_only_days', $params)) {
\CoreLibs\Combined\DateTime::intervalStringFormat($seconds, show_only_days:true);
} elseif (in_array('truncate_after', $params)) {
\CoreLibs\Combined\DateTime::intervalStringFormat($seconds, truncate_after: 'v');
} elseif (in_array('show_only_days:truncate_after', $params)) {
\CoreLibs\Combined\DateTime::intervalStringFormat($seconds, show_only_days:true, truncate_after: 'y');
}
}
}
/**
* Undocumented function
*
@@ -203,6 +536,25 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
];
}
/**
* Undocumented function
*
* @covers ::stringToTime
* @dataProvider reverseIntervalProvider
* @testdox stringToTime $input will be $expected [$_dataName]
*
* @param string|int|float $input
* @param string|int|float $expected
* @return void
*/
public function testStringToTime($input, $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::stringToTime($input)
);
}
/**
* Undocumented function
*
@@ -238,6 +590,25 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
];
}
/**
* Undocumented function
*
* @covers ::checkDate
* @dataProvider dateProvider
* @testdox checkDate $input will be $expected [$_dataName]
*
* @param string $input
* @param bool $expected
* @return void
*/
public function testCheckDate(string $input, bool $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::checkDate($input)
);
}
/**
* Undocumented function
*
@@ -297,6 +668,25 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
];
}
/**
* Undocumented function
*
* @covers ::checkDateTime
* @dataProvider dateTimeProvider
* @testdox checkDateTime $input will be $expected [$_dataName]
*
* @param string $input
* @param bool $expected
* @return void
*/
public function testCheckDateTime(string $input, bool $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::checkDateTime($input)
);
}
/**
* Undocumented function
*
@@ -371,6 +761,37 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
];
}
/**
* Undocumented function
*
* @covers ::compareDate
* @dataProvider dateCompareProvider
* @testdox compareDate $input_a compared to $input_b will be $expected [$_dataName]
*
* @param string $input_a
* @param string $input_b
* @param int|bool $expected
* @param string|null $exception
* @param int|null $exception_code
* @return void
*/
public function testCompareDate(
string $input_a,
string $input_b,
int|bool $expected,
?string $exception,
?int $exception_code
): void {
if ($expected === false) {
$this->expectException($exception);
$this->expectExceptionCode($exception_code);
}
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::compareDate($input_a, $input_b)
);
}
/**
* Undocumented function
*
@@ -466,6 +887,37 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
];
}
/**
* Undocumented function
*
* @covers ::compareDateTime
* @dataProvider dateTimeCompareProvider
* @testdox compareDateTime $input_a compared to $input_b will be $expected [$_dataName]
*
* @param string $input_a
* @param string $input_b
* @param int|bool $expected
* @param string|null $exception
* @param int|null $exception_code
* @return void
*/
public function testCompareDateTime(
string $input_a,
string $input_b,
int|bool $expected,
?string $exception,
?int $exception_code
): void {
if ($expected === false) {
$this->expectException($exception);
$this->expectExceptionCode($exception_code);
}
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::compareDateTime($input_a, $input_b)
);
}
/**
* Undocumented function
*
@@ -520,214 +972,6 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
];
}
/**
* Undocumented function
*
* @return array
*/
public function dateRangeHasWeekendProvider(): array
{
return [
'no weekend' => [
'2023-07-03',
'2023-07-04',
false
],
'start weekend sat' => [
'2023-07-01',
'2023-07-04',
true
],
'start weekend sun' => [
'2023-07-02',
'2023-07-04',
true
],
'end weekend sat' => [
'2023-07-03',
'2023-07-08',
true
],
'end weekend sun' => [
'2023-07-03',
'2023-07-09',
true
],
'long period > 6 days' => [
'2023-07-03',
'2023-07-27',
true
]
];
}
/**
* date string convert test
*
* @covers ::dateStringFormat
* @dataProvider timestampProvider
* @testdox dateStringFormat $input (microtime $flag) will be $expected [$_dataName]
*
* @param int|float $input
* @param bool $flag
* @param string $expected
* @return void
*/
public function testDateStringFormat(
$input,
bool $flag_show_micro,
bool $flag_micro_as_float,
string $expected
): void {
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::dateStringFormat(
$input,
$flag_show_micro,
$flag_micro_as_float
)
);
}
/**
* interval convert test
*
* @covers ::timeStringFormat
* @dataProvider intervalProvider
* @testdox timeStringFormat $input (microtime $flag) will be $expected [$_dataName]
*
* @param int|float $input
* @param bool $flag
* @param string $expected
* @return void
*/
public function testTimeStringFormat($input, bool $flag, string $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::timeStringFormat($input, $flag)
);
}
/**
* Undocumented function
*
* @covers ::stringToTime
* @dataProvider reverseIntervalProvider
* @testdox stringToTime $input will be $expected [$_dataName]
*
* @param string|int|float $input
* @param string|int|float $expected
* @return void
*/
public function testStringToTime($input, $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::stringToTime($input)
);
}
/**
* Undocumented function
*
* @covers ::checkDate
* @dataProvider dateProvider
* @testdox checkDate $input will be $expected [$_dataName]
*
* @param string $input
* @param bool $expected
* @return void
*/
public function testCheckDate(string $input, bool $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::checkDate($input)
);
}
/**
* Undocumented function
*
* @covers ::checkDateTime
* @dataProvider dateTimeProvider
* @testdox checkDateTime $input will be $expected [$_dataName]
*
* @param string $input
* @param bool $expected
* @return void
*/
public function testCheckDateTime(string $input, bool $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::checkDateTime($input)
);
}
/**
* Undocumented function
*
* @covers ::compareDate
* @dataProvider dateCompareProvider
* @testdox compareDate $input_a compared to $input_b will be $expected [$_dataName]
*
* @param string $input_a
* @param string $input_b
* @param int|bool $expected
* @param string|null $exception
* @param int|null $exception_code
* @return void
*/
public function testCompareDate(
string $input_a,
string $input_b,
int|bool $expected,
?string $exception,
?int $exception_code
): void {
if ($expected === false) {
$this->expectException($exception);
$this->expectExceptionCode($exception_code);
}
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::compareDate($input_a, $input_b)
);
}
/**
* Undocumented function
*
* @covers ::compareDateTime
* @dataProvider dateTimeCompareProvider
* @testdox compareDateTime $input_a compared to $input_b will be $expected [$_dataName]
*
* @param string $input_a
* @param string $input_b
* @param int|bool $expected
* @param string|null $exception
* @param int|null $exception_code
* @return void
*/
public function testCompareDateTime(
string $input_a,
string $input_b,
int|bool $expected,
?string $exception,
?int $exception_code
): void {
if ($expected === false) {
$this->expectException($exception);
$this->expectExceptionCode($exception_code);
}
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::compareDateTime($input_a, $input_b)
);
}
/**
* Undocumented function
*
@@ -906,6 +1150,47 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
);
}
/**
* Undocumented function
*
* @return array
*/
public function dateRangeHasWeekendProvider(): array
{
return [
'no weekend' => [
'2023-07-03',
'2023-07-04',
false
],
'start weekend sat' => [
'2023-07-01',
'2023-07-04',
true
],
'start weekend sun' => [
'2023-07-02',
'2023-07-04',
true
],
'end weekend sat' => [
'2023-07-03',
'2023-07-08',
true
],
'end weekend sun' => [
'2023-07-03',
'2023-07-09',
true
],
'long period > 6 days' => [
'2023-07-03',
'2023-07-27',
true
]
];
}
/**
* Undocumented function
*

View File

@@ -253,7 +253,8 @@ final class CoreLibsConvertByteTest extends TestCase
*/
public function testHumanReadableByteFormatException(int $flag): void
{
$this->expectException(\Exception::class);
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(1);
\CoreLibs\Convert\Byte::humanReadableByteFormat(12, $flag);
}
@@ -272,7 +273,8 @@ final class CoreLibsConvertByteTest extends TestCase
*/
public function testStringByteFormatException(int $flag): void
{
$this->expectException(\Exception::class);
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(1);
\CoreLibs\Convert\Byte::stringByteFormat(12, $flag);
}
}

View File

@@ -232,7 +232,7 @@ final class CoreLibsDBIOTest extends TestCase
$this->assertEquals(
$error,
$last_error,
'Assert query warning'
'Assert query error'
);
return [$last_warning, $last_error];
}
@@ -251,8 +251,6 @@ final class CoreLibsDBIOTest extends TestCase
*/
public function testDbVersion(): void
{
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -276,8 +274,6 @@ final class CoreLibsDBIOTest extends TestCase
*/
public function testDbVersionNumeric(): void
{
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -306,8 +302,6 @@ final class CoreLibsDBIOTest extends TestCase
*/
public function testDbVersionInfoParameters(): void
{
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -365,8 +359,6 @@ final class CoreLibsDBIOTest extends TestCase
*/
public function testDbVersionInfo(string $parameter, string $expected): void
{
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -1592,8 +1584,6 @@ final class CoreLibsDBIOTest extends TestCase
string $error,
bool $run_many_times = false
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -1832,8 +1822,6 @@ final class CoreLibsDBIOTest extends TestCase
string $error,
string $insert_data
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -2002,8 +1990,6 @@ final class CoreLibsDBIOTest extends TestCase
string $error,
string $insert_data
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -3069,8 +3055,6 @@ final class CoreLibsDBIOTest extends TestCase
string $error,
string $insert_data
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -3465,7 +3449,7 @@ final class CoreLibsDBIOTest extends TestCase
$read_query,
null,
null,
//
// warning: 20
true, '20', '',
//
'result', '', '',
@@ -3482,6 +3466,31 @@ final class CoreLibsDBIOTest extends TestCase
'returning_id' => false,
],
],
// prepare with different statement name
'prepare query with same statement name, different query' => [
'double_error',
$read_query,
// primary key
null,
// arguments (none)
null,
// expected return false, warning: no, error: 26
false, '', '26',
// return expected, warning, error
'', '', '',
// dummy query for second prepare with wrong query
$read_query . ' WHERE uid = $3',
[],
//
$insert_query,
//
[
'pk_name' => '',
'count' => 0,
'query' => 'SELECT row_int, uid FROM table_with_primary_key',
'returning_id' => false,
],
],
// insert wrong data count compared to needed (execute 23)
'wrong parmeter count' => [
'wrong_param_count',
@@ -3554,8 +3563,6 @@ final class CoreLibsDBIOTest extends TestCase
string $insert_data,
array $prepare_cursor,
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -3575,6 +3582,9 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbPrepare($stm_name, $query) :
$db->dbPrepare($stm_name, $query, $pk_name);
}
if ($error_prepare == '26') {
$prepare_result = $db->dbPrepare($stm_name, $expected_data_query);
}
// if result type, or if forced bool
if (is_string($expected_prepare) && $expected_prepare == 'result') {
// if PHP or newer, must be Object PgSql\Result
@@ -3597,66 +3607,68 @@ final class CoreLibsDBIOTest extends TestCase
// for non fail prepare test exec
// check test result
$execute_result = $query_data === null ?
$db->dbExecute($stm_name) :
$db->dbExecute($stm_name, $query_data);
if ($expected_execute == 'result') {
// if PHP or newer, must be Object PgSql\Result
$this->assertIsObject(
$execute_result
);
// also check that this is correct instance type
$this->assertInstanceOf(
'PgSql\Result',
$execute_result
);
// if this is an select use dbFetchArray to get data and test
} else {
$this->assertEquals(
$expected_execute,
$execute_result
);
}
// error/warning check
$this->subAssertErrorTest($db, $warning_execute, $error_execute);
// now check test result if expected return is result
if (
$expected_execute == 'result' &&
!empty($expected_data_query)
) {
// $expected_data_query
// $expected_data
$rows = $db->dbReturnArray($expected_data_query);
$this->assertEquals(
$expected_data,
$rows
);
}
if (
$expected_execute == 'result' &&
$execute_result !== false &&
empty($expected_data_query) &&
count($expected_data)
) {
// compare previously read data to compare data
$compare_data = [];
// read in the query data
while (is_array($row = $db->dbFetchArray($execute_result, true))) {
$compare_data[] = $row;
if (!$error_prepare) {
$execute_result = $query_data === null ?
$db->dbExecute($stm_name) :
$db->dbExecute($stm_name, $query_data);
if ($expected_execute == 'result') {
// if PHP or newer, must be Object PgSql\Result
$this->assertIsObject(
$execute_result
);
// also check that this is correct instance type
$this->assertInstanceOf(
'PgSql\Result',
$execute_result
);
// if this is an select use dbFetchArray to get data and test
} else {
$this->assertEquals(
$expected_execute,
$execute_result
);
}
// error/warning check
$this->subAssertErrorTest($db, $warning_execute, $error_execute);
// now check test result if expected return is result
if (
$expected_execute == 'result' &&
!empty($expected_data_query)
) {
// $expected_data_query
// $expected_data
$rows = $db->dbReturnArray($expected_data_query);
$this->assertEquals(
$expected_data,
$rows
);
}
if (
$expected_execute == 'result' &&
$execute_result !== false &&
empty($expected_data_query) &&
count($expected_data)
) {
// compare previously read data to compare data
$compare_data = [];
// read in the query data
while (is_array($row = $db->dbFetchArray($execute_result, true))) {
$compare_data[] = $row;
}
$this->assertEquals(
$expected_data,
$compare_data
);
}
$this->assertEquals(
$expected_data,
$compare_data
);
}
// check dbGetPrepareCursorValue
foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) {
$this->assertEquals(
$prepare_cursor[$key],
$db->dbGetPrepareCursorValue($stm_name, $key),
'Prepared cursor: ' . $key . ': failed assertion'
);
// check dbGetPrepareCursorValue
foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) {
$this->assertEquals(
$prepare_cursor[$key],
$db->dbGetPrepareCursorValue($stm_name, $key),
'Prepared cursor: ' . $key . ': failed assertion'
);
}
}
// reset all data
@@ -3844,8 +3856,6 @@ final class CoreLibsDBIOTest extends TestCase
string $expected_get_var,
string $expected_get_db
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config[$connection],
self::$log
@@ -3907,9 +3917,13 @@ final class CoreLibsDBIOTest extends TestCase
'id' => '51',
'error' => 'Max query call needs to be set to at least 1',
// run:: can be +1 if called in set and not direct
'source' => "/^include::main::run::run::run::run::run::run::(run::)?runBare::runTest::testDbErrorHandling::dbSetMaxQueryCall$/",
// 'main::run::run::run::run::run::run::run::runBare::runTest::testDbErrorHandling::dbSetMaxQueryCall
'source' => "/^(include::)?main::(run::)+runBare::runTest::testDbErrorHandling::dbSetMaxQueryCall$/",
'pg_error' => '',
'msg' => '',
'message' => '',
'context' => [
'max_calls' => 0
]
]
],
'trigger warning' => [
@@ -3942,8 +3956,6 @@ final class CoreLibsDBIOTest extends TestCase
string $error_id,
array $expected_history
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -3969,7 +3981,7 @@ final class CoreLibsDBIOTest extends TestCase
foreach ($expected_history as $key => $value) {
// check if starts with / because this is regex (timestamp)
// if (substr($expected_2, 0, 1) == '/) {
if (strpos($value, '/') === 0) {
if (!is_array($value) && strpos($value, '/') === 0) {
// this is regex
$this->assertMatchesRegularExpression(
$value,
@@ -4057,8 +4069,6 @@ final class CoreLibsDBIOTest extends TestCase
bool $expected_set_flag,
string $expected_get_encoding
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config[$connection],
self::$log
@@ -4140,8 +4150,6 @@ final class CoreLibsDBIOTest extends TestCase
?string $encoding_php,
string $text
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config[$connection],
self::$log
@@ -4271,8 +4279,6 @@ final class CoreLibsDBIOTest extends TestCase
string $table,
string $primary_key
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -4329,7 +4335,7 @@ final class CoreLibsDBIOTest extends TestCase
// NOTE if there are different INSERTS before the primary keys
// will not match anymore. Must be updated by hand
// IMPORTANT: if this is stand alone the primary key will not match and fail
$table_with_primary_key_id = 68;
$table_with_primary_key_id = 70;
// 0: query + returning
// 1: params
// 1: pk name for db exec
@@ -4529,8 +4535,6 @@ final class CoreLibsDBIOTest extends TestCase
array|string|int|null $expected_ret_ext,
array $expected_ret_arr
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -4874,8 +4878,6 @@ final class CoreLibsDBIOTest extends TestCase
array $expected_col_names,
array $expected_col_types
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
@@ -5029,6 +5031,147 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbClose();
}
// query placeholder convert
public function queryPlaceholderReplaceProvider(): array
{
// WHERE row_varchar = $1
return [
'select, no change' => [
'query' => <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
SQL,
'params' => [],
'found' => 0,
'expected_query' => '',
'expected_params' => [],
],
'select, params ?' => [
'query' => <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = ?
SQL,
'params' => ['string a'],
'found' => 1,
'expected_query' => <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = $1
SQL,
'expected_params' => ['string a'],
],
'select, params :' => [
'query' => <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = :row_varchar
SQL,
'params' => [':row_varchar' => 'string a'],
'found' => 1,
'expected_query' => <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = $1
SQL,
'expected_params' => ['string a'],
]
];
}
/**
* test query string with placeholders convert
*
* @dataProvider queryPlaceholderReplaceProvider
* @testdox Query replacement test [$_dataName]
*
* @param string $query
* @param array $params
* @param string $expected_query
* @param array $expected_params
* @return void
*/
public function testQueryPlaceholderReplace(
string $query,
array $params,
int $expected_found,
string $expected_query,
array $expected_params
): void {
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
);
$db->dbSetConvertPlaceholder(true);
//
if ($db->dbCheckQueryForSelect($query)) {
$res = $db->dbReturnRowParams($query, $params);
$converted = $db->dbGetPlaceholderConverted();
} else {
$db->dbExecParams($query, $params);
$converted = $db->dbGetPlaceholderConverted();
}
$this->assertEquals(
$expected_found,
$converted['found'],
'Found not equal'
);
$this->assertEquals(
$expected_query,
$converted['query'],
'Query not equal'
);
$this->assertEquals(
$expected_params,
$converted['params'],
'Params not equal'
);
}
/**
* test exception for placeholder convert
* -> internally converted to error
*
* @testdox Query Replace error tests
*
* @return void
*/
public function testQueryPlaceholderReplaceException(): void
{
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
);
$db->dbSetConvertPlaceholder(true);
$db->dbExecParams(
<<<SQL
SELECT foo FROM bar
WHERE a = ? and b = :bname
SQL,
['a', 'b']
);
$this->assertEquals(
200,
$db->dbGetLastError()
);
// catch unset, for :names
$db->dbExecParams(
<<<SQL
SELECT foo FROM bar
WHERE a = :aname and b = :bname
SQL,
[':foo' => 'a', ':bname' => 'b']
);
$this->assertEquals(
210,
$db->dbGetLastError()
);
// TODO: other way around for to pdo
}
// TODO implement below checks
// - complex write sets
// dbWriteData, dbWriteDataExt
@@ -5157,8 +5300,6 @@ final class CoreLibsDBIOTest extends TestCase
string $warning_final,
string $error_final
): void {
// self::$log->setLogLevelAll('debug', true);
// self::$log->setLogLevelAll('print', true);
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log

View File

@@ -460,8 +460,8 @@ final class CoreLibsDebugSupportTest extends TestCase
* Undocumented function
*
* @cover ::getCallerFileLine
* @testWith ["vendor/phpunit/phpunit/src/Framework/TestCase.php:"]
* @testdox getCallerFileLine check based on regex /[\w\-\/]/vendor/phpunit/phpunit/src/Framework/TestCase.php:\d+ [$_dataName]
* @testWith ["vendor/phpunit/phpunit/src/Framework/TestCase.php:6434","phar:///home/clemens/.phive/phars/phpunit-9.6.13.phar/phpunit/Framework/TestCase.php:6434"]
* @testdox getCallerFileLine check based on regex .../Framework/TestCase.php:\d+ [$_dataName]
*
* @param string $expected
* @return void
@@ -469,7 +469,14 @@ final class CoreLibsDebugSupportTest extends TestCase
public function testGetCallerFileLine(): void
{
// regex prefix with path "/../" and then fixed vendor + \d+
$regex = "/^\/[\w\-\/]+\/vendor\/phpunit\/phpunit\/src\/Framework\/TestCase.php:\d+$/";
// or phar start if phiev installed
// phar:///home/clemens/.phive/phars/phpunit-9.6.13.phar/phpunit/Framework/TestCase.php
$regex = "/^("
. "\/.*\/vendor\/phpunit\/phpunit\/src"
. "|"
. "phar:\/\/\/.*\.phive\/phars\/phpunit-\d+\.\d+\.\d+\.phar\/phpunit"
. ")"
. "\/Framework\/TestCase.php:\d+$/";
$this->assertMatchesRegularExpression(
$regex,
Support::getCallerFileLine()
@@ -506,7 +513,7 @@ final class CoreLibsDebugSupportTest extends TestCase
public function testGetCallerMethodList(array $expected): void
{
$compare = Support::getCallerMethodList();
// 10: legact
// 10: legacy
// 11: direct
// 12: full call
switch (count($compare)) {
@@ -514,27 +521,30 @@ final class CoreLibsDebugSupportTest extends TestCase
// add nothing
$this->assertEquals(
$expected,
Support::getCallerMethodList(),
$compare,
'assert expected 10'
);
break;
case 11:
// add one "run" before "runBare"
// array_splice(
// $expected,
// 7,
// 0,
// ['run']
// );
array_splice(
$expected,
0,
0,
['include']
);
if ($compare[0] == 'include') {
// add include at first
array_splice(
$expected,
0,
0,
['include']
);
} else {
array_splice(
$expected,
6,
0,
['run']
);
}
$this->assertEquals(
$expected,
Support::getCallerMethodList(),
$compare,
'assert expected 11'
);
break;
@@ -554,13 +564,38 @@ final class CoreLibsDebugSupportTest extends TestCase
);
$this->assertEquals(
$expected,
Support::getCallerMethodList(),
$compare,
'assert expected 12'
);
break;
}
}
/**
* Undocumented function
*
* @cover ::getCallStack
* @testdox getCallStack check if it returns data [$_dataName]
*
* @return void
*/
public function testGetCallStack(): void
{
$call_stack = Support::getCallStack();
// print "Get CALL: " . print_r(Support::getCallStack(), true) . "\n";
if ($call_stack < 8) {
$this->assertFalse(true, 'getCallStack too low: 8');
} else {
$this->assertTrue(true, 'getCallSteck ok');
}
// just test top entry
$first = array_shift($call_stack);
$this->assertStringEndsWith(
':tests\CoreLibsDebugSupportTest->testGetCallStack',
$first,
);
}
/**
* test the lowest one (one above base)
*

View File

@@ -99,7 +99,7 @@ final class CoreLibsGetSystemTest extends TestCase
1 => 'phpunit',
2 => 'phpunit',
// NOTE: this can change, so it is a regex check
3 => "/^(\/?.*\/?)?vendor\/bin\/phpunit$/",
3 => "/^(\/?.*\/?)?(vendor\/bin|tools)\/phpunit$/",
],
'some path with extension' => [
0 => '/some/path/to/file.txt',

View File

@@ -44,6 +44,11 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
'str' => 'INFO',
'expected' => 'info',
],
'notice' => [
'level' => 'notice',
'str' => 'NOTICE',
'expected' => 'notice',
],
'warn' => [
'level' => 'warn',
'str' => 'WARN',
@@ -91,9 +96,9 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
public function testErrorMessageLevelOk(string $level, string $str, string $expected): void
{
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testErrorMessages',
'log_file_id' => 'testErrorMessagesLevelOk',
'log_folder' => self::LOG_FOLDER,
'log_level' => Level::Debug,
'log_level' => Level::Error,
]);
$em = new \CoreLibs\Logging\ErrorMessage($log);
$em->setMessage(
@@ -123,9 +128,9 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
public function testErrorMessageOk(): void
{
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testErrorMessages',
'log_file_id' => 'testErrorMessagesOk',
'log_folder' => self::LOG_FOLDER,
'log_level' => Level::Debug
'log_level' => Level::Error
]);
$em = new \CoreLibs\Logging\ErrorMessage($log);
$em->setErrorMsg(
@@ -238,6 +243,22 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
'log_error' => true,
'expected' => '<ERROR> OTHER ERROR MESSAGE',
],
'notice' => [
'id' => '100',
'level' => 'notice',
'str' => 'NOTICE MESSAGE',
'message' => null,
'log_error' => null,
'expected' => '<NOTICE> NOTICE MESSAGE',
],
'notice, message' => [
'id' => '100',
'level' => 'notice',
'str' => 'NOTICE MESSAGE',
'message' => 'OTHER NOTICE MESSAGE',
'log_error' => null,
'expected' => '<NOTICE> OTHER NOTICE MESSAGE',
],
'crash' => [
'id' => '300',
'level' => 'crash',
@@ -293,7 +314,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
* Undocumented function
*
* @dataProvider providerErrorMessageLog
* @testdox Test Log writing [$_dataName]
* @testdox Test Log writing with log level Error [$_dataName]
*
* @param string $id
* @param string $level
@@ -303,7 +324,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
* @param string $expected
* @return void
*/
public function testErrorMessageLog(
public function testErrorMessageLogErrorLevel(
string $id,
string $level,
string $str,
@@ -312,7 +333,63 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
string $expected
): void {
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testErrorMessages',
'log_file_id' => 'testErrorMessagesLogError',
'log_folder' => self::LOG_FOLDER,
'log_level' => Level::Notice,
'log_per_run' => true
]);
$em = new \CoreLibs\Logging\ErrorMessage($log);
$em->setErrorMsg(
$id,
$level,
$str,
message: $message,
log_error: $log_error
);
$file_content = '';
if (is_file($log->getLogFolder() . $log->getLogFile())) {
$file_content = file_get_contents(
$log->getLogFolder() . $log->getLogFile()
) ?: '';
}
// if error, if null or false, it will not be logged
if ($level == 'error' && ($log_error === null || $log_error === false)) {
$this->assertStringNotContainsString(
$expected,
$file_content
);
} else {
$this->assertStringContainsString(
$expected,
$file_content
);
}
}
/**
* Undocumented function
*
* @dataProvider providerErrorMessageLog
* @testdox Test Log writing with log Level Debug [$_dataName]
*
* @param string $id
* @param string $level
* @param string $str
* @param string|null $message
* @param bool|null $log_error
* @param string $expected
* @return void
*/
public function testErrorMessageLogErrorDebug(
string $id,
string $level,
string $str,
?string $message,
?bool $log_error,
string $expected
): void {
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testErrorMessagesLogDebug',
'log_folder' => self::LOG_FOLDER,
'log_level' => Level::Debug,
'log_per_run' => true
@@ -331,8 +408,8 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
$log->getLogFolder() . $log->getLogFile()
) ?: '';
}
// if n
if ($level == 'error' && ($log_error === null || $log_error === false)) {
// if error, and log is debug level, only explicit false are not logged
if ($level == 'error' && $log_error === false) {
$this->assertStringNotContainsString(
$expected,
$file_content
@@ -344,6 +421,84 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
);
}
}
/**
* Undocumented function
*
* @testdox Test jump target set and reporting
*
* @return void
*/
public function testJumpTarget(): void
{
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testErrorMessagesLogDebug',
'log_folder' => self::LOG_FOLDER,
'log_level' => Level::Debug,
'log_per_run' => true
]);
$em = new \CoreLibs\Logging\ErrorMessage($log);
$em->setJumpTarget(
'target-f',
'Target text'
);
$this->assertEquals(
[
['target' => 'target-f', 'info' => 'Target text', 'level' => 'error']
],
$em->getJumpTarget()
);
// set same target, keep as before
$em->setJumpTarget(
'target-f',
'Other text'
);
$this->assertEquals(
[
['target' => 'target-f', 'info' => 'Target text', 'level' => 'error']
],
$em->getJumpTarget()
);
// add new now two messages
$em->setJumpTarget(
'target-s',
'More text'
);
$this->assertEquals(
[
['target' => 'target-f', 'info' => 'Target text', 'level' => 'error'],
['target' => 'target-s', 'info' => 'More text', 'level' => 'error'],
],
$em->getJumpTarget()
);
// add empty info
$em->setJumpTarget(
'target-e',
''
);
$this->assertEquals(
[
['target' => 'target-f', 'info' => 'Target text', 'level' => 'error'],
['target' => 'target-s', 'info' => 'More text', 'level' => 'error'],
['target' => 'target-e', 'info' => 'Jump to: target-e', 'level' => 'error'],
],
$em->getJumpTarget()
);
// add through message
$em->setErrorMsg('E-101', 'abort', 'Abort message', jump_target:[
'target' => 'abort-target',
'info' => 'Abort error'
]);
$this->assertEquals(
[
['target' => 'target-f', 'info' => 'Target text', 'level' => 'error'],
['target' => 'target-s', 'info' => 'More text', 'level' => 'error'],
['target' => 'target-e', 'info' => 'Jump to: target-e', 'level' => 'error'],
['target' => 'abort-target', 'info' => 'Abort error', 'level' => 'abort'],
],
$em->getJumpTarget()
);
}
}
// __END__

View File

@@ -20,7 +20,7 @@ final class CoreLibsLoggingLoggingTest extends TestCase
private const LOG_FOLDER = __DIR__ . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR;
private const REGEX_BASE = "\[[\d\-\s\.:]+\]\s{1}" // date
. "\[[\w\.]+(:\d+)?\]\s{1}" // host:port
. "\[[\w\-\.\/]+:\d+\]\s{1}" // folder/file
. "\[(phar:\/\/)?[\w\-\.\/]+:\d+\]\s{1}" // folder/file [note phar:// is for phpunit]
. "\[\w+\]\s{1}" // run id
. "{[\w\\\\]+((::|->)\w+)?}\s{1}"; // class

View File

@@ -16,17 +16,89 @@ final class CoreLibsOutputImageTest extends TestCase
/**
* Undocumented function
*
* @testdox Output\Image Class tests
* @covers ::createThumbnail
* @testdox createThumbnail checks
*
* @return void
*/
public function testOutputImage()
public function testCreateThumbnail(): void
{
// $this->assertTrue(true, 'Output Image Tests not implemented');
$this->markTestIncomplete(
'Output\Image Tests have not yet been implemented'
// CONVERT does not exist
$this->expectException(\RuntimeException::class);
\CoreLibs\Output\Image::createThumbnail('do_not_exist.png', 200, 200);
// set convert
$paths = [
'/bin',
'/usr/bin',
'/usr/local/bin',
];
// find convert
foreach ($paths as $path) {
if (
file_exists($path . DIRECTORY_SEPARATOR . 'convert') &&
is_file($path . DIRECTORY_SEPARATOR . 'convert')
) {
// image magick convert location
define('CONVERT', $path . DIRECTORY_SEPARATOR . 'convert');
break;
}
}
unset($paths);
// cannot set dummy file
$this->expectException(\Exception::class);
\CoreLibs\Output\Image::createThumbnail('do_not_exist.png', 200, 200);
}
/**
* Undocumented function
*
* @covers ::createThumbnailSimple
* @testdox createThumbnailSimple checks
*
* @return void
*/
public function testCreateThumbnailSimple(): void
{
// file does not exist
$this->expectException(\UnexpectedValueException::class);
\CoreLibs\Output\Image::createThumbnailSimple(
'do_not_exist.png',
200,
200,
cache_folder: '/tmp/',
web_folder: '/tmp/'
);
// $this->markTestSkipped('No implementation for Output\Image at the moment');
// cache folder is not dir
$this->expectException(\UnexpectedValueException::class);
\CoreLibs\Output\Image::createThumbnailSimple(
'do_not_exist.png',
200,
200,
cache_folder: '/foo/bar/',
web_folder: '/tmp/'
);
// target cache folder is not writeable
// RuntimeException: imagecreatetruecolor failed
// RuntimeException: imagecolorallocatealpha failed
}
/**
* Undocumented function
*
* @covers ::correctImageOrientation
* @testdox correctImageOrientation checks
*
* @return void
*/
public function testCorrectImageOrientation(): void
{
// test file does not exist
$this->expectException(\UnexpectedValueException::class);
\CoreLibs\Output\Image::correctImageOrientation('do_not_exist.png');
// test folder not writeable
// test exit_read_data not present (how)?
// test image rotate
}
}

View File

@@ -0,0 +1,15 @@
<?php
$set = 0;
foreach (['/../../www', '/../www', '/../..', '/..', '/../../src', '/../src'] as $src) {
if (is_file(dirname(__DIR__) . $src . '/vendor/autoload.php')) {
require dirname(__DIR__) . $src . '/vendor/autoload.php';
$set = 1;
break;
}
}
if (!$set) {
die("Cannot find /vendor/autoload.php in reference to: " . dirname(__DIR__));
}
// __END__

1
tools/phan Symbolic link
View File

@@ -0,0 +1 @@
/home/clemens/.phive/phars/phan-5.4.2.phar

1
tools/phpcbf Symbolic link
View File

@@ -0,0 +1 @@
/home/clemens/.phive/phars/phpcbf-3.7.2.phar

1
tools/phpcs Symbolic link
View File

@@ -0,0 +1 @@
/home/clemens/.phive/phars/phpcs-3.7.2.phar

1
tools/phpstan Symbolic link
View File

@@ -0,0 +1 @@
/home/clemens/.phive/phars/phpstan-1.10.37.phar

1
tools/phpunit Symbolic link
View File

@@ -0,0 +1 @@
/home/clemens/.phive/phars/phpunit-9.6.13.phar

1
tools/psalm Symbolic link
View File

@@ -0,0 +1 @@
/home/clemens/.phive/phars/psalm-5.15.0.phar