Compare commits

..

46 Commits

Author SHA1 Message Date
Clemens Schwaighofer
2b079ff836 DB\IO: add missing debug query, clean up not needed code
in dbReturn with params on not matching param the system exited on fail
without printing the query making it hard to find where the error is.
Added debug output in case the params count is not matching.
Same move in the dbExecute call

removed param count check from dbReturnRow/dbReturnArray as this check
is done in the dbExecParams call anyway
2023-04-11 10:58:38 +09:00
Clemens Schwaighofer
37201799b5 DB\IO params debug output fix for dbReturn/dbReturnParams calls
Those two calls did not replace the params with values for debug output
2023-04-10 17:20:53 +09:00
Clemens Schwaighofer
b9d8911c7b ACL\Login
load and export the additional acl json arrays for
* user: USER_ADDITIONAL_ACL
* group: GROUP_ADDITIONAL_ACL
* access: array element 'additional_acl'

also added to the master acl array:
'additional_acl' => ['user' => [], 'group' => []]
'unit_detail' => [] => ['additional_acl' => []]
2023-04-10 14:32:32 +09:00
Clemens Schwaighofer
c51ceb926e Bug fix for DB\IO params detection
Param detection found too many params, for example '$1'.
Fixed the regex to only allow params that are no preceeded by '
And must start with space/tab, =, (
2023-04-07 14:34:13 +09:00
Clemens Schwaighofer
b4b33d6873 Bug fix for DB\IO returning detection
it was still coded with old one line and not taking in possible
line breaks in the returning code part
2023-04-03 15:02:39 +09:00
Clemens Schwaighofer
959240b0fa Fixes in db class tests files 2023-03-29 09:59:30 +09:00
Clemens Schwaighofer
7eace1013e DB\IO switch dbReturn, dbReturnParams to NO_CACHE as default
Cache is never used, so to keep memory default lower, lets switch to
NO_CACHE
2023-03-29 09:55:09 +09:00
Clemens Schwaighofer
be1e55cad7 Add more DB tests with params methods 2023-03-28 17:01:02 +09:00
Clemens Schwaighofer
11a8c6440b Update DB\IO debug output for parameter queries
if value null set "NULL" else convert to string

Update class basic test with various type tests
2023-03-28 16:46:34 +09:00
Clemens Schwaighofer
742cbc31df DB\IO add dbExec*, dbReturn* Params methods
Instead of prepare/execute, add the proper quary_params calls for all
the dbExec*, dbReturn* calls

Also add field types to cursor info
2023-03-28 15:31:07 +09:00
Clemens Schwaighofer
28909fdc03 composer base packages updates 2023-03-28 11:51:54 +09:00
Clemens Schwaighofer
c3b29ad0d7 comment formatting update only 2023-03-27 16:25:11 +09:00
Clemens Schwaighofer
6d481657df class test file update for DB with ANY calls 2023-03-16 18:21:48 +09:00
Clemens Schwaighofer
fc57aabf5d Updates in SmartyExtend set var calls
Removed cms object from Frontend and replaced with optional smarty data
array (HEADER, DATA, DEBUG_DATA)
Updated admin call that if $cms is given above data will be extracted.
Added a CONTENT_PATH option for admin, must be set if $cms is set
Is used for the adbTopMenu call
Moved the $cms global check and trigger to the admin call branch only
2023-03-13 11:29:21 +09:00
Clemens Schwaighofer
d56ee68482 Fix missing default null in setSmartyVarsFrontend 2023-03-13 10:36:05 +09:00
Clemens Schwaighofer
b89ab09e12 SmartyExtend parameter phpdoc order fix 2023-03-13 09:25:50 +09:00
Clemens Schwaighofer
e873ade6c0 Change the SmartyExtended Vars set calls to use options array
Instead of having each parameter single, group them into an options array
so we do not have to worry about updating the whole function call.

Keep the main core call as is
2023-03-13 09:15:59 +09:00
Clemens Schwaighofer
5910b884ac Bug fix in SmartyExtend class and user name set 2023-03-10 15:25:54 +09:00
Clemens Schwaighofer
e3bd2c1c3b Remove more _SESSION calls in classes, test updates
Admin\EditBase now has ACL\Login class as mandatory class parameter
Output\Form\Generate has loginAcl array parameter as mandatory
2023-03-10 15:08:56 +09:00
Clemens Schwaighofer
90a8c5540f Deprecate the GetLocale class and replace with ACL\Login locales set
If ACL\Login is used, the loginGetLocale() call should be used to fetch
the locale data set during login

Language\L10n now has new encoding/charset optional parameter 4
If the locale does not have a charset set the defautl charset is UTF-8
unless set via the new parameter or setOverrideEncoding()

Because of the deprecation of the getLocales call the following methods
do no longer need $locales
- Admin\Backend
- Admin\EditBase
- Output\Form\Generate
- Template\SmartyExtend

They all use the new l10n->getLocaleAsArray() call to set all the local
variables

* Admin\EditBase has new parameter "options" to set
- cache_id
- compile_id
which must be passed on the SmartyExtend class init
2023-03-10 13:43:43 +09:00
Clemens Schwaighofer
ea503fffe9 Merge branch 'development' 2023-03-09 16:45:07 +09:00
Clemens Schwaighofer
feba79a2e8 Install psalm as dev, sync scripts updates 2023-03-09 16:27:10 +09:00
Clemens Schwaighofer
6bec59e387 Deprecate check for GetDotEnv tests 2023-03-09 16:17:52 +09:00
Clemens Schwaighofer
03fbcaecfb Code Clean up, more testing
Remove unused code and classes.
Clean up code to remove all named constant from them and throw
deprecation alerts if used.
Add basic psalm setup in root folder and remove from www folder
2023-03-09 15:55:57 +09:00
Clemens Schwaighofer
283e7de1dc Remove Jason class, now in Json, deprecated since v7 2023-03-02 15:35:13 +09:00
Clemens Schwaighofer
d952c5f774 Remove Jason class, now in Json, deprecated since v7 2023-03-02 15:26:40 +09:00
Clemens Schwaighofer
cd8351d761 Update composer package create readme 2023-03-02 11:51:39 +09:00
Clemens Schwaighofer
b992901072 Move all tests into sub folders for a more clear structure 2023-03-02 11:51:29 +09:00
Clemens Schwaighofer
1596654149 Moved minimum php version to 8.1
All PostgreSQL calls are now Connection/Resource object types and not
resource
All methods have parameter type set
2023-02-28 17:36:19 +09:00
Clemens Schwaighofer
44f37b7f74 Fix in EditUser Table Array query load for languages
Also split all queries into multi line ones
Fixes in Form\Generate for TableArray Interface location move
Update EditBase to new and old edit schema (scheme) file name
2023-02-28 10:31:34 +09:00
Clemens Schwaighofer
829f5c567f Update composer autoload map files 2023-02-28 06:40:19 +09:00
Clemens Schwaighofer
710a48abcd Move the TableArrays Interface to a sub folder in TableArrays
to avoid strange path lookups
2023-02-28 06:36:11 +09:00
Clemens Schwaighofer
f564c27319 Add readme file for composer package deploy flow 2023-02-24 16:44:21 +09:00
Clemens Schwaighofer
00b98e7230 phpunit test scritps fix 2023-02-24 09:45:52 +09:00
Clemens Schwaighofer
7cae3e701a phpunit test fixes 2023-02-24 09:43:24 +09:00
Clemens Schwaighofer
da67d1bde3 phpstan update 2023-02-22 06:47:34 +09:00
Clemens Schwaighofer
16c3653cee phpstan updates for Smarty move to composer package 2023-02-22 06:42:53 +09:00
Clemens Schwaighofer
47c4c5cb69 Install smarty from composer package 2023-02-22 06:36:34 +09:00
Clemens Schwaighofer
7b9dc9c8b2 Rename the VarSetType class to SetVarType
The old name made no sense and it was only used in two projects, so the
deprecation step has been skipped
2023-02-22 06:32:58 +09:00
Clemens Schwaighofer
6133da9069 phpunit test must be installed in www/ folder
Because www folder is source base, if phpunit is installed outside it
cannot find the classes
2023-02-22 06:31:50 +09:00
Clemens Schwaighofer
fa0b102d1a Fix test file for Form\Generate
There is no global variable load anymore

Override table arrays have to be set on load
2023-02-17 13:19:59 +09:00
Clemens Schwaighofer
0e99700bbe Composer vendor name change 2023-02-16 13:04:20 +09:00
Clemens Schwaighofer
2f0b9fb360 Move Fonts folder from lib to includes 2023-02-16 12:32:15 +09:00
Clemens Schwaighofer
c7cc3c2938 Move all dev tools from www composer to outside master composer 2023-02-13 16:36:51 +09:00
Clemens Schwaighofer
f508b607a6 Add new class for auto set vars to certain types or null
Convert\VarSetType for always return type expected (eg string will be string)
or Convert\VarSetTypeNull to return string or null on failure

The return value for failure if not matching type can be set for both, but
only for Convert\VarSetTypeNull the return value can be set to null.
2023-02-08 12:12:46 +09:00
Clemens Schwaighofer
f94b350ba4 Install phan/phpstan local 2023-02-08 12:02:18 +09:00
3836 changed files with 599548 additions and 6399 deletions

View File

@@ -27,6 +27,7 @@ use Phan\Config;
return [
// "target_php_version" => "8.2",
"minimum_target_php_version" => "8.1",
// turn color on (-C)
"color_issue_messages_if_supported" => true,
// If true, missing properties will be created when
@@ -95,8 +96,6 @@ return [
"exclude_analysis_directory_list" => [
'www/vendor',
'www/tests',
'www/lib/Smarty',
'www/lib/smarty-4.3.0',
'www/templates_c',
'www/log',
'www/tmp',
@@ -117,10 +116,6 @@ return [
// ignore the old qq tests
'www/admin/qq_file_upload_front.php',
'www/admin/qq_file_upload_ajax.php',
// symlink ignore
'www/lib/smarty-4.3.0/libs/Smarty.class.php',
// legacy edit base (until removal)
'www/includes/edit_base.LEGACY.php'
],
// what not to show as problem

View File

@@ -1,9 +1,11 @@
#!/bin/env bash
base="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/";
# -c phpunit.xml
# --testdox
# call with "t" to give verbose testdox output
# SUPPORTED: https://www.php.net/supported-versions.php
# call with 7.4, 8.0, 8.1 to force a certain php version
# call with php version number to force a certain php version
opt_testdox="";
if [ "${1}" = "t" ] || [ "${2}" = "t" ]; then
@@ -13,8 +15,8 @@ php_bin="";
if [ ! -z "${1}" ]; then
case "${1}" in
# "7.3") php_bin="/usr/bin/php7.3 "; ;;
"7.4") php_bin="/usr/bin/php7.4 "; ;;
"8.0") php_bin="/usr/bin/php8.0 "; ;;
# "7.4") php_bin="/usr/bin/php7.4 "; ;;
# "8.0") php_bin="/usr/bin/php8.0 "; ;;
"8.1") php_bin="/usr/bin/php8.1 "; ;;
"8.2") php_bin="/usr/bin/php8.2 "; ;;
*) echo "Not support PHP: ${1}"; exit; ;;
@@ -23,8 +25,8 @@ fi;
if [ ! -z "${2}" ] && [ -z "${php_bin}" ]; then
case "${2}" in
# "7.3") php_bin="/usr/bin/php7.3 "; ;;
"7.4") php_bin="/usr/bin/php7.4 "; ;;
"8.0") php_bin="/usr/bin/php8.0 "; ;;
# "7.4") php_bin="/usr/bin/php7.4 "; ;;
# "8.0") php_bin="/usr/bin/php8.0 "; ;;
"8.1") php_bin="/usr/bin/php8.1 "; ;;
"8.2") php_bin="/usr/bin/php8.2 "; ;;
*) echo "Not support PHP: ${1}"; exit; ;;

View File

@@ -0,0 +1,22 @@
#!/bin/env bash
# syncs
# 4dev/tests/
# www/lib/CoreLibs/
#
# to the composer corelibs all repo
GO="${1}";
DRY_RUN="";
if [ "${GO}" != "go" ]; then
DRY_RUN="-n ";
fi;
BASE="/storage/var/www/html/developers/clemens/core_data/";
SOURCE="${BASE}php_libraries/trunk/"
TARGET="${BASE}composer-packages/CoreLibs-Composer-All/"
rsync ${DRY_RUN}-Plzvrupt --stats --delete ${SOURCE}4dev/tests/ ${TARGET}test/phpunit/
rsync ${DRY_RUN}-Plzvrupt --stats --delete ${SOURCE}www/lib/CoreLibs/ ${TARGET}src/
# __END__

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# create path
path=$(pwd)"/"$0;
@@ -10,6 +10,11 @@ TARGET_HOST_WEB="<user>@<host>";
TMP_DIR=$LOCAL_BASE_DIR"/4dev/tmp/";
tmpf_web=$TMP_DIR"sync.exclude.tmp";
if [ ! -d "$LOCAL_BASE_DIR" ]; then
echo "Folder: $LOCAL_BASE_DIR not found";
exit;
fi;
# if vendor be sure group folder is +x
chmod -R ug+rX ${LOCAL_DIR}/vender/

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
/**
* Test base setup
* @testdox AAASetupData\AAASetupDataTest just setup BASE
*/
final class CoreLibsAAASetupDataTest extends TestCase
{
/**
* Covers nothing
*
* @testdox Just setup BASE
*
* @return void
*/
public function testSetupData(): void
{
if (!defined('BASE')) {
define(
'BASE',
str_replace('/configs', '', __DIR__)
. DIRECTORY_SEPARATOR
);
}
$this->assertEquals(
str_replace('/configs', '', __DIR__)
. DIRECTORY_SEPARATOR,
BASE,
'BASE Path set check'
);
}
}
// __END__

View File

@@ -0,0 +1 @@
../Language/includes/

1
4dev/tests/AAASetupData/log Symbolic link
View File

@@ -0,0 +1 @@
../Debug/log/

View File

@@ -7,6 +7,14 @@ namespace tests;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
/*
Not yet covered tests:
- loginGetLocale
- loginGetHeaderColor
- loginGetPages
- loginGetEuid
*/
/**
* Test class for ACL\Login
* @coversDefaultClass \CoreLibs\ACL\Login
@@ -120,8 +128,6 @@ final class CoreLibsACLLoginTest extends TestCase
// define('LOGIN_DB_SCHEMA', '');
// SHOULD SET
// PASSWORD_MIN_LENGTH (d9)
// PASSWORD_MAX_LENGTH (d255)
// DEFAULT_ACL_LEVEL (d80)
// OPT:
@@ -261,6 +267,8 @@ final class CoreLibsACLLoginTest extends TestCase
'GROUP_ACL_LEVEL' => -1,
'PAGES_ACL_LEVEL' => [],
'USER_ACL_LEVEL' => -1,
'USER_ADDITIONAL_ACL' => [],
'GROUP_ADDITIONAL_ACL' => [],
'UNIT_UID' => [
'AdminAccess' => 1,
],
@@ -274,6 +282,7 @@ final class CoreLibsACLLoginTest extends TestCase
'data' => [
'test' => 'value',
],
'additional_acl' => []
],
],
// 'UNIT_DEFAULT' => '',
@@ -1106,7 +1115,22 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\ACL\Login&MockObject */
$login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class)
->setConstructorArgs([self::$db, self::$log, $session_mock, false])
->setConstructorArgs([
self::$db,
self::$log,
$session_mock,
[
'auto_login' => false,
'default_acl_level' => 80,
'logout_target' => '',
'site_locale' => 'en_US.UTF-8',
'site_domain' => 'admin',
'site_encoding' => 'UTF-8',
'locale_path' => __DIR__ . DIRECTORY_SEPARATOR
. 'includes' . DIRECTORY_SEPARATOR
. 'locale' . DIRECTORY_SEPARATOR,
]
])
->onlyMethods(['loginTerminate', 'loginReadPageName', 'loginPrintLogin'])
->getMock();
$login_mock->expects($this->any())
@@ -1729,7 +1753,7 @@ final class CoreLibsACLLoginTest extends TestCase
],
20
],
'invalud search' => [
'invalid search' => [
12,
'foo',
[],
@@ -1774,7 +1798,22 @@ final class CoreLibsACLLoginTest extends TestCase
);
/** @var \CoreLibs\ACL\Login&MockObject */
$login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class)
->setConstructorArgs([self::$db, self::$log, $session_mock, false])
->setConstructorArgs([
self::$db,
self::$log,
$session_mock,
[
'auto_login' => false,
'default_acl_level' => 80,
'logout_target' => '',
'site_locale' => 'en_US.UTF-8',
'site_domain' => 'admin',
'site_encoding' => 'UTF-8',
'locale_path' => __DIR__ . DIRECTORY_SEPARATOR
. 'includes' . DIRECTORY_SEPARATOR
. 'locale' . DIRECTORY_SEPARATOR,
]
])
->onlyMethods(['loginTerminate'])
->getMock();
$login_mock->expects($this->any())
@@ -1873,7 +1912,22 @@ final class CoreLibsACLLoginTest extends TestCase
);
/** @var \CoreLibs\ACL\Login&MockObject */
$login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class)
->setConstructorArgs([self::$db, self::$log, $session_mock, false])
->setConstructorArgs([
self::$db,
self::$log,
$session_mock,
[
'auto_login' => false,
'default_acl_level' => 80,
'logout_target' => '',
'site_locale' => 'en_US.UTF-8',
'site_domain' => 'admin',
'site_encoding' => 'UTF-8',
'locale_path' => __DIR__ . DIRECTORY_SEPARATOR
. 'includes' . DIRECTORY_SEPARATOR
. 'locale' . DIRECTORY_SEPARATOR,
]
])
->onlyMethods(['loginTerminate'])
->getMock();
$login_mock->expects($this->any())
@@ -1946,7 +2000,22 @@ final class CoreLibsACLLoginTest extends TestCase
);
/** @var \CoreLibs\ACL\Login&MockObject */
$login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class)
->setConstructorArgs([self::$db, self::$log, $session_mock, false])
->setConstructorArgs([
self::$db,
self::$log,
$session_mock,
[
'auto_login' => false,
'default_acl_level' => 80,
'logout_target' => '',
'site_locale' => 'en_US.UTF-8',
'site_domain' => 'admin',
'site_encoding' => 'UTF-8',
'locale_path' => __DIR__ . DIRECTORY_SEPARATOR
. 'includes' . DIRECTORY_SEPARATOR
. 'locale' . DIRECTORY_SEPARATOR,
]
])
->onlyMethods(['loginTerminate'])
->getMock();
$login_mock->expects($this->any())
@@ -2027,7 +2096,22 @@ final class CoreLibsACLLoginTest extends TestCase
);
/** @var \CoreLibs\ACL\Login&MockObject */
$login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class)
->setConstructorArgs([self::$db, self::$log, $session_mock, false])
->setConstructorArgs([
self::$db,
self::$log,
$session_mock,
[
'auto_login' => false,
'default_acl_level' => 80,
'logout_target' => '',
'site_locale' => 'en_US.UTF-8',
'site_domain' => 'admin',
'site_encoding' => 'UTF-8',
'locale_path' => __DIR__ . DIRECTORY_SEPARATOR
. 'includes' . DIRECTORY_SEPARATOR
. 'locale' . DIRECTORY_SEPARATOR,
]
])
->onlyMethods(['loginTerminate'])
->getMock();
$login_mock->expects($this->any())

1
4dev/tests/ACL/includes Symbolic link
View File

@@ -0,0 +1 @@
../AAASetupData/includes

View File

@@ -107,6 +107,13 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase
*/
public function arraySearchRecursiveAllProvider(): array
{
/*
0: $needle,
1: array $input,
2: ?string $key_search_for,
3: bool $flag,
4: array $expected
*/
return [
'find value' => [
0 => 'bar',
@@ -172,6 +179,13 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase
*/
public function arraySearchSimpleProvider(): array
{
/*
0: array $input,
1: $key,
2: $value,
3: bool $flag,
4: bool $expected
*/
return [
'key/value exist' => [
0 => self::$array,
@@ -665,7 +679,7 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase
*
* @param array $input
* @param string|int $key
* @param string|int $value
* @param string|int|bool $value
* @param bool $expected
* @return void
*/
@@ -712,12 +726,23 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase
*/
public function testArrayMergeRecursiveWarningA(): void
{
set_error_handler(
static function (int $errno, string $errstr): never {
throw new Exception($errstr, $errno);
},
E_USER_WARNING
);
$arrays = func_get_args();
// first is expected warning
$warning = array_shift($arrays);
$this->expectWarning();
$this->expectWarningMessage($warning);
// phpunit 10.0 compatible
$this->expectExceptionMessage($warning);
\CoreLibs\Combined\ArrayHandler::arrayMergeRecursive(...$arrays);
restore_error_handler();
}
/**

View File

@@ -0,0 +1,652 @@
<?php // phpcs:disable Generic.Files.LineLength
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Convert\SetVarTypeNull;
/**
* Test class for Convert\Strings
* @coversDefaultClass \CoreLibs\Convert\SetVarTypeNull
* @testdox \CoreLibs\Convert\SetVarTypeNull method tests
*/
final class CoreLibsConvertSetVarTypeNullTest extends TestCase
{
/**
* Undocumented function
*
* @return array
*/
public function varSetTypeStringProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'empty string' => [
'',
null,
''
],
'filled string' => [
'string',
null,
'string'
],
'valid string, override set' => [
'string',
'override',
'string'
],
'int, no override' => [
1,
null,
null
],
'int, override set' => [
1,
'not int',
'not int'
]
];
}
/**
* Undocumented function
* @covers ::setStr
* @dataProvider varSetTypeStringProvider
* @testdox setStr $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param string|null $default
* @param string|null $expected
* @return void
*/
public function testSetString(mixed $input, ?string $default, ?string $expected): void
{
$set_var = SetVarTypeNull::setStr($input, $default);
if ($expected !== null) {
$this->assertIsString($set_var);
} else {
$this->assertNull($set_var);
}
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varMakeTypeStringProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'empty string' => [
'',
null,
''
],
'filled string' => [
'string',
null,
'string'
],
'valid string, override set' => [
'string',
'override',
'string'
],
'int, no override' => [
1,
null,
'1'
],
'int, override set' => [
1,
'not int',
'1'
],
'float, no override' => [
1.5,
null,
'1.5'
],
// all the strange things here
'function, override set' => [
$foo = function () {
return '';
},
'function',
'function'
],
'function, no override' => [
$foo = function () {
return '';
},
null,
null
],
'hex value, override set' => [
0x55,
'hex',
'85'
]
];
}
/**
* Undocumented function
* @covers ::makeStr
* @dataProvider varMakeTypeStringProvider
* @testdox makeStr $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param string|null $default
* @param string|null $expected
* @return void
*/
public function testMakeString(mixed $input, ?string $default, ?string $expected): void
{
$set_var = SetVarTypeNull::makeStr($input, $default);
if ($expected !== null) {
$this->assertIsString($set_var);
} else {
$this->assertNull($set_var);
}
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varSetTypeIntProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'int' => [
1,
null,
1
],
'int, override set' => [
1,
-1,
1
],
'string, no override' => [
'string',
null,
null
],
'string, override' => [
'string',
-1,
-1
],
'float' => [
1.5,
null,
null
]
];
}
/**
* Undocumented function
* @covers ::setInt
* @dataProvider varSetTypeIntProvider
* @testdox setInt $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param int|null $default
* @param int|null $expected
* @return void
*/
public function testSetInt(mixed $input, ?int $default, ?int $expected): void
{
$set_var = SetVarTypeNull::setInt($input, $default);
if ($expected !== null) {
$this->assertIsInt($set_var);
} else {
$this->assertNull($set_var);
}
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varMakeTypeIntProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'int' => [
1,
null,
1
],
'int, override set' => [
1,
-1,
1
],
'string, no override' => [
'string',
null,
0
],
'string, override' => [
'string',
-1,
0
],
'float' => [
1.5,
null,
1
],
// all the strange things here
'function, override set' => [
$foo = function () {
return '';
},
-1,
-1
],
'function, no override ' => [
$foo = function () {
return '';
},
null,
null
],
'hex value, override set' => [
0x55,
-1,
85
],
];
}
/**
* Undocumented function
* @covers ::makeInt
* @dataProvider varMakeTypeIntProvider
* @testdox makeInt $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param int|null $default
* @param int|null $expected
* @return void
*/
public function testMakeInt(mixed $input, ?int $default, ?int $expected): void
{
$set_var = SetVarTypeNull::makeInt($input, $default);
if ($expected !== null) {
$this->assertIsInt($set_var);
} else {
$this->assertNull($set_var);
}
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varSetTypeFloatProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'float' => [
1.5,
null,
1.5
],
'float, override set' => [
1.5,
-1.5,
1.5
],
'string, no override' => [
'string',
null,
null
],
'string, override' => [
'string',
1.5,
1.5
],
'int' => [
1,
null,
null
]
];
}
/**
* Undocumented function
* @covers ::setFloat
* @dataProvider varSetTypeFloatProvider
* @testdox setFloat $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param float|null $default
* @param float|null $expected
* @return void
*/
public function testSetFloat(mixed $input, ?float $default, ?float $expected): void
{
$set_var = SetVarTypeNull::setFloat($input, $default);
if ($expected !== null) {
$this->assertIsFloat($set_var);
} else {
$this->assertNull($set_var);
}
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varMakeTypeFloatProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'float' => [
1.5,
null,
1.5
],
'float, override set' => [
1.5,
-1.5,
1.5
],
'string, no override' => [
'string',
null,
0.0
],
'string, override' => [
'string',
1.5,
0.0
],
'int' => [
1,
null,
1.0
],
// all the strange things here
'function, override set' => [
$foo = function () {
return '';
},
-1.0,
-1.0
],
// all the strange things here
'function, no override' => [
$foo = function () {
return '';
},
null,
null
],
'hex value, override set' => [
0x55,
-1,
85.0
],
];
}
/**
* Undocumented function
* @covers ::makeFloat
* @dataProvider varMakeTypeFloatProvider
* @testdox makeFloat $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param float|null $default
* @param float|null $expected
* @return void
*/
public function testMakeFloat(mixed $input, ?float $default, ?float $expected): void
{
$set_var = SetVarTypeNull::makeFloat($input, $default);
if ($expected !== null) {
$this->assertIsFloat($set_var);
} else {
$this->assertNull($set_var);
}
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varSetTypeArrayProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'array, empty' => [
[],
null,
[]
],
'array, filled' => [
['array'],
null,
['array']
],
'string, no override' => [
'string',
null,
null
],
'string, override' => [
'string',
['string'],
['string']
]
];
}
/**
* Undocumented function
* @covers ::setArray
* @dataProvider varSetTypeArrayProvider
* @testdox setArray $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param array|null $default
* @param array|null $expected
* @return void
*/
public function testSetArray(mixed $input, ?array $default, ?array $expected): void
{
$set_var = SetVarTypeNull::setArray($input, $default);
if ($expected !== null) {
$this->assertIsArray($set_var);
} else {
$this->assertNull($set_var);
}
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varSetTypeBoolProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'bool true' => [
true,
null,
true
],
'bool false' => [
false,
null,
false
],
'string, no override' => [
'string',
null,
null
],
'string, override' => [
'string',
true,
true
]
];
}
/**
* Undocumented function
* @covers ::setBool
* @dataProvider varSetTypeBoolProvider
* @testdox setBool $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param bool|null $default
* @param bool|null $expected
* @return void
*/
public function testSetBool(mixed $input, ?bool $default, ?bool $expected): void
{
$set_var = SetVarTypeNull::setBool($input, $default);
if ($expected !== null) {
$this->assertIsBool($set_var);
} else {
$this->assertNull($set_var);
}
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varMakeTypeBoolProvider(): array
{
// 0: input
// 2: expected
return [
'true' => [
true,
true
],
'false' => [
false,
false
],
'string on' => [
'on',
true
],
'string off' => [
'off',
false
],
'invalid string' => [
'sulzenbacher',
null,
],
'invalid string, override' => [
'sulzenbacher',
null,
],
'array to default' => [
[],
false
],
];
}
/**
* Undocumented function
* @covers ::setBool
* @dataProvider varMakeTypeBoolProvider
* @testdox setBool $input will be $expected [$_dataName]
*
* @param mixed $input
* @param bool|null $default
* @param bool|null $expected
* @return void
*/
public function testMakeBool(mixed $input, ?bool $expected): void
{
$set_var = SetVarTypeNull::makeBool($input);
if ($expected !== null) {
$this->assertIsBool($set_var);
} else {
$this->assertNull($set_var);
}
$this->assertEquals(
$expected,
$set_var
);
}
}
// __END__

View File

@@ -0,0 +1,632 @@
<?php // phpcs:disable Generic.Files.LineLength
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Convert\SetVarType;
/**
* Test class for Convert\Strings
* @coversDefaultClass \CoreLibs\Convert\SetVarType
* @testdox \CoreLibs\Convert\SetVarType method tests
*/
final class CoreLibsConvertSetVarTypeTest extends TestCase
{
/**
* Undocumented function
*
* @return array
*/
public function varSetTypeStringProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'empty string' => [
'',
null,
''
],
'filled string' => [
'string',
null,
'string'
],
'valid string, override set' => [
'string',
'override',
'string'
],
'int, no override' => [
1,
null,
''
],
'int, override set' => [
1,
'not int',
'not int'
]
];
}
/**
* Undocumented function
* @covers ::setStr
* @dataProvider varSetTypeStringProvider
* @testdox setStr $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param string|null $default
* @param string $expected
* @return void
*/
public function testSetString(mixed $input, ?string $default, string $expected): void
{
if ($default === null) {
$set_var = SetVarType::setStr($input);
} else {
$set_var = SetVarType::setStr($input, $default);
}
$this->assertIsString($set_var);
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varMakeTypeStringProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'empty string' => [
'',
null,
''
],
'filled string' => [
'string',
null,
'string'
],
'valid string, override set' => [
'string',
'override',
'string'
],
'int, no override' => [
1,
null,
'1'
],
'int, override set' => [
1,
'not int',
'1'
],
// all the strange things here
'function, override set' => [
$foo = function () {
return '';
},
'function',
'function'
],
'hex value, override set' => [
0x55,
'hex',
'85'
]
];
}
/**
* Undocumented function
* @covers ::makeStr
* @dataProvider varMakeTypeStringProvider
* @testdox makeStr $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param string|null $default
* @param string $expected
* @return void
*/
public function testMakeString(mixed $input, ?string $default, string $expected): void
{
if ($default === null) {
$set_var = SetVarType::makeStr($input);
} else {
$set_var = SetVarType::makeStr($input, $default);
}
$this->assertIsString($set_var);
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varSetTypeIntProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'int' => [
1,
null,
1
],
'int, override set' => [
1,
-1,
1
],
'string, no override' => [
'string',
null,
0
],
'string, override' => [
'string',
-1,
-1
],
'float' => [
1.5,
null,
0
]
];
}
/**
* Undocumented function
* @covers ::setInt
* @dataProvider varSetTypeIntProvider
* @testdox setInt $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param int|null $default
* @param int $expected
* @return void
*/
public function testSetInt(mixed $input, ?int $default, int $expected): void
{
if ($default === null) {
$set_var = SetVarType::setInt($input);
} else {
$set_var = SetVarType::setInt($input, $default);
}
$this->assertIsInt($set_var);
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varMakeTypeIntProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'int' => [
1,
null,
1
],
'int, override set' => [
1,
-1,
1
],
'string, no override' => [
'string',
null,
0
],
'string, override' => [
'string',
-1,
0
],
'float' => [
1.5,
null,
1
],
// all the strange things here
'function, override set' => [
$foo = function () {
return '';
},
-1,
-1
],
'hex value, override set' => [
0x55,
-1,
85
],
];
}
/**
* Undocumented function
* @covers ::makeInt
* @dataProvider varMakeTypeIntProvider
* @testdox makeInt $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param int|null $default
* @param int $expected
* @return void
*/
public function testMakeInt(mixed $input, ?int $default, int $expected): void
{
if ($default === null) {
$set_var = SetVarType::makeInt($input);
} else {
$set_var = SetVarType::makeInt($input, $default);
}
$this->assertIsInt($set_var);
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varSetTypeFloatProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'float' => [
1.5,
null,
1.5
],
'float, override set' => [
1.5,
-1.5,
1.5
],
'string, no override' => [
'string',
null,
0.0
],
'string, override' => [
'string',
1.5,
1.5
],
'int' => [
1,
null,
0.0
]
];
}
/**
* Undocumented function
* @covers ::setFloat
* @dataProvider varSetTypeFloatProvider
* @testdox setFloat $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param float|null $default
* @param float $expected
* @return void
*/
public function testSetFloat(mixed $input, ?float $default, float $expected): void
{
if ($default === null) {
$set_var = SetVarType::setFloat($input);
} else {
$set_var = SetVarType::setFloat($input, $default);
}
$this->assertIsFloat($set_var);
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varMakeTypeFloatProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'float' => [
1.5,
null,
1.5
],
'float, override set' => [
1.5,
-1.5,
1.5
],
'string, no override' => [
'string',
null,
0.0
],
'string, override' => [
'string',
1.5,
0.0
],
'int' => [
1,
null,
1.0
],
// all the strange things here
'function, override set' => [
$foo = function () {
return '';
},
-1.0,
-1.0
],
'hex value, override set' => [
0x55,
-1,
85.0
],
];
}
/**
* Undocumented function
* @covers ::makeFloat
* @dataProvider varMakeTypeFloatProvider
* @testdox makeFloat $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param float|null $default
* @param float $expected
* @return void
*/
public function testMakeFloat(mixed $input, ?float $default, float $expected): void
{
if ($default === null) {
$set_var = SetVarType::makeFloat($input);
} else {
$set_var = SetVarType::makeFloat($input, $default);
}
$this->assertIsFloat($set_var);
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varSetTypeArrayProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'array, empty' => [
[],
null,
[]
],
'array, filled' => [
['array'],
null,
['array']
],
'string, no override' => [
'string',
null,
[]
],
'string, override' => [
'string',
['string'],
['string']
]
];
}
/**
* Undocumented function
* @covers ::setArray
* @dataProvider varSetTypeArrayProvider
* @testdox setArray $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param array|null $default
* @param array $expected
* @return void
*/
public function testSetArray(mixed $input, ?array $default, array $expected): void
{
if ($default === null) {
$set_var = SetVarType::setArray($input);
} else {
$set_var = SetVarType::setArray($input, $default);
}
$this->assertIsArray($set_var);
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varSetTypeBoolProvider(): array
{
// 0: input
// 1: default (null default)
// 2: expected
return [
'bool true' => [
true,
null,
true
],
'bool false' => [
false,
null,
false
],
'string, no override' => [
'string',
null,
false
],
'string, override' => [
'string',
true,
true
]
];
}
/**
* Undocumented function
* @covers ::setBool
* @dataProvider varSetTypeBoolProvider
* @testdox setBool $input with override $default will be $expected [$_dataName]
*
* @param mixed $input
* @param bool|null $default
* @param bool $expected
* @return void
*/
public function testSetBool(mixed $input, ?bool $default, bool $expected): void
{
if ($default === null) {
$set_var = SetVarType::setBool($input);
} else {
$set_var = SetVarType::setBool($input, $default);
}
$this->assertIsBool($set_var);
$this->assertEquals(
$expected,
$set_var
);
}
/**
* Undocumented function
*
* @return array
*/
public function varMakeTypeBoolProvider(): array
{
// 0: input
// 2: expected
return [
'true' => [
true,
null,
true
],
'false' => [
false,
null,
false
],
'string on' => [
'on',
null,
true
],
'string off' => [
'off',
null,
false
],
'invalid string' => [
'sulzenbacher',
null,
false,
],
'invalid string, override' => [
'sulzenbacher',
true,
true,
],
'array to default' => [
[],
null,
false
],
];
}
/**
* Undocumented function
* @covers ::setBool
* @dataProvider varMakeTypeBoolProvider
* @testdox setBool $input will be $expected [$_dataName]
*
* @param mixed $input
* @param bool|null $default
* @param bool $expected
* @return void
*/
public function testMakeBool(mixed $input, ?bool $default, bool $expected): void
{
if ($default === null) {
$set_var = SetVarType::makeBool($input);
} else {
$set_var = SetVarType::makeBool($input, $default);
}
$this->assertIsBool($set_var);
$this->assertEquals(
$expected,
$set_var
);
}
}
// __END__

View File

@@ -1,310 +0,0 @@
<?php // phpcs:disable Generic.Files.LineLength
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
/**
* Test class for Language\GetLocale
*
* @coversDefaultClass \CoreLibs\Language\GetLocale
* @testdox \CoreLibs\Language\GetLocale method tests
*/
final class CoreLibsLanguageGetLocaleTest extends TestCase
{
/**
* set all constant variables that must be set before call
*
* @return void
*/
public static function setUpBeforeClass(): void
{
// default web page encoding setting
if (!defined('DEFAULT_ENCODING')) {
define('DEFAULT_ENCODING', 'UTF-8');
}
if (!defined('DEFAULT_LOCALE')) {
// default lang + encoding
define('DEFAULT_LOCALE', 'en_US.UTF-8');
}
// site
if (!defined('SITE_ENCODING')) {
define('SITE_ENCODING', DEFAULT_ENCODING);
}
if (!defined('SITE_LOCALE')) {
define('SITE_LOCALE', DEFAULT_LOCALE);
}
// just set
if (!defined('BASE')) {
define('BASE', str_replace('/configs', '', __DIR__) . DIRECTORY_SEPARATOR);
}
if (!defined('INCLUDES')) {
define('INCLUDES', 'includes' . DIRECTORY_SEPARATOR);
}
if (!defined('LANG')) {
define('LANG', 'lang' . DIRECTORY_SEPARATOR);
}
if (!defined('LOCALE')) {
define('LOCALE', 'locale' . DIRECTORY_SEPARATOR);
}
if (!defined('CONTENT_PATH')) {
define('CONTENT_PATH', 'frontend' . DIRECTORY_SEPARATOR);
}
// array session
$_SESSION = [];
global $_SESSION;
}
/**
* all the test data
*
* @return array
*/
public function setLocaleProvider(): array
{
return [
// 0: locale
// 1: domain
// 2: encoding
// 3: path
// 4: SESSION: DEFAULT_LOCALE
// 5: SESSION: DEFAULT_CHARSET
// 6: expected array
'no params, all default constants' => [
// lang, domain, encoding, path
null, null, null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'en_US.UTF-8',
'lang' => 'en_US',
'domain' => 'frontend',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
'no params, session charset and lang' => [
// lang, domain, encoding, path
null, null, null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
'ja_JP', 'UTF-8',
// return array
[
'locale' => 'ja_JP',
'lang' => 'ja_JP',
'domain' => 'frontend',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
'no params, session charset and lang short' => [
// lang, domain, encoding, path
null, null, null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
'ja', 'UTF-8',
// return array
[
'locale' => 'ja',
'lang' => 'ja',
'domain' => 'frontend',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
// param lang (no sessions)
'locale param only, no sessions' => [
// lang, domain, encoding, path
'ja.UTF-8', null, null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja.UTF-8',
'lang' => 'ja',
'domain' => 'frontend',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
// different locale setting
'locale complex param only, no sessions' => [
// lang, domain, encoding, path
'ja_JP.SJIS', null, null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja_JP.SJIS',
'lang' => 'ja_JP',
'domain' => 'frontend',
'encoding' => 'SJIS',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
// param lang and domain (no override)
'locale, domain params, no sessions' => [
// lang, domain, encoding, path
'ja.UTF-8', 'admin', null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja.UTF-8',
'lang' => 'ja',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
// param lang and domain (no override)
'locale, domain, encoding params, no sessions' => [
// lang, domain, encoding, path
'ja.UTF-8', 'admin', 'UTF-8', null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja.UTF-8',
'lang' => 'ja',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
// lang, domain, path (no override)
'locale, domain and path, no sessions' => [
// lang, domain, encoding, path
'ja.UTF-8', 'admin', '', __DIR__ . '/locale_other/',
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja.UTF-8',
'lang' => 'ja',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?locale_other\/$/",
],
],
// all params set (no override)
'all parameter, no sessions' => [
// lang, domain, encoding, path
'ja', 'admin', 'UTF-8', __DIR__ . '/locale_other/',
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja',
'lang' => 'ja',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?locale_other\/$/",
],
],
// param lang and domain (no override)
'long locale, domain, encoding params, no sessions' => [
// lang, domain, encoding, path
'de_CH.UTF-8@euro', 'admin', 'UTF-8', null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'de_CH.UTF-8@euro',
'lang' => 'de_CH',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
// TODO invalid params (bad path) (no override)
// TODO param calls, but with override set
];
}
/**
* Undocumented function
*
* @covers ::setLocale
* @dataProvider setLocaleProvider
* @testdox lang settings lang $language, domain $domain, encoding $encoding, path $path; session lang: $SESSION_DEFAULT_LOCALE, session char: $SESSION_DEFAULT_CHARSET [$_dataName]
*
* @return void
*/
public function testsetLocale(
?string $language,
?string $domain,
?string $encoding,
?string $path,
?string $SESSION_DEFAULT_LOCALE,
?string $SESSION_DEFAULT_CHARSET,
array $expected
): void {
$return_lang_settings = [];
global $_SESSION;
// set override
if ($SESSION_DEFAULT_LOCALE !== null) {
$_SESSION['DEFAULT_LOCALE'] = $SESSION_DEFAULT_LOCALE;
}
if ($SESSION_DEFAULT_CHARSET !== null) {
$_SESSION['DEFAULT_CHARSET'] = $SESSION_DEFAULT_CHARSET;
}
// function call
if ($language === null && $domain === null && $encoding === null && $path === null) {
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale();
} elseif ($language !== null && $domain === null && $encoding === null && $path === null) {
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
$language
);
} elseif ($language !== null && $domain !== null && $encoding === null && $path === null) {
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
$language,
$domain
);
} elseif ($language !== null && $domain !== null && $encoding !== null && $path === null) {
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
$language,
$domain,
$encoding
);
} else {
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
$language,
$domain,
$encoding,
$path
);
}
// print "RETURN: " . print_r($return_lang_settings, true) . "\n";
foreach (
[
'locale', 'lang', 'domain', 'encoding', 'path'
] as $key
) {
$value = $expected[$key];
if (strpos($value, "/") === 0) {
// this is regex
$this->assertMatchesRegularExpression(
$value,
$return_lang_settings[$key],
'assert regex failed for ' . $key
);
} else {
// assert equal
$this->assertEquals(
$value,
$return_lang_settings[$key],
'assert equal failed for ' . $key
);
}
}
// unset all vars
$_SESSION = [];
unset($GLOBALS['OVERRIDE_LANG']);
}
}
// __END__

View File

@@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
*/
final class CoreLibsDebugLoggingTest extends TestCase
{
private const LOG_FOLDER = __DIR__ . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR;
/**
* test set for options BASIC
*
@@ -33,17 +34,20 @@ final class CoreLibsDebugLoggingTest extends TestCase
return [
'log folder set' => [
[
'log_folder' => '/tmp'
'log_folder' => DIRECTORY_SEPARATOR . 'tmp',
'file_id' => 'testClassInit'
],
[
'log_folder' => '/tmp/',
'log_folder' => DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR,
'debug_all' => false,
'print_all' => false,
],
[]
],
'nothing set' => [
null,
[
'file_id' => 'testClassInit'
],
[
'log_folder' => getcwd() . DIRECTORY_SEPARATOR,
'debug_all' => false,
@@ -51,30 +55,33 @@ final class CoreLibsDebugLoggingTest extends TestCase
],
[]
],
'no options set, constant set' => [
null,
'no options set, constant set [DEPRECATED]' => [
[
'log_folder' => str_replace('/configs', '', __DIR__)
. DIRECTORY_SEPARATOR . 'log/',
'file_id' => 'testClassInit'
],
[
'log_folder' => str_replace(DIRECTORY_SEPARATOR . 'configs', '', __DIR__)
. DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR,
'debug_all' => false,
'print_all' => false,
],
[
'constant' => [
'BASE' => str_replace('/configs', '', __DIR__)
'BASE' => str_replace(DIRECTORY_SEPARATOR . 'configs', '', __DIR__)
. DIRECTORY_SEPARATOR,
'LOG' => 'log/'
'LOG' => 'log' . DIRECTORY_SEPARATOR
]
]
],
'standard test set' => [
[
'log_folder' => '/tmp',
'log_folder' => DIRECTORY_SEPARATOR . 'tmp',
'file_id' => 'testClassInit',
'debug_all' => true,
'print_all' => true,
],
[
'log_folder' => '/tmp/',
'log_folder' => DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR,
'debug_all' => true,
'print_all' => true,
],
@@ -89,35 +96,66 @@ final class CoreLibsDebugLoggingTest extends TestCase
* @dataProvider optionsProvider
* @testdox init test [$_dataName]
*
* @param array|null $options
* @param array $options
* @param array $expected
* @param array $override
* @return void
*/
public function testClassInit(?array $options, array $expected, array $override): void
public function testClassInit(array $options, array $expected, array $override): void
{
if (!empty($override['constant'])) {
foreach ($override['constant'] as $var => $value) {
define($var, $value);
if (!defined($var)) {
define($var, $value);
}
}
// for deprecated no log_folder set
// if base is defined and it does have AAASetupData set
// change the log_folder "Debug" to "AAASetupData"
if (
defined('BASE') &&
strpos(BASE, DIRECTORY_SEPARATOR . 'AAASetupData') !== false
) {
$expected['log_folder'] = str_replace(
DIRECTORY_SEPARATOR . 'Debug',
DIRECTORY_SEPARATOR . 'AAASetupData',
$expected['log_folder']
);
}
}
if ($options === null) {
$log = new \CoreLibs\Debug\Logging();
} else {
$log = new \CoreLibs\Debug\Logging($options);
// if not log folder and constant set -> expect E_USER_DEPRECATION
if (!empty($override['constant']) && empty($options['log_folder'])) {
// the deprecation message
$deprecation_message = 'options: log_folder must be set. '
. 'Setting via BASE and LOG constants is deprecated';
// convert E_USER_DEPRECATED to a exception
set_error_handler(
static function (int $errno, string $errstr): never {
throw new \Exception($errstr, $errno);
},
E_USER_DEPRECATED
);
// catch this with the message
$this->expectExceptionMessage($deprecation_message);
}
$log = new \CoreLibs\Debug\Logging($options);
// reset error handler
restore_error_handler();
// check that settings match
$this->assertEquals(
$expected['log_folder'],
$log->getSetting('log_folder')
$log->getSetting('log_folder'),
'log folder not matching'
);
$this->assertEquals(
$expected['debug_all'],
$log->getSetting('debug_output_all')
$log->getSetting('debug_output_all'),
'debug all flag not matching'
);
$this->assertEquals(
$expected['print_all'],
$log->getSetting('print_output_all')
$log->getSetting('print_output_all'),
'print all flag not matching'
);
// print "LOG: " . $log->getSetting('log_folder') . "\n";
// print "DEBUG: " . $log->getSetting('debug_output_all') . "\n";
@@ -134,17 +172,23 @@ final class CoreLibsDebugLoggingTest extends TestCase
// 0: options
// 1: expected
// 2: override
// 3: exception message
return [
'no log id set' => [
null,
[
'log_folder' => self::LOG_FOLDER,
],
[
'log_file_id' => ''
],
[]
[],
null
],
// set log id manually afterwards
'set log id manually' => [
null,
[
'log_folder' => self::LOG_FOLDER,
],
[
'log_file_id' => '',
'set_log_file_id' => 'abc123',
@@ -154,21 +198,26 @@ final class CoreLibsDebugLoggingTest extends TestCase
'values' => [
'log_file_id' => 'abc123'
]
]
],
null
],
// set log id from options
'set log id via options' => [
[
'file_id' => 'abc456',
'log_folder' => self::LOG_FOLDER,
],
[
'log_file_id' => 'abc456'
],
[]
[],
null
],
// set log id from GLOBALS [DEPRECATED]
'set log id via globals' => [
null,
'set log id via globals [DEPRECATED]' => [
[
'log_folder' => self::LOG_FOLDER,
],
[
'log_file_id' => 'def123'
],
@@ -176,11 +225,14 @@ final class CoreLibsDebugLoggingTest extends TestCase
'globals' => [
'LOG_FILE_ID' => 'def123'
]
]
],
'options: file_id must be set. Setting via LOG_FILE_ID global variable is deprecated'
],
// set log id from CONSTANT [DEPRECATED]
'set log id via constant' => [
null,
'set log id via constant [DEPRECATED]' => [
[
'log_folder' => self::LOG_FOLDER,
],
[
'log_file_id' => 'ghi123'
],
@@ -192,12 +244,14 @@ final class CoreLibsDebugLoggingTest extends TestCase
'constant' => [
'LOG_FILE_ID' => 'ghi123'
]
]
],
'options: file_id must be set. Setting via LOG_FILE_ID constant is deprecated'
],
// invalid, keep previous set
'invalid log id' => [
[
'file_id' => 'jkl456'
'file_id' => 'jkl456',
'log_folder' => self::LOG_FOLDER,
],
[
'log_file_id' => 'jkl456',
@@ -207,7 +261,8 @@ final class CoreLibsDebugLoggingTest extends TestCase
'values' => [
'log_file_id' => './#'
]
]
],
null
]
];
}
@@ -219,13 +274,18 @@ final class CoreLibsDebugLoggingTest extends TestCase
* @dataProvider logIdOptionsProvider
* @testdox log id set/get tests [$_dataName]
*
* @param array|null $options
* @param array $options
* @param array $expected
* @param array $override
* @param string|null $deprecation_message until we remove the old code
* @return void
*/
public function testLogId(?array $options, array $expected, array $override): void
{
public function testLogId(
array $options,
array $expected,
array $override,
?string $deprecation_message
): void {
// we need to set with file_id option, globals LOG_FILE_ID, constant LOG_FILE_ID
if (!empty($override['constant'])) {
foreach ($override['constant'] as $var => $value) {
@@ -237,11 +297,20 @@ final class CoreLibsDebugLoggingTest extends TestCase
$GLOBALS[$var] = $value;
}
}
if ($options === null) {
$log = new \CoreLibs\Debug\Logging();
} else {
$log = new \CoreLibs\Debug\Logging($options);
if (!empty($override['constant']) || !empty($override['globals'])) {
// convert E_USER_DEPRECATED to a exception
set_error_handler(
static function (int $errno, string $errstr): never {
throw new \Exception($errstr, $errno);
},
E_USER_DEPRECATED
);
// catch this with the message
$this->expectExceptionMessage($deprecation_message);
}
$log = new \CoreLibs\Debug\Logging($options);
// reset error handler
restore_error_handler();
// check current
$this->assertEquals(
$log->getLogId(),
@@ -316,7 +385,10 @@ final class CoreLibsDebugLoggingTest extends TestCase
bool $expected_get
): void {
// neutral start with default
$log = new \CoreLibs\Debug\Logging();
$log = new \CoreLibs\Debug\Logging([
'file_id' => 'testSetGetLogLevelAll',
'log_folder' => self::LOG_FOLDER
]);
// set and check
$this->assertEquals(
$log->setLogLevelAll($type, $flag),
@@ -438,7 +510,10 @@ final class CoreLibsDebugLoggingTest extends TestCase
$expected_get
): void {
// neutral start with default
$log = new \CoreLibs\Debug\Logging();
$log = new \CoreLibs\Debug\Logging([
'file_id' => 'testSetGetLogLevel',
'log_folder' => self::LOG_FOLDER
]);
// set
$this->assertEquals(
$log->setLogLevel($type, $flag, $debug_on),
@@ -517,7 +592,10 @@ final class CoreLibsDebugLoggingTest extends TestCase
bool $expected_get
): void {
// neutral start with default
$log = new \CoreLibs\Debug\Logging();
$log = new \CoreLibs\Debug\Logging([
'file_id' => 'testSetGetLogPer',
'log_folder' => self::LOG_FOLDER
]);
// set and check
$this->assertEquals(
$log->setLogPer($type, $set),
@@ -546,7 +624,10 @@ final class CoreLibsDebugLoggingTest extends TestCase
public function testSetGetLogPrintFileDate(bool $input, bool $expected_set, bool $expected_get): void
{
// neutral start with default
$log = new \CoreLibs\Debug\Logging();
$log = new \CoreLibs\Debug\Logging([
'file_id' => 'testSetGetLogPrintFileDate',
'log_folder' => self::LOG_FOLDER
]);
// set and check
$this->assertEquals(
$log->setGetLogPrintFileDate($input),
@@ -612,7 +693,10 @@ final class CoreLibsDebugLoggingTest extends TestCase
*/
public function testPrAr(array $input, string $expected): void
{
$log = new \CoreLibs\Debug\Logging();
$log = new \CoreLibs\Debug\Logging([
'file_id' => 'testPrAr',
'log_folder' => self::LOG_FOLDER
]);
$this->assertEquals(
$log->prAr($input),
$expected
@@ -673,7 +757,10 @@ final class CoreLibsDebugLoggingTest extends TestCase
*/
public function testPrBl(bool $input, ?string $true, ?string $false, string $expected): void
{
$log = new \CoreLibs\Debug\Logging();
$log = new \CoreLibs\Debug\Logging([
'file_id' => 'testPrBl',
'log_folder' => self::LOG_FOLDER
]);
$return = '';
if ($true === null && $false === null) {
$return = $log->prBl($input);
@@ -959,9 +1046,16 @@ final class CoreLibsDebugLoggingTest extends TestCase
public function testLogUniqueId(bool $option, bool $override): void
{
if ($option === true) {
$log = new \CoreLibs\Debug\Logging(['per_run' => $option]);
$log = new \CoreLibs\Debug\Logging([
'file_id' => 'testLogUniqueId',
'log_folder' => self::LOG_FOLDER,
'per_run' => $option
]);
} else {
$log = new \CoreLibs\Debug\Logging();
$log = new \CoreLibs\Debug\Logging([
'file_id' => 'testLogUniqueId',
'log_folder' => self::LOG_FOLDER
]);
$log->setLogUniqueId();
}
$per_run_id = $log->getLogUniqueId();

View File

@@ -114,7 +114,15 @@ final class CoreLibsDebugSupportTest extends TestCase
*/
public function printToStringProvider(): array
{
// 0: unput
// 1: html flag (only for strings and arry)
// 2: expected
return [
'null' => [
null,
null,
'NULL',
],
'string' => [
'a string',
null,
@@ -333,16 +341,19 @@ final class CoreLibsDebugSupportTest extends TestCase
* @dataProvider printToStringProvider
* @testdox printToString $input with $flag will be $expected [$_dataName]
*
* @param mixed $input
* @param boolean|null $flag
* @param string $expected
* @param mixed $input anything
* @param boolean|null $flag html flag, only for string and array
* @param string $expected always string
* @return void
*/
public function testPrintToString($input, ?bool $flag, string $expected): void
public function testPrintToString(mixed $input, ?bool $flag, string $expected): void
{
if ($flag === null) {
// if expected starts with / and ends with / then this is a regex compare
if (substr($expected, 0, 1) == '/' && substr($expected, -1, 1) == '/') {
if (
substr($expected, 0, 1) == '/' &&
substr($expected, -1, 1) == '/'
) {
$this->assertMatchesRegularExpression(
$expected,
\CoreLibs\Debug\Support::printToString($input)
@@ -382,7 +393,7 @@ final class CoreLibsDebugSupportTest extends TestCase
* Undocumented function
*
* @cover ::getCallerMethodList
* @testWith [["main", "run", "run", "run", "run", "run", "run", "runBare", "runTest", "testGetCallerMethodList"],["main", "run", "run", "run", "run", "run", "run", "run", "runBare", "runTest", "testGetCallerMethodList"]]
* @testWith [["main", "run", "run", "run", "run", "run", "run", "runBare", "runTest", "testGetCallerMethodList"],["include", "main", "run", "run", "run", "run", "run", "run", "run", "runBare", "runTest", "testGetCallerMethodList"]]
* @testdox getCallerMethodList check if it returns $expected [$_dataName]
*
* @param array $expected

3
4dev/tests/Debug/log/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*log
*LOG
!.gitignore

View File

@@ -77,21 +77,24 @@ final class CoreLibsGetDotEnvTest extends TestCase
'file' => 'cannot_read.env',
'status' => 2,
'content' => [],
'chmod' => '000',
// 0000
'chmod' => '100000',
],
'empty file' => [
'folder' => __DIR__ . DIRECTORY_SEPARATOR . 'dotenv',
'file' => 'empty.env',
'status' => 1,
'content' => [],
'chmod' => null,
// 0664
'chmod' => '100664',
],
'override all' => [
'folder' => __DIR__ . DIRECTORY_SEPARATOR . 'dotenv',
'file' => 'test.env',
'status' => 0,
'content' => $dot_env_content,
'chmod' => null,
// 0664
'chmod' => '100664',
],
'override directory' => [
'folder' => __DIR__ . DIRECTORY_SEPARATOR . 'dotenv',
@@ -124,6 +127,16 @@ final class CoreLibsGetDotEnvTest extends TestCase
array $expected_env,
?string $chmod
): void {
if (
!empty($chmod) &&
$chmod == '100000' &&
getmyuid() == 0
) {
$this->markTestSkipped(
"Skip cannot read file test because run user is root"
);
return;
}
// if we have file + chmod set
$old_chmod = null;
if (
@@ -134,6 +147,20 @@ final class CoreLibsGetDotEnvTest extends TestCase
$old_chmod = fileperms($folder . DIRECTORY_SEPARATOR . $file);
chmod($folder . DIRECTORY_SEPARATOR . $file, octdec($chmod));
}
$message = '\CoreLibs\Get\DotEnv is deprecated in favor for '
. 'composer package gullevek\dotenv which is a copy of this';
// convert E_USER_DEPRECATED to a exception
set_error_handler(
static function (int $errno, string $errstr): never {
throw new \Exception($errstr, $errno);
},
E_USER_DEPRECATED
);
// tests are never run -> deprecated
if (is_file($folder . DIRECTORY_SEPARATOR . $file)) {
chmod($folder . DIRECTORY_SEPARATOR . $file, 0664);
}
$this->expectExceptionMessage($message);
if ($folder !== null && $file !== null) {
$status = DotEnv::readEnvFile($folder, $file);
} elseif ($folder !== null) {
@@ -141,6 +168,7 @@ final class CoreLibsGetDotEnvTest extends TestCase
} else {
$status = DotEnv::readEnvFile();
}
restore_error_handler();
$this->assertEquals(
$status,
$expected_status,
@@ -153,8 +181,9 @@ final class CoreLibsGetDotEnvTest extends TestCase
'Assert _ENV correct'
);
// if we have file and chmod unset
if ($old_chmod !== null) {
chmod($folder . DIRECTORY_SEPARATOR . $file, $old_chmod);
print "Write mode: $old_chmod\n";
if ($old_chmod !== null && $chmod == '100000') {
chmod($folder . DIRECTORY_SEPARATOR . $file, 0664);
}
}
}

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 => "/^(\/?.*\/?)?www\/vendor\/bin\/phpunit$/",
3 => "/^(\/?.*\/?)?vendor\/bin\/phpunit$/",
],
'some path with extension' => [
0 => '/some/path/to/file.txt',

View File

@@ -0,0 +1,568 @@
<?php // phpcs:disable Generic.Files.LineLength
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
/**
* Test class for Language\GetLocale
*
* @coversDefaultClass \CoreLibs\Language\GetLocale
* @testdox \CoreLibs\Language\GetLocale method tests
*/
final class CoreLibsLanguageGetLocaleTest extends TestCase
{
public const SITE_ENCODING = 'UTF-8';
public const SITE_LOCALE = 'en_US.UTF-8';
public const SITE_DOMAIN = 'admin';
public const LOCALE_PATH = __DIR__ . DIRECTORY_SEPARATOR
. 'includes' . DIRECTORY_SEPARATOR
. 'locale' . DIRECTORY_SEPARATOR;
/**
* set all constant variables that must be set before call
*
* @return void
*/
public static function setUpBeforeClass(): void
{
// default web page encoding setting
/* if (!defined('DEFAULT_ENCODING')) {
define('DEFAULT_ENCODING', 'UTF-8');
}
if (!defined('DEFAULT_LOCALE')) {
// default lang + encoding
define('DEFAULT_LOCALE', 'en_US.UTF-8');
}
// site
if (!defined('SITE_ENCODING')) {
define('SITE_ENCODING', DEFAULT_ENCODING);
}
if (!defined('SITE_LOCALE')) {
define('SITE_LOCALE', DEFAULT_LOCALE);
} */
// just set
/* if (!defined('BASE')) {
define('BASE', str_replace('/configs', '', __DIR__) . DIRECTORY_SEPARATOR);
}
if (!defined('INCLUDES')) {
define('INCLUDES', 'includes' . DIRECTORY_SEPARATOR);
}
if (!defined('LANG')) {
define('LANG', 'lang' . DIRECTORY_SEPARATOR);
}
if (!defined('LOCALE')) {
define('LOCALE', 'locale' . DIRECTORY_SEPARATOR);
}
if (!defined('CONTENT_PATH')) {
define('CONTENT_PATH', 'frontend' . DIRECTORY_SEPARATOR);
} */
// array session
$_SESSION = [];
global $_SESSION;
}
/**
* all the test data
*
* @return array<mixed>
*/
/* public function setLocaleProvider(): array
{
return [
// 0: locale
// 1: domain
// 2: encoding
// 3: path
// 4: SESSION: DEFAULT_LOCALE
// 5: SESSION: DEFAULT_CHARSET
// 6: expected array
// 7: deprecation message
'no params, all default constants' => [
// lang, domain, encoding, path
null, null, null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'en_US.UTF-8',
'lang' => 'en_US',
'domain' => 'frontend',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
'setLocale: Unset $locale or unset SESSION locale is deprecated',
],
'no params, session charset and lang' => [
// lang, domain, encoding, path
null, null, null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
'ja_JP', 'UTF-8',
// return array
[
'locale' => 'ja_JP',
'lang' => 'ja_JP',
'domain' => 'frontend',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
'setLocale: Unset $domain is deprecated'
],
'no params, session charset and lang short' => [
// lang, domain, encoding, path
null, null, null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
'ja', 'UTF-8',
// return array
[
'locale' => 'ja',
'lang' => 'ja',
'domain' => 'frontend',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
'setLocale: Unset $domain is deprecated',
],
// param lang (no sessions)
'locale param only, no sessions' => [
// lang, domain, encoding, path
'ja.UTF-8', null, null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja.UTF-8',
'lang' => 'ja',
'domain' => 'frontend',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
'setLocale: Unset $domain is deprecated',
],
// different locale setting
'locale complex param only, no sessions' => [
// lang, domain, encoding, path
'ja_JP.SJIS', null, null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja_JP.SJIS',
'lang' => 'ja_JP',
'domain' => 'frontend',
'encoding' => 'SJIS',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
'setLocale: Unset $domain is deprecated',
],
// param lang and domain (no override)
'locale, domain params, no sessions' => [
// lang, domain, encoding, path
'ja.UTF-8', 'admin', null, null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja.UTF-8',
'lang' => 'ja',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
'setLocale: Unset $path is deprecated',
],
// param lang and domain (no override)
'locale, domain, encoding params, no sessions' => [
// lang, domain, encoding, path
'ja.UTF-8', 'admin', 'UTF-8', null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja.UTF-8',
'lang' => 'ja',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
'setLocale: Unset $path is deprecated'
],
// lang, domain, path (no override)
'locale, domain and path, no sessions' => [
// lang, domain, encoding, path
'ja.UTF-8', 'admin', '', __DIR__ . '/locale_other/',
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja.UTF-8',
'lang' => 'ja',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?locale_other\/$/",
],
null
],
// all params set (no override)
'all parameter, no sessions' => [
// lang, domain, encoding, path
'ja', 'admin', 'UTF-8', __DIR__ . '/locale_other/',
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'ja',
'lang' => 'ja',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?locale_other\/$/",
],
null
],
// param lang and domain (no override)
'long locale, domain, encoding params, no sessions' => [
// lang, domain, encoding, path
'de_CH.UTF-8@euro', 'admin', 'UTF-8', null,
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
null, null,
// return array
[
'locale' => 'de_CH.UTF-8@euro',
'lang' => 'de_CH',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
'setLocale: Unset $path is deprecated',
],
// TODO invalid params (bad path) (no override)
// TODO param calls, but with override set
];
} */
/**
* Undocumented function
*
* @covers ::setLocale
* @dataProvider setLocaleProvider
* @testdox lang settings lang $language, domain $domain, encoding $encoding, path $path; session lang: $SESSION_DEFAULT_LOCALE, session char: $SESSION_DEFAULT_CHARSET [$_dataName]
*
* @param string|null $language
* @param string|null $domain
* @param string|null $encoding
* @param string|null $path
* @param string|null $SESSION_DEFAULT_LOCALE
* @param string|null $SESSION_DEFAULT_CHARSET
* @param array<mixed> $expected
* @param string|null $deprecation_message
* @return void
*/
/* public function testsetLocale(
?string $language,
?string $domain,
?string $encoding,
?string $path,
?string $SESSION_DEFAULT_LOCALE,
?string $SESSION_DEFAULT_CHARSET,
array $expected,
?string $deprecation_message
): void {
$return_lang_settings = [];
global $_SESSION;
// set override
if ($SESSION_DEFAULT_LOCALE !== null) {
$_SESSION['DEFAULT_LOCALE'] = $SESSION_DEFAULT_LOCALE;
}
if ($SESSION_DEFAULT_CHARSET !== null) {
$_SESSION['DEFAULT_CHARSET'] = $SESSION_DEFAULT_CHARSET;
}
if ($deprecation_message !== null) {
set_error_handler(
static function (int $errno, string $errstr): never {
throw new \Exception($errstr, $errno);
},
E_USER_DEPRECATED
);
// catch this with the message
$this->expectExceptionMessage($deprecation_message);
}
// function call
if (
$language === null && $domain === null &&
$encoding === null && $path === null
) {
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale();
} elseif (
$language !== null && $domain === null &&
$encoding === null && $path === null
) {
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
$language
);
} elseif (
$language !== null && $domain !== null &&
$encoding === null && $path === null
) {
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
$language,
$domain
);
} elseif (
$language !== null && $domain !== null &&
$encoding !== null && $path === null
) {
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
$language,
$domain,
$encoding
);
} else {
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
$language,
$domain,
$encoding,
$path
);
}
restore_error_handler();
// print "RETURN: " . print_r($return_lang_settings, true) . "\n";
foreach (
[
'locale', 'lang', 'domain', 'encoding', 'path'
] as $key
) {
$value = $expected[$key];
if (strpos($value, "/") === 0) {
// this is regex
$this->assertMatchesRegularExpression(
$value,
$return_lang_settings[$key],
'assert regex failed for ' . $key
);
} else {
// assert equal
$this->assertEquals(
$value,
$return_lang_settings[$key],
'assert equal failed for ' . $key
);
}
}
// unset all vars
$_SESSION = [];
unset($GLOBALS['OVERRIDE_LANG']);
} */
/**
* all the test data
*
* @return array<mixed>
*/
public function setLocaleFromSessionProvider(): array
{
return [
// 0: locale
// 1: domain
// 2: encoding
// 3: path
// 4: SESSION: DEFAULT_LOCALE
// 5: SESSION: DEFAULT_CHARSET
// 5: SESSION: DEFAULT_DOMAIN
// 6: SESSION: LOCALE_PATH
// 6: expected array
// 7: deprecation message
'all session vars set' => [
// lang, domain, encoding, path
self::SITE_LOCALE, self::SITE_DOMAIN, self::SITE_ENCODING, self::LOCALE_PATH,
// SESSION SETTINGS: locale, charset, domain, path
'ja_JP.UTF-8', 'UTF-8', 'admin', __DIR__ . '/locale_other/',
// return array
[
'locale' => 'ja_JP.UTF-8',
'lang' => 'ja_JP',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?locale_other\/$/",
],
],
// param lang and domain (no override)
'no session set, only parameters' => [
// lang, domain, encoding, path
self::SITE_LOCALE, self::SITE_DOMAIN, self::SITE_ENCODING, self::LOCALE_PATH,
// SESSION SETTINGS: locale, charset, domain, path
null, null, null, null,
// return array
[
'locale' => 'en_US.UTF-8',
'lang' => 'en_US',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
// special parse session check for locales
'all session vars set, short lang' => [
// lang, domain, encoding, path
self::SITE_LOCALE, self::SITE_DOMAIN, self::SITE_ENCODING, self::LOCALE_PATH,
// SESSION SETTINGS: locale, charset, domain, path
'ja', 'UTF-8', 'admin', __DIR__ . '/locale_other/',
// return array
[
'locale' => 'ja',
'lang' => 'ja',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?locale_other\/$/",
],
],
// lang with modifier
// param lang and domain (no override)
'long locale, domain, encoding params, no sessions' => [
// lang, domain, encoding, path
self::SITE_LOCALE, self::SITE_DOMAIN, self::SITE_ENCODING, self::LOCALE_PATH,
// SESSION SETTINGS: locale, charset, domain, path
'de_CH.UTF-8@euro', 'admin', 'UTF-8', __DIR__ . '/includes/locale/',
// return array
[
'locale' => 'de_CH.UTF-8@euro',
'lang' => 'de_CH',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
// missing session values check
// special parse session check for locales
'session missing encoding, set from parameters' => [
// lang, domain, encoding, path
self::SITE_LOCALE, self::SITE_DOMAIN, self::SITE_ENCODING, self::LOCALE_PATH,
// SESSION SETTINGS: locale, charset, domain, path
'ja', null, 'admin', __DIR__ . '/locale_other/',
// return array
[
'locale' => 'ja',
'lang' => 'ja',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?locale_other\/$/",
],
],
// null return check for invalid entries
'no session set, only parameters, all invalid' => [
// lang, domain, encoding, path
'###', '&&&&', '$$$$', 'foo_bar_path',
// SESSION SETTINGS: locale, charset, domain, path
null, null, null, null,
// return array
[
'locale' => null,
'lang' => null,
'domain' => null,
'encoding' => null,
'path' => null,
],
],
// invalid session names, fall backup
'all session vars are invalid, fallback' => [
// lang, domain, encoding, path
self::SITE_LOCALE, self::SITE_DOMAIN, self::SITE_ENCODING, self::LOCALE_PATH,
// SESSION SETTINGS: locale, charset, domain, path
'###', '&&&&', '$$$$', 'foo_bar_path',
// return array
[
'locale' => 'en_US.UTF-8',
'lang' => 'en_US',
'domain' => 'admin',
'encoding' => 'UTF-8',
'path' => "/^\/(.*\/)?includes\/locale\/$/",
],
],
];
}
/**
* Undocumented function
*
* @covers ::setLocale
* @dataProvider setLocaleFromSessionProvider
* @testdox lang settings lang $language, domain $domain, encoding $encoding, path $path; session lang: $SESSION_DEFAULT_LOCALE, session char: $SESSION_DEFAULT_CHARSET [$_dataName]
*
* @param string| $language
* @param string| $domain
* @param string| $encoding
* @param string| $path
* @param string|null $SESSION_DEFAULT_LOCALE
* @param string|null $SESSION_DEFAULT_CHARSET
* @param string|null $SESSION_DEFAULT_DOMAIN
* @param string|null $SESSION_LOCALE_PATH
* @param array<mixed> $expected
* @return void
*/
public function testsetLocaleFromSession(
string $language,
string $domain,
string $encoding,
string $path,
?string $SESSION_DEFAULT_LOCALE,
?string $SESSION_DEFAULT_CHARSET,
?string $SESSION_DEFAULT_DOMAIN,
?string $SESSION_LOCALE_PATH,
array $expected,
): void {
$return_lang_settings = [];
global $_SESSION;
// set override
if ($SESSION_DEFAULT_LOCALE !== null) {
$_SESSION['DEFAULT_LOCALE'] = $SESSION_DEFAULT_LOCALE;
}
if ($SESSION_DEFAULT_CHARSET !== null) {
$_SESSION['DEFAULT_CHARSET'] = $SESSION_DEFAULT_CHARSET;
}
if ($SESSION_DEFAULT_DOMAIN !== null) {
$_SESSION['DEFAULT_DOMAIN'] = $SESSION_DEFAULT_DOMAIN;
}
if ($SESSION_LOCALE_PATH !== null) {
$_SESSION['LOCALE_PATH'] = $SESSION_LOCALE_PATH;
}
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocaleFromSession(
$language,
$domain,
$encoding,
$path
);
// print "RETURN: " . print_r($return_lang_settings, true) . "\n";
foreach (
[
'locale', 'lang', 'domain', 'encoding', 'path'
] as $key
) {
$value = $expected[$key];
if (
!empty($value) &&
strpos($value, "/") === 0
) {
// this is regex
$this->assertMatchesRegularExpression(
$value,
$return_lang_settings[$key] ?? '',
'assert regex failed for ' . $key
);
} else {
// assert equal
$this->assertEquals(
$value,
$return_lang_settings[$key],
'assert equal failed for ' . $key
);
}
}
// unset all vars
$_SESSION = [];
unset($GLOBALS['OVERRIDE_LANG']);
}
}
// __END__

View File

@@ -22,37 +22,16 @@ final class CoreLibsLanguageL10nTest extends TestCase
*/
public static function setUpBeforeClass(): void
{
// default web page encoding setting
if (!defined('DEFAULT_ENCODING')) {
define('DEFAULT_ENCODING', 'UTF-8');
}
if (!defined('DEFAULT_LOCALE')) {
// default lang + encoding
define('DEFAULT_LOCALE', 'en_US.UTF-8');
}
// site
if (!defined('SITE_ENCODING')) {
define('SITE_ENCODING', DEFAULT_ENCODING);
}
if (!defined('SITE_LOCALE')) {
define('SITE_LOCALE', DEFAULT_LOCALE);
}
// just set
// for deprecation test only, will be removed
if (!defined('BASE')) {
define('BASE', str_replace('/configs', '', __DIR__) . DIRECTORY_SEPARATOR);
define('BASE', str_replace(DIRECTORY_SEPARATOR . 'configs', '', __DIR__) . DIRECTORY_SEPARATOR);
}
if (!defined('INCLUDES')) {
define('INCLUDES', 'includes' . DIRECTORY_SEPARATOR);
}
if (!defined('LANG')) {
define('LANG', 'lang' . DIRECTORY_SEPARATOR);
}
if (!defined('LOCALE')) {
define('LOCALE', 'locale' . DIRECTORY_SEPARATOR);
}
if (!defined('CONTENT_PATH')) {
define('CONTENT_PATH', 'frontend' . DIRECTORY_SEPARATOR);
}
}
/**
@@ -105,77 +84,163 @@ final class CoreLibsLanguageL10nTest extends TestCase
{
return [
// 0: locale
// 1: domain
// 2: encoding
// 1: encoding
// 2: domain
// 3: path
// 4: locale expected
// 5: locale set expected
// 6: domain exepcted
// 7: context (null for none)
// 8: test string in
// 9: test translated
// 6: lang expected
// 7: lang short expected
// 8: encoding expected
// 9: domain exepcted
// 10: context (null for none)
// 11: test string in
// 12: test translated
// 13: deprecation message (until removed)
// new style load
'gettext load en' => [
'en_US.UTF-8',
'UTF-8',
'frontend',
__DIR__ . 'includes/locale/',
//
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
// 4, 5, 6, 7, 8, 9
'en_US.UTF-8',
'en_US',
'en_US',
'en',
'UTF-8',
'frontend',
// 10
null,
// 11, 12
'Original',
'Translated frontend en_US',
// 13
null,
],
'gettext load en' => [
'en_US.UTF-8',
'UTF-8',
'frontend',
__DIR__ . 'includes/locale/',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
//
'en_US.UTF-8',
'en_US',
'en_US',
'en',
'UTF-8',
'frontend',
//
'context',
//
'Original',
'Original context frontend en_US',
//
null,
],
'gettext load ja' => [
'ja_JP.UTF-8',
'UTF-8',
'admin',
__DIR__ . 'includes/locale/',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
//
'ja_JP.UTF-8',
'ja_JP',
'ja_JP',
'ja',
'UTF-8',
'admin',
//
null,
//
'Original',
'Translated admin ja_JP',
//
null,
],
// mixed path and domain
'mixed path and domain' => [
// load short locale with different encoding
'gettext load short ja no encoding' => [
'ja',
'SJIS',
'admin',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
//
'ja',
'ja',
'ja',
'ja',
'SJIS',
'admin',
//
null,
//
'Original',
'Translated admin ja_JP',
//
null,
],
// mixed path and domain [DEPRECATED]
'mixed path and domain [DEPRECATED]' => [
'en_US.UTF-8',
__DIR__ . 'includes/locale/',
'UTF-8',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
'frontend',
//
'en_US.UTF-8',
'en_US',
'en_US',
'en',
'UTF-8',
'frontend',
//
'context',
//
'Original',
'Original context frontend en_US',
//
'L10n constructor parameter switch is no longer supported. domain is 2nd, path is 3rd parameter'
],
// unset path
'unset path with locale and domain [DEPRECATED]' => [
'ja_JP.UTF-8',
'UTF-8',
'admin',
null,
//
'ja_JP.UTF-8',
'ja_JP',
'ja_JP',
'ja',
'UTF-8',
'admin',
//
null,
//
'Original',
'Translated admin ja_JP',
//
'Empty path parameter is no longer allowed if locale and domain are set',
],
// null set
'empty load new ' => [
'',
'',
'',
'',
//
'',
'',
'',
'',
'', // unset on empty call
'',
//
null,
//
'Original',
'Original',
//
null,
]
];
}
@@ -188,37 +253,62 @@ final class CoreLibsLanguageL10nTest extends TestCase
* @testdox check l10n init with Locale $locale, Path $path, Domain $domain, Legacy: $legacy with $context [$_dataName]
*
* @param string|null $locale
* @param string|null $encoding
* @param string|null $domain
* @param string|null $path
* @param string $locale_expected
* @param string $locale_set_expected
* @param string $lang_expected
* @param string $lang_short_expected
* @param string $encoding_expected
* @param string $domain_expected
* @param ?string $context
* @param string|null $context
* @param string $original
* @param string $translated
* @param string|null $deprecation_message
* @return void
*/
public function testL10nObject(
?string $locale,
?string $encoding,
?string $domain,
?string $path,
string $locale_expected,
string $locale_set_expected,
string $lang_expected,
string $lang_short_expected,
string $encoding_expected,
string $domain_expected,
?string $context,
string $original,
string $translated
string $translated,
?string $deprecation_message
): void {
if ($deprecation_message !== null) {
set_error_handler(
static function (int $errno, string $errstr): never {
throw new \Exception($errstr, $errno);
},
E_USER_DEPRECATED
);
// catch this with the message
$this->expectExceptionMessage($deprecation_message);
}
if ($locale === null) {
$l10n = new \CoreLibs\Language\L10n();
} elseif ($domain === null) {
// deprecated, locale + domain must be set, handled like empty calls
$l10n = new \CoreLibs\Language\L10n($locale);
} elseif ($path === null) {
// deprecated, path must be set, will thow DEPRECATION error, handled like empty
$l10n = new \CoreLibs\Language\L10n($locale, $domain);
} else {
} elseif ($encoding === null) {
// if encoding not found will be UTF-8
$l10n = new \CoreLibs\Language\L10n($locale, $domain, $path);
} else {
$l10n = new \CoreLibs\Language\L10n($locale, $domain, $path, $encoding);
}
// print "LOC: " . $locale . ", " . $l10n->getLocale() . ", " . $locale_expected . "\n";
restore_error_handler();
// print "MO: " . $l10n->getMoFile() . "\n";
$this->assertEquals(
$locale_expected,
@@ -248,6 +338,20 @@ final class CoreLibsLanguageL10nTest extends TestCase
'Translated string assert failed in context: ' . $context
);
}
// test get locel as array
$locale = $l10n->getLocaleAsArray();
$this->assertEquals(
[
'locale' => $locale_expected,
'lang' => $lang_expected,
'lang_short' => $lang_short_expected,
'domain' => $domain_expected,
'encoding' => $encoding_expected,
'path' => $path
],
$locale,
'getLocaleAsArray mismatch'
);
}
// l10nReloadMOfile and getTranslator
@@ -283,7 +387,7 @@ final class CoreLibsLanguageL10nTest extends TestCase
// set 0-2
'en_US.UTF-8',
'frontend',
__DIR__ . 'includes/locale/',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
// status 3
false,
// to translate 4
@@ -296,7 +400,7 @@ final class CoreLibsLanguageL10nTest extends TestCase
// set new 8-10
'ja_JP.UTF-8',
'frontend',
__DIR__ . 'includes/locale/',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
// status new 11
false,
// check new setter 12-14
@@ -322,7 +426,7 @@ final class CoreLibsLanguageL10nTest extends TestCase
// set new 8-10
'en_US.UTF-8',
'frontend',
__DIR__ . 'includes/locale/',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
// status new 11
false,
// check new setter 12-14
@@ -387,12 +491,8 @@ final class CoreLibsLanguageL10nTest extends TestCase
string $domain_expected_b,
string $translated_b
): void {
if ($locale === null) {
if ($locale === null || $domain === null || $path === null) {
$l10n = new \CoreLibs\Language\L10n();
} elseif ($domain === null) {
$l10n = new \CoreLibs\Language\L10n($locale);
} elseif ($path === null) {
$l10n = new \CoreLibs\Language\L10n($locale, $domain);
} else {
$l10n = new \CoreLibs\Language\L10n($locale, $domain, $path);
}
@@ -494,16 +594,16 @@ final class CoreLibsLanguageL10nTest extends TestCase
{
return [
// 0: locale
// 1: path
// 2: domain
// 1: domain
// 2: path
// 3: context (null for none)
// 4: single string
// 5: plural string
// 6: array for each n value expected string
'plural text en' => [
'en_US',
__DIR__ . 'includes/locale/',
'admin',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
// context
null,
// text single/multi in
@@ -518,8 +618,8 @@ final class CoreLibsLanguageL10nTest extends TestCase
],
'plural text context en' => [
'en_US',
__DIR__ . 'includes/locale/',
'admin',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
// context
'context',
// text single/multi in
@@ -544,8 +644,8 @@ final class CoreLibsLanguageL10nTest extends TestCase
* @testdox plural string test for locale $locale and domain $domain with $context [$_dataName]
*
* @param string $locale
* @param string $path
* @param string $domain
* @param string $path
* @param ?string $context
* @param string $original_single
* @param string $original_plural
@@ -555,8 +655,8 @@ final class CoreLibsLanguageL10nTest extends TestCase
public function testNgettext(
// config 0-3
string $locale,
string $path,
string $domain,
string $path,
// context string
?string $context,
// input strings
@@ -565,7 +665,7 @@ final class CoreLibsLanguageL10nTest extends TestCase
// expected
array $expected_strings
): void {
$l10n = new \CoreLibs\Language\L10n($locale, $path, $domain, false);
$l10n = new \CoreLibs\Language\L10n($locale, $domain, $path);
foreach ($expected_strings as $n => $expected) {
if (empty($context)) {
@@ -981,7 +1081,7 @@ final class CoreLibsLanguageL10nTest extends TestCase
'standard en' => [
'en_US.UTF-8',
'frontend',
__DIR__ . 'includes/locale/',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
'UTF-8',
'Original',
'Translated frontend en_US',
@@ -989,7 +1089,7 @@ final class CoreLibsLanguageL10nTest extends TestCase
'standard ja' => [
'ja_JP.UTF-8',
'admin',
__DIR__ . 'includes/locale/',
__DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR,
'UTF-8',
'Original',
'Translated admin ja_JP',
@@ -1030,6 +1130,7 @@ final class CoreLibsLanguageL10nTest extends TestCase
_textdomain($domain);
_bindtextdomain($domain, $path);
_bind_textdomain_codeset($domain, $encoding);
$this->assertEquals(
$translated,
__($original),

View File

@@ -0,0 +1 @@
ja_JP

View File

@@ -1 +0,0 @@
test.env

View File

@@ -4,7 +4,6 @@
* Uses PSR-12
* tab indent instead of 4 spaces indent
* Warning at 120 character length, error at 240 character length
## General information
@@ -24,9 +23,9 @@ There are three branches:
### master
The active branch, which is the namespace branch.
Currently compatible with PHP 7.4 and 8.0
Compatible with PHP 8.1 or higher
### legacy
### legacy (deprecated)
The old non namepsace format layout.
This is fully deprecated and will no longer be maintaned.
@@ -39,17 +38,17 @@ Any current development is done here
## Static checks
With phpstan (`4dev/checking/phpstan.sh`)
`phpstan`
`vendor/bin/phpstan`
With phan (`4dev/checking/phan.sh`)
`phan --progress-bar -C --analyze-twice`
`vendor/bin/phan --progress-bar -C --analyze-twice`
pslam is setup but not configured
## Unit tests
With phpunit (`4dev/checking/phpunit.sh`)
`phpunit -c $phpunit.xml 4dev/tests/`
`www/vendor/bin/phpunit -c $phpunit.xml 4dev/tests/`
## Other Notes

View File

@@ -0,0 +1,16 @@
# CoreLibs Composer release flow
- run local phan/phptan/phunit tests
- commit and sync to master branch
- create a version tag in master branch
- checkout development on CoreLibs-composer-all branch
- sync `php_libraries/trunk/www/lib/CoreLibs/*` to c`omposer-packages/CoreLibs-Composer-All/src/`
- if phpunit files have been changed/updated sync them to `composer-packages/CoreLibs-Composer-All/test/phpunit/`
- run phan/phpstan/phpunit tests in composer branch
- commit and sync to master
- create the same version tag as before in the trunk/master
- GITEA and GITLAB:
- Run `publish/publish.sh` script to create composer packages
- Composer Packagest local
- update pacakges.json file with new version and commit
- run `git pull egra-gitea master` on udon-core in `/var/www/html/composer/www`

21
composer.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "egrajp/development-corelibs-dev",
"version": "dev-master",
"description": "CoreLibs: Development package",
"type": "library",
"require-dev": {
"phpstan/phpstan": "^1.10",
"phan/phan": "^5.4",
"phpstan/extension-installer": "^1.2",
"vimeo/psalm": "^5.7",
"phpstan/phpstan-deprecation-rules": "^1.1"
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true
}
},
"require": {
"php": ">=8.1"
}
}

2579
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,5 +8,7 @@ $_SERVER['HTTP_HOST'] = 'soba.tokyo.tequila.jp';
// for whatever reason it does not load that from the confing.master.php
// for includes/admin_header.php
define('BASE_NAME', '');
define('SITE_DOMAIN', '');
define('HOST_NAME', 'soba.tokyo.tequila.jp');
// __END__

View File

@@ -5,58 +5,43 @@ parameters:
tmpDir: /tmp/phpstan-corelibs
level: 8 # max is now 9
checkMissingCallableSignature: true
treatPhpDocTypesAsCertain: false
paths:
- %currentWorkingDirectory%/www
bootstrapFiles:
- %currentWorkingDirectory%/phpstan-bootstrap.php
# - %currentWorkingDirectory%/www/lib/autoloader.php
- %currentWorkingDirectory%/www/vendor/autoload.php
scanDirectories:
- www/lib/Smarty
- www/vendor
scanFiles:
- www/configs/config.php
- www/configs/config.master.php
# if composer.json autoloader defined, this is not needed
# - www/lib/autoloader.php
- www/vendor/autoload.php
- www/lib/Smarty/Autoloader.php
excludePaths:
# do not check old qq file uploader tests
- www/admin/qq_file_upload_*.php
# ignore all test files
- www/admin/class_test*.php
# extra in sub folder
- www/admin/subfolder/class_test*.php
- www/admin/error_test.php
# admin synlink files
- www/admin/edit_*.php
# config symlinks
- www/admin/config.php
- www/frontend/config.php
- www/frontend/*/config.php
# ignore admin header stuff
# - www/includes/admin_header.php # ignore the admin include stuff
- www/includes/admin_footer.php # ignore the admin include stuff
# deprecated files
- www/includes/admin_set_paths.php # ignore the admin include stuff
- www/includes/admin_smarty.php # ignore the admin include stuff
- www/includes/edit_base.LEGACY.php # old style
# folders with data no check needed
- www/templates_c
- www/cache
- www/log
- www/media
- www/tmp
# external libs are not checked
- www/lib/Smarty/
- www/lib/smarty-*/
# ignore composer
- www/vendor
# ignore errores with
ignoreErrors:
#- # this error is ignore because of the PHP 8.0 to 8.1 change for pg_*, only for 8.0 or lower
# message: "#^Parameter \\#1 \\$(result|connection) of function pg_\\w+ expects resource(\\|null)?, object\\|resource(\\|bool)? given\\.$#"
# path: %currentWorkingDirectory%/www/lib/CoreLibs/DB/SQL/PgSQL.php
- # this is for 8.1 or newer
message: "#^Parameter \\#1 \\$(result|connection) of function pg_\\w+ expects PgSql\\\\(Result|Connection(\\|string)?(\\|null)?), object\\|resource given\\.$#"
path: %currentWorkingDirectory%/www/lib/CoreLibs/DB/SQL/PgSQL.php
# this is ignored for now
- # in the class_test tree we allow deprecated calls
message: "#^Call to deprecated method #"
path: %currentWorkingDirectory%/www/admin/class_test.*.php
# - '#Expression in empty\(\) is always falsy.#'
# -
# message: '#Reflection error: [a-zA-Z0-9\\_]+ not found.#'

View File

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

5
psalm-config.php Normal file
View File

@@ -0,0 +1,5 @@
<?php
define('BASE', '');
// __END__

26
psalm.xml Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<psalm
errorLevel="8"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
autoloader="psalm-config.php"
findUnusedBaselineEntry="true"
findUnusedCode="true"
>
<projectFiles>
<file name="phpstan-conditional.php" />
<file name="phpstan-bootstrap.php" />
<directory name="www" />
<ignoreFiles>
<directory name="www/templates_c" />
<directory name="www/cache" />
<directory name="www/log" />
<directory name="www/media" />
<directory name="www/tmp" />
<directory name="www/vendor" />
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

23
vendor/amphp/amp/LICENSE vendored Normal file
View File

@@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2015-2019 amphp
Copyright (c) 2016 PHP Asynchronous Interoperability Group
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

74
vendor/amphp/amp/composer.json vendored Normal file
View File

@@ -0,0 +1,74 @@
{
"name": "amphp/amp",
"homepage": "https://amphp.org/amp",
"description": "A non-blocking concurrency framework for PHP applications.",
"keywords": [
"async",
"asynchronous",
"concurrency",
"promise",
"awaitable",
"future",
"non-blocking",
"event",
"event-loop"
],
"license": "MIT",
"authors": [
{
"name": "Daniel Lowrey",
"email": "rdlowrey@php.net"
},
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
},
{
"name": "Bob Weinand",
"email": "bobwei9@hotmail.com"
},
{
"name": "Niklas Keller",
"email": "me@kelunik.com"
}
],
"require": {
"php": ">=7.1"
},
"require-dev": {
"ext-json": "*",
"amphp/phpunit-util": "^1",
"amphp/php-cs-fixer-config": "dev-master",
"react/promise": "^2",
"phpunit/phpunit": "^7 | ^8 | ^9",
"psalm/phar": "^3.11@dev",
"jetbrains/phpstorm-stubs": "^2019.3"
},
"autoload": {
"psr-4": {
"Amp\\": "lib"
},
"files": [
"lib/functions.php",
"lib/Internal/functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Amp\\Test\\": "test"
}
},
"support": {
"issues": "https://github.com/amphp/amp/issues",
"irc": "irc://irc.freenode.org/amphp"
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"scripts": {
"test": "@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit",
"code-style": "@php ./vendor/bin/php-cs-fixer fix"
}
}

80
vendor/amphp/amp/lib/CallableMaker.php vendored Normal file
View File

@@ -0,0 +1,80 @@
<?php
namespace Amp;
// @codeCoverageIgnoreStart
if (\PHP_VERSION_ID < 70100) {
/** @psalm-suppress DuplicateClass */
trait CallableMaker
{
/** @var \ReflectionClass */
private static $__reflectionClass;
/** @var \ReflectionMethod[] */
private static $__reflectionMethods = [];
/**
* Creates a callable from a protected or private instance method that may be invoked by callers requiring a
* publicly invokable callback.
*
* @param string $method Instance method name.
*
* @return callable
*
* @psalm-suppress MixedInferredReturnType
*/
private function callableFromInstanceMethod(string $method): callable
{
if (!isset(self::$__reflectionMethods[$method])) {
if (self::$__reflectionClass === null) {
self::$__reflectionClass = new \ReflectionClass(self::class);
}
self::$__reflectionMethods[$method] = self::$__reflectionClass->getMethod($method);
}
return self::$__reflectionMethods[$method]->getClosure($this);
}
/**
* Creates a callable from a protected or private static method that may be invoked by methods requiring a
* publicly invokable callback.
*
* @param string $method Static method name.
*
* @return callable
*
* @psalm-suppress MixedInferredReturnType
*/
private static function callableFromStaticMethod(string $method): callable
{
if (!isset(self::$__reflectionMethods[$method])) {
if (self::$__reflectionClass === null) {
self::$__reflectionClass = new \ReflectionClass(self::class);
}
self::$__reflectionMethods[$method] = self::$__reflectionClass->getMethod($method);
}
return self::$__reflectionMethods[$method]->getClosure();
}
}
} else {
/** @psalm-suppress DuplicateClass */
trait CallableMaker
{
/**
* @deprecated Use \Closure::fromCallable() instead of this method in PHP 7.1.
*/
private function callableFromInstanceMethod(string $method): callable
{
return \Closure::fromCallable([$this, $method]);
}
/**
* @deprecated Use \Closure::fromCallable() instead of this method in PHP 7.1.
*/
private static function callableFromStaticMethod(string $method): callable
{
return \Closure::fromCallable([self::class, $method]);
}
}
} // @codeCoverageIgnoreEnd

View File

@@ -0,0 +1,49 @@
<?php
namespace Amp;
/**
* Cancellation tokens are simple objects that allow registering handlers to subscribe to cancellation requests.
*/
interface CancellationToken
{
/**
* Subscribes a new handler to be invoked on a cancellation request.
*
* This handler might be invoked immediately in case the token has already been cancelled. Returned generators will
* automatically be run as coroutines. Any unhandled exceptions will be throw into the event loop.
*
* @param callable(CancelledException) $callback Callback to be invoked on a cancellation request. Will receive a
* `CancelledException` as first argument that may be used to fail the operation's promise.
*
* @return string Identifier that can be used to cancel the subscription.
*/
public function subscribe(callable $callback): string;
/**
* Unsubscribes a previously registered handler.
*
* The handler will no longer be called as long as this method isn't invoked from a subscribed callback.
*
* @param string $id
*
* @return void
*/
public function unsubscribe(string $id);
/**
* Returns whether cancellation has been requested yet.
*
* @return bool
*/
public function isRequested(): bool;
/**
* Throws the `CancelledException` if cancellation has been requested, otherwise does nothing.
*
* @return void
*
* @throws CancelledException
*/
public function throwIfRequested();
}

View File

@@ -0,0 +1,163 @@
<?php
namespace Amp;
use React\Promise\PromiseInterface as ReactPromise;
use function Amp\Promise\rethrow;
/**
* A cancellation token source provides a mechanism to cancel operations.
*
* Cancellation of operation works by creating a cancellation token source and passing the corresponding token when
* starting the operation. To cancel the operation, invoke `CancellationTokenSource::cancel()`.
*
* Any operation can decide what to do on a cancellation request, it has "don't care" semantics. An operation SHOULD be
* aborted, but MAY continue. Example: A DNS client might continue to receive and cache the response, as the query has
* been sent anyway. An HTTP client would usually close a connection, but might not do so in case a response is close to
* be fully received to reuse the connection.
*
* **Example**
*
* ```php
* $tokenSource = new CancellationTokenSource;
* $token = $tokenSource->getToken();
*
* $response = yield $httpClient->request("https://example.com/stream", $token);
* $responseBody = $response->getBody();
*
* while (($chunk = yield $response->read()) !== null) {
* // consume $chunk
*
* if ($noLongerInterested) {
* $cancellationTokenSource->cancel();
* break;
* }
* }
* ```
*
* @see CancellationToken
* @see CancelledException
*/
final class CancellationTokenSource
{
/** @var CancellationToken */
private $token;
/** @var callable|null */
private $onCancel;
public function __construct()
{
$onCancel = null;
$this->token = new class($onCancel) implements CancellationToken {
/** @var string */
private $nextId = "a";
/** @var callable[] */
private $callbacks = [];
/** @var \Throwable|null */
private $exception;
/**
* @param mixed $onCancel
* @param-out callable $onCancel
*/
public function __construct(&$onCancel)
{
/** @psalm-suppress MissingClosureReturnType We still support PHP 7.0 */
$onCancel = function (\Throwable $exception) {
$this->exception = $exception;
$callbacks = $this->callbacks;
$this->callbacks = [];
foreach ($callbacks as $callback) {
$this->invokeCallback($callback);
}
};
}
/**
* @param callable $callback
*
* @return void
*/
private function invokeCallback(callable $callback)
{
// No type declaration to prevent exception outside the try!
try {
/** @var mixed $result */
$result = $callback($this->exception);
if ($result instanceof \Generator) {
/** @psalm-var \Generator<mixed, Promise|ReactPromise|(Promise|ReactPromise)[], mixed, mixed> $result */
$result = new Coroutine($result);
}
if ($result instanceof Promise || $result instanceof ReactPromise) {
rethrow($result);
}
} catch (\Throwable $exception) {
Loop::defer(static function () use ($exception) {
throw $exception;
});
}
}
public function subscribe(callable $callback): string
{
$id = $this->nextId++;
if ($this->exception) {
$this->invokeCallback($callback);
} else {
$this->callbacks[$id] = $callback;
}
return $id;
}
public function unsubscribe(string $id)
{
unset($this->callbacks[$id]);
}
public function isRequested(): bool
{
return isset($this->exception);
}
public function throwIfRequested()
{
if (isset($this->exception)) {
throw $this->exception;
}
}
};
$this->onCancel = $onCancel;
}
public function getToken(): CancellationToken
{
return $this->token;
}
/**
* @param \Throwable|null $previous Exception to be used as the previous exception to CancelledException.
*
* @return void
*/
public function cancel(\Throwable $previous = null)
{
if ($this->onCancel === null) {
return;
}
$onCancel = $this->onCancel;
$this->onCancel = null;
$onCancel(new CancelledException($previous));
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Amp;
/**
* Will be thrown in case an operation is cancelled.
*
* @see CancellationToken
* @see CancellationTokenSource
*/
class CancelledException extends \Exception
{
public function __construct(\Throwable $previous = null)
{
parent::__construct("The operation was cancelled", 0, $previous);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Amp;
final class CombinedCancellationToken implements CancellationToken
{
/** @var array{0: CancellationToken, 1: string}[] */
private $tokens = [];
/** @var string */
private $nextId = "a";
/** @var callable[] */
private $callbacks = [];
/** @var CancelledException|null */
private $exception;
public function __construct(CancellationToken ...$tokens)
{
$thatException = &$this->exception;
$thatCallbacks = &$this->callbacks;
foreach ($tokens as $token) {
$id = $token->subscribe(static function (CancelledException $exception) use (&$thatException, &$thatCallbacks) {
$thatException = $exception;
$callbacks = $thatCallbacks;
$thatCallbacks = [];
foreach ($callbacks as $callback) {
asyncCall($callback, $thatException);
}
});
$this->tokens[] = [$token, $id];
}
}
public function __destruct()
{
foreach ($this->tokens as list($token, $id)) {
/** @var CancellationToken $token */
$token->unsubscribe($id);
}
}
/** @inheritdoc */
public function subscribe(callable $callback): string
{
$id = $this->nextId++;
if ($this->exception) {
asyncCall($callback, $this->exception);
} else {
$this->callbacks[$id] = $callback;
}
return $id;
}
/** @inheritdoc */
public function unsubscribe(string $id)
{
unset($this->callbacks[$id]);
}
/** @inheritdoc */
public function isRequested(): bool
{
foreach ($this->tokens as list($token)) {
if ($token->isRequested()) {
return true;
}
}
return false;
}
/** @inheritdoc */
public function throwIfRequested()
{
foreach ($this->tokens as list($token)) {
$token->throwIfRequested();
}
}
}

160
vendor/amphp/amp/lib/Coroutine.php vendored Normal file
View File

@@ -0,0 +1,160 @@
<?php
namespace Amp;
use React\Promise\PromiseInterface as ReactPromise;
/**
* Creates a promise from a generator function yielding promises.
*
* When a promise is yielded, execution of the generator is interrupted until the promise is resolved. A success
* value is sent into the generator, while a failure reason is thrown into the generator. Using a coroutine,
* asynchronous code can be written without callbacks and be structured like synchronous code.
*
* @template-covariant TReturn
* @template-implements Promise<TReturn>
*/
final class Coroutine implements Promise
{
use Internal\Placeholder;
/**
* Attempts to transform the non-promise yielded from the generator into a promise, otherwise returns an instance
* `Amp\Failure` failed with an instance of `Amp\InvalidYieldError`.
*
* @param mixed $yielded Non-promise yielded from generator.
* @param \Generator $generator No type for performance, we already know the type.
*
* @return Promise
*/
private static function transform($yielded, $generator): Promise
{
$exception = null; // initialize here, see https://github.com/vimeo/psalm/issues/2951
try {
if (\is_array($yielded)) {
return Promise\all($yielded);
}
if ($yielded instanceof ReactPromise) {
return Promise\adapt($yielded);
}
// No match, continue to returning Failure below.
} catch (\Throwable $exception) {
// Conversion to promise failed, fall-through to returning Failure below.
}
return new Failure(new InvalidYieldError(
$generator,
\sprintf(
"Unexpected yield; Expected an instance of %s or %s or an array of such instances",
Promise::class,
ReactPromise::class
),
$exception
));
}
/**
* @param \Generator $generator
* @psalm-param \Generator<mixed,Promise|ReactPromise|array<array-key,
* Promise|ReactPromise>,mixed,Promise<TReturn>|ReactPromise|TReturn> $generator
*/
public function __construct(\Generator $generator)
{
try {
$yielded = $generator->current();
if (!$yielded instanceof Promise) {
if (!$generator->valid()) {
$this->resolve($generator->getReturn());
return;
}
$yielded = self::transform($yielded, $generator);
}
} catch (\Throwable $exception) {
$this->fail($exception);
return;
}
/**
* @param \Throwable|null $e Exception to be thrown into the generator.
* @param mixed $v Value to be sent into the generator.
*
* @return void
*
* @psalm-suppress MissingClosureParamType
* @psalm-suppress MissingClosureReturnType
*/
$onResolve = function (\Throwable $e = null, $v) use ($generator, &$onResolve) {
/** @var bool $immediate Used to control iterative coroutine continuation. */
static $immediate = true;
/** @var \Throwable|null $exception Promise failure reason when executing next coroutine step, null at all other times. */
static $exception;
/** @var mixed $value Promise success value when executing next coroutine step, null at all other times. */
static $value;
$exception = $e;
/** @psalm-suppress MixedAssignment */
$value = $v;
if (!$immediate) {
$immediate = true;
return;
}
try {
try {
do {
if ($exception) {
// Throw exception at current execution point.
$yielded = $generator->throw($exception);
} else {
// Send the new value and execute to next yield statement.
$yielded = $generator->send($value);
}
if (!$yielded instanceof Promise) {
if (!$generator->valid()) {
$this->resolve($generator->getReturn());
$onResolve = null;
return;
}
$yielded = self::transform($yielded, $generator);
}
$immediate = false;
$yielded->onResolve($onResolve);
} while ($immediate);
$immediate = true;
} catch (\Throwable $exception) {
$this->fail($exception);
$onResolve = null;
} finally {
$exception = null;
$value = null;
}
} catch (\Throwable $e) {
Loop::defer(static function () use ($e) {
throw $e;
});
}
};
try {
$yielded->onResolve($onResolve);
unset($generator, $yielded, $onResolve);
} catch (\Throwable $e) {
Loop::defer(static function () use ($e) {
throw $e;
});
}
}
}

76
vendor/amphp/amp/lib/Deferred.php vendored Normal file
View File

@@ -0,0 +1,76 @@
<?php
namespace Amp;
/**
* Deferred is a container for a promise that is resolved using the resolve() and fail() methods of this object.
* The contained promise may be accessed using the promise() method. This object should not be part of a public
* API, but used internally to create and resolve a promise.
*
* @template TValue
*/
final class Deferred
{
/** @var Promise<TValue> Has public resolve and fail methods. */
private $resolver;
/** @var Promise<TValue> Hides placeholder methods */
private $promise;
public function __construct()
{
$this->resolver = new class implements Promise {
use Internal\Placeholder {
resolve as public;
fail as public;
isResolved as public;
}
};
$this->promise = new Internal\PrivatePromise($this->resolver);
}
/**
* @return Promise<TValue>
*/
public function promise(): Promise
{
return $this->promise;
}
/**
* Fulfill the promise with the given value.
*
* @param mixed $value
*
* @psalm-param TValue|Promise<TValue> $value
*
* @return void
*/
public function resolve($value = null)
{
/** @psalm-suppress UndefinedInterfaceMethod */
$this->resolver->resolve($value);
}
/**
* Fails the promise the the given reason.
*
* @param \Throwable $reason
*
* @return void
*/
public function fail(\Throwable $reason)
{
/** @psalm-suppress UndefinedInterfaceMethod */
$this->resolver->fail($reason);
}
/**
* @return bool True if the promise has been resolved.
*/
public function isResolved(): bool
{
return $this->resolver->isResolved();
}
}

58
vendor/amphp/amp/lib/Delayed.php vendored Normal file
View File

@@ -0,0 +1,58 @@
<?php
namespace Amp;
/**
* Creates a promise that resolves itself with a given value after a number of milliseconds.
*
* @template-covariant TReturn
* @template-implements Promise<TReturn>
*/
final class Delayed implements Promise
{
use Internal\Placeholder;
/** @var string|null Event loop watcher identifier. */
private $watcher;
/**
* @param int $time Milliseconds before succeeding the promise.
* @param TReturn $value Succeed the promise with this value.
*/
public function __construct(int $time, $value = null)
{
$this->watcher = Loop::delay($time, function () use ($value) {
$this->watcher = null;
$this->resolve($value);
});
}
/**
* References the internal watcher in the event loop, keeping the loop running while this promise is pending.
*
* @return self
*/
public function reference(): self
{
if ($this->watcher !== null) {
Loop::reference($this->watcher);
}
return $this;
}
/**
* Unreferences the internal watcher in the event loop, allowing the loop to stop while this promise is pending if
* no other events are pending in the loop.
*
* @return self
*/
public function unreference(): self
{
if ($this->watcher !== null) {
Loop::unreference($this->watcher);
}
return $this;
}
}

84
vendor/amphp/amp/lib/Emitter.php vendored Normal file
View File

@@ -0,0 +1,84 @@
<?php
namespace Amp;
/**
* Emitter is a container for an iterator that can emit values using the emit() method and completed using the
* complete() and fail() methods of this object. The contained iterator may be accessed using the iterate()
* method. This object should not be part of a public API, but used internally to create and emit values to an
* iterator.
*
* @template TValue
*/
final class Emitter
{
/** @var Iterator<TValue> Has public emit, complete, and fail methods. */
private $emitter;
/** @var Iterator<TValue> Hides producer methods. */
private $iterator;
public function __construct()
{
$this->emitter = new class implements Iterator {
use Internal\Producer {
emit as public;
complete as public;
fail as public;
}
};
$this->iterator = new Internal\PrivateIterator($this->emitter);
}
/**
* @return Iterator
* @psalm-return Iterator<TValue>
*/
public function iterate(): Iterator
{
return $this->iterator;
}
/**
* Emits a value to the iterator.
*
* @param mixed $value
*
* @psalm-param TValue $value
*
* @return Promise
* @psalm-return Promise<null>
* @psalm-suppress MixedInferredReturnType
* @psalm-suppress MixedReturnStatement
*/
public function emit($value): Promise
{
/** @psalm-suppress UndefinedInterfaceMethod */
return $this->emitter->emit($value);
}
/**
* Completes the iterator.
*
* @return void
*/
public function complete()
{
/** @psalm-suppress UndefinedInterfaceMethod */
$this->emitter->complete();
}
/**
* Fails the iterator with the given reason.
*
* @param \Throwable $reason
*
* @return void
*/
public function fail(\Throwable $reason)
{
/** @psalm-suppress UndefinedInterfaceMethod */
$this->emitter->fail($reason);
}
}

52
vendor/amphp/amp/lib/Failure.php vendored Normal file
View File

@@ -0,0 +1,52 @@
<?php
namespace Amp;
use React\Promise\PromiseInterface as ReactPromise;
/**
* Creates a failed promise using the given exception.
*
* @template-covariant TValue
* @template-implements Promise<TValue>
*/
final class Failure implements Promise
{
/** @var \Throwable $exception */
private $exception;
/**
* @param \Throwable $exception Rejection reason.
*/
public function __construct(\Throwable $exception)
{
$this->exception = $exception;
}
/**
* {@inheritdoc}
*/
public function onResolve(callable $onResolved)
{
try {
/** @var mixed $result */
$result = $onResolved($this->exception, null);
if ($result === null) {
return;
}
if ($result instanceof \Generator) {
$result = new Coroutine($result);
}
if ($result instanceof Promise || $result instanceof ReactPromise) {
Promise\rethrow($result);
}
} catch (\Throwable $exception) {
Loop::defer(static function () use ($exception) {
throw $exception;
});
}
}
}

View File

@@ -0,0 +1,187 @@
<?php
namespace Amp\Internal;
use Amp\Coroutine;
use Amp\Failure;
use Amp\Loop;
use Amp\Promise;
use React\Promise\PromiseInterface as ReactPromise;
/**
* Trait used by Promise implementations. Do not use this trait in your code, instead compose your class from one of
* the available classes implementing \Amp\Promise.
*
* @internal
*/
trait Placeholder
{
/** @var bool */
private $resolved = false;
/** @var mixed */
private $result;
/** @var ResolutionQueue|null|callable(\Throwable|null, mixed): (Promise|\React\Promise\PromiseInterface|\Generator<mixed,
* Promise|\React\Promise\PromiseInterface|array<array-key, Promise|\React\Promise\PromiseInterface>, mixed,
* mixed>|null)|callable(\Throwable|null, mixed): void */
private $onResolved;
/** @var null|array */
private $resolutionTrace;
/**
* @inheritdoc
*/
public function onResolve(callable $onResolved)
{
if ($this->resolved) {
if ($this->result instanceof Promise) {
$this->result->onResolve($onResolved);
return;
}
try {
/** @var mixed $result */
$result = $onResolved(null, $this->result);
if ($result === null) {
return;
}
if ($result instanceof \Generator) {
$result = new Coroutine($result);
}
if ($result instanceof Promise || $result instanceof ReactPromise) {
Promise\rethrow($result);
}
} catch (\Throwable $exception) {
Loop::defer(static function () use ($exception) {
throw $exception;
});
}
return;
}
if (null === $this->onResolved) {
$this->onResolved = $onResolved;
return;
}
if (!$this->onResolved instanceof ResolutionQueue) {
/** @psalm-suppress InternalClass */
$this->onResolved = new ResolutionQueue($this->onResolved);
}
/** @psalm-suppress InternalMethod */
$this->onResolved->push($onResolved);
}
public function __destruct()
{
try {
$this->result = null;
} catch (\Throwable $e) {
Loop::defer(static function () use ($e) {
throw $e;
});
}
}
/**
* @param mixed $value
*
* @return void
*
* @throws \Error Thrown if the promise has already been resolved.
*/
private function resolve($value = null)
{
if ($this->resolved) {
$message = "Promise has already been resolved";
if (isset($this->resolutionTrace)) {
$trace = formatStacktrace($this->resolutionTrace);
$message .= ". Previous resolution trace:\n\n{$trace}\n\n";
} else {
// @codeCoverageIgnoreStart
$message .= ", define environment variable AMP_DEBUG or const AMP_DEBUG = true and enable assertions "
. "for a stacktrace of the previous resolution.";
// @codeCoverageIgnoreEnd
}
throw new \Error($message);
}
\assert((function () {
$env = \getenv("AMP_DEBUG") ?: "0";
if (($env !== "0" && $env !== "false") || (\defined("AMP_DEBUG") && \AMP_DEBUG)) {
$trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
\array_shift($trace); // remove current closure
$this->resolutionTrace = $trace;
}
return true;
})());
if ($value instanceof ReactPromise) {
$value = Promise\adapt($value);
}
$this->resolved = true;
$this->result = $value;
if ($this->onResolved === null) {
return;
}
$onResolved = $this->onResolved;
$this->onResolved = null;
if ($this->result instanceof Promise) {
$this->result->onResolve($onResolved);
return;
}
try {
/** @var mixed $result */
$result = $onResolved(null, $this->result);
$onResolved = null; // allow garbage collection of $onResolved, to catch any exceptions from destructors
if ($result === null) {
return;
}
if ($result instanceof \Generator) {
$result = new Coroutine($result);
}
if ($result instanceof Promise || $result instanceof ReactPromise) {
Promise\rethrow($result);
}
} catch (\Throwable $exception) {
Loop::defer(static function () use ($exception) {
throw $exception;
});
}
}
/**
* @param \Throwable $reason Failure reason.
*
* @return void
*/
private function fail(\Throwable $reason)
{
$this->resolve(new Failure($reason));
}
/**
* @return bool True if the placeholder has been resolved.
*/
private function isResolved(): bool
{
return $this->resolved;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Amp\Internal;
use Amp\Iterator;
use Amp\Promise;
/**
* Wraps an Iterator instance that has public methods to emit, complete, and fail into an object that only allows
* access to the public API methods.
*
* @template-covariant TValue
* @template-implements Iterator<TValue>
*/
final class PrivateIterator implements Iterator
{
/** @var Iterator<TValue> */
private $iterator;
/**
* @param Iterator $iterator
*
* @psalm-param Iterator<TValue> $iterator
*/
public function __construct(Iterator $iterator)
{
$this->iterator = $iterator;
}
/**
* @return Promise<bool>
*/
public function advance(): Promise
{
return $this->iterator->advance();
}
/**
* @psalm-return TValue
*/
public function getCurrent()
{
return $this->iterator->getCurrent();
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Amp\Internal;
use Amp\Promise;
/**
* Wraps a Promise instance that has public methods to resolve and fail the promise into an object that only allows
* access to the public API methods.
*/
final class PrivatePromise implements Promise
{
/** @var Promise */
private $promise;
public function __construct(Promise $promise)
{
$this->promise = $promise;
}
public function onResolve(callable $onResolved)
{
$this->promise->onResolve($onResolved);
}
}

View File

@@ -0,0 +1,212 @@
<?php
namespace Amp\Internal;
use Amp\Deferred;
use Amp\Failure;
use Amp\Promise;
use Amp\Success;
use React\Promise\PromiseInterface as ReactPromise;
/**
* Trait used by Iterator implementations. Do not use this trait in your code, instead compose your class from one of
* the available classes implementing \Amp\Iterator.
* Note that it is the responsibility of the user of this trait to ensure that listeners have a chance to listen first
* before emitting values.
*
* @internal
* @template-covariant TValue
*/
trait Producer
{
/** @var Promise|null */
private $complete;
/** @var mixed[] */
private $values = [];
/** @var Deferred[] */
private $backPressure = [];
/** @var int */
private $consumePosition = -1;
/** @var int */
private $emitPosition = -1;
/** @var Deferred|null */
private $waiting;
/** @var null|array */
private $resolutionTrace;
/**
* {@inheritdoc}
*
* @return Promise<bool>
*/
public function advance(): Promise
{
if ($this->waiting !== null) {
throw new \Error("The prior promise returned must resolve before invoking this method again");
}
unset($this->values[$this->consumePosition]);
$position = ++$this->consumePosition;
if (\array_key_exists($position, $this->values)) {
\assert(isset($this->backPressure[$position]));
$deferred = $this->backPressure[$position];
unset($this->backPressure[$position]);
$deferred->resolve();
return new Success(true);
}
if ($this->complete) {
return $this->complete;
}
$this->waiting = new Deferred;
return $this->waiting->promise();
}
/**
* {@inheritdoc}
*
* @return TValue
*/
public function getCurrent()
{
if (empty($this->values) && $this->complete) {
throw new \Error("The iterator has completed");
}
if (!\array_key_exists($this->consumePosition, $this->values)) {
throw new \Error("Promise returned from advance() must resolve before calling this method");
}
return $this->values[$this->consumePosition];
}
/**
* Emits a value from the iterator. The returned promise is resolved once the emitted value has been consumed.
*
* @param mixed $value
*
* @return Promise
* @psalm-return Promise<null>
*
* @throws \Error If the iterator has completed.
*/
private function emit($value): Promise
{
if ($this->complete) {
throw new \Error("Iterators cannot emit values after calling complete");
}
if ($value instanceof ReactPromise) {
$value = Promise\adapt($value);
}
if ($value instanceof Promise) {
$deferred = new Deferred;
$value->onResolve(function ($e, $v) use ($deferred) {
if ($this->complete) {
$deferred->fail(
new \Error("The iterator was completed before the promise result could be emitted")
);
return;
}
if ($e) {
$this->fail($e);
$deferred->fail($e);
return;
}
$deferred->resolve($this->emit($v));
});
return $deferred->promise();
}
$position = ++$this->emitPosition;
$this->values[$position] = $value;
if ($this->waiting !== null) {
$waiting = $this->waiting;
$this->waiting = null;
$waiting->resolve(true);
return new Success; // Consumer was already waiting for a new value, so back-pressure is unnecessary.
}
$this->backPressure[$position] = $pressure = new Deferred;
return $pressure->promise();
}
/**
* Completes the iterator.
*
* @return void
*
* @throws \Error If the iterator has already been completed.
*/
private function complete()
{
if ($this->complete) {
$message = "Iterator has already been completed";
if (isset($this->resolutionTrace)) {
$trace = formatStacktrace($this->resolutionTrace);
$message .= ". Previous completion trace:\n\n{$trace}\n\n";
} else {
// @codeCoverageIgnoreStart
$message .= ", define environment variable AMP_DEBUG or const AMP_DEBUG = true and enable assertions "
. "for a stacktrace of the previous resolution.";
// @codeCoverageIgnoreEnd
}
throw new \Error($message);
}
\assert((function () {
$env = \getenv("AMP_DEBUG") ?: "0";
if (($env !== "0" && $env !== "false") || (\defined("AMP_DEBUG") && \AMP_DEBUG)) {
$trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
\array_shift($trace); // remove current closure
$this->resolutionTrace = $trace;
}
return true;
})());
$this->complete = new Success(false);
if ($this->waiting !== null) {
$waiting = $this->waiting;
$this->waiting = null;
$waiting->resolve($this->complete);
}
}
/**
* @param \Throwable $exception
*
* @return void
*/
private function fail(\Throwable $exception)
{
$this->complete = new Failure($exception);
if ($this->waiting !== null) {
$waiting = $this->waiting;
$this->waiting = null;
$waiting->resolve($this->complete);
}
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Amp\Internal;
use Amp\Coroutine;
use Amp\Loop;
use Amp\Promise;
use React\Promise\PromiseInterface as ReactPromise;
/**
* Stores a set of functions to be invoked when a promise is resolved.
*
* @internal
* @psalm-internal Amp\Internal
*/
class ResolutionQueue
{
/** @var array<array-key, callable(\Throwable|null, mixed): (Promise|\React\Promise\PromiseInterface|\Generator<mixed,
* Promise|\React\Promise\PromiseInterface|array<array-key, Promise|\React\Promise\PromiseInterface>, mixed,
* mixed>|null) | callable(\Throwable|null, mixed): void> */
private $queue = [];
/**
* @param callable|null $callback Initial callback to add to queue.
*
* @psalm-param null|callable(\Throwable|null, mixed): (Promise|\React\Promise\PromiseInterface|\Generator<mixed,
* Promise|\React\Promise\PromiseInterface|array<array-key, Promise|\React\Promise\PromiseInterface>, mixed,
* mixed>|null) | callable(\Throwable|null, mixed): void $callback
*/
public function __construct(callable $callback = null)
{
if ($callback !== null) {
$this->push($callback);
}
}
/**
* Unrolls instances of self to avoid blowing up the call stack on resolution.
*
* @param callable $callback
*
* @psalm-param callable(\Throwable|null, mixed): (Promise|\React\Promise\PromiseInterface|\Generator<mixed,
* Promise|\React\Promise\PromiseInterface|array<array-key, Promise|\React\Promise\PromiseInterface>, mixed,
* mixed>|null) | callable(\Throwable|null, mixed): void $callback
*
* @return void
*/
public function push(callable $callback)
{
if ($callback instanceof self) {
$this->queue = \array_merge($this->queue, $callback->queue);
return;
}
$this->queue[] = $callback;
}
/**
* Calls each callback in the queue, passing the provided values to the function.
*
* @param \Throwable|null $exception
* @param mixed $value
*
* @return void
*/
public function __invoke($exception, $value)
{
foreach ($this->queue as $callback) {
try {
$result = $callback($exception, $value);
if ($result === null) {
continue;
}
if ($result instanceof \Generator) {
$result = new Coroutine($result);
}
if ($result instanceof Promise || $result instanceof ReactPromise) {
Promise\rethrow($result);
}
} catch (\Throwable $exception) {
Loop::defer(static function () use ($exception) {
throw $exception;
});
}
}
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace Amp\Internal;
/**
* Formats a stacktrace obtained via `debug_backtrace()`.
*
* @param array<array{file?: string, line: int, type?: string, class: string, function: string}> $trace Output of
* `debug_backtrace()`.
*
* @return string Formatted stacktrace.
*
* @codeCoverageIgnore
* @internal
*/
function formatStacktrace(array $trace): string
{
return \implode("\n", \array_map(static function ($e, $i) {
$line = "#{$i} ";
if (isset($e["file"])) {
$line .= "{$e['file']}:{$e['line']} ";
}
if (isset($e["type"])) {
$line .= $e["class"] . $e["type"];
}
return $line . $e["function"] . "()";
}, $trace, \array_keys($trace)));
}
/**
* Creates a `TypeError` with a standardized error message.
*
* @param string[] $expected Expected types.
* @param mixed $given Given value.
*
* @return \TypeError
*
* @internal
*/
function createTypeError(array $expected, $given): \TypeError
{
$givenType = \is_object($given) ? \sprintf("instance of %s", \get_class($given)) : \gettype($given);
if (\count($expected) === 1) {
$expectedType = "Expected the following type: " . \array_pop($expected);
} else {
$expectedType = "Expected one of the following types: " . \implode(", ", $expected);
}
return new \TypeError("{$expectedType}; {$givenType} given");
}
/**
* Returns the current time relative to an arbitrary point in time.
*
* @return int Time in milliseconds.
*/
function getCurrentTime(): int
{
/** @var int|null $startTime */
static $startTime;
/** @var int|null $nextWarning */
static $nextWarning;
if (\PHP_INT_SIZE === 4) {
// @codeCoverageIgnoreStart
if ($startTime === null) {
$startTime = \PHP_VERSION_ID >= 70300 ? \hrtime(false)[0] : \time();
$nextWarning = \PHP_INT_MAX - 86400 * 7;
}
if (\PHP_VERSION_ID >= 70300) {
list($seconds, $nanoseconds) = \hrtime(false);
$seconds -= $startTime;
if ($seconds >= $nextWarning) {
$timeToOverflow = (\PHP_INT_MAX - $seconds * 1000) / 1000;
\trigger_error(
"getCurrentTime() will overflow in $timeToOverflow seconds, please restart the process before that. " .
"You're using a 32 bit version of PHP, so time will overflow about every 24 days. Regular restarts are required.",
\E_USER_WARNING
);
/** @psalm-suppress PossiblyNullOperand */
$nextWarning += 600; // every 10 minutes
}
return (int) ($seconds * 1000 + $nanoseconds / 1000000);
}
$seconds = \microtime(true) - $startTime;
if ($seconds >= $nextWarning) {
$timeToOverflow = (\PHP_INT_MAX - $seconds * 1000) / 1000;
\trigger_error(
"getCurrentTime() will overflow in $timeToOverflow seconds, please restart the process before that. " .
"You're using a 32 bit version of PHP, so time will overflow about every 24 days. Regular restarts are required.",
\E_USER_WARNING
);
/** @psalm-suppress PossiblyNullOperand */
$nextWarning += 600; // every 10 minutes
}
return (int) ($seconds * 1000);
// @codeCoverageIgnoreEnd
}
if (\PHP_VERSION_ID >= 70300) {
list($seconds, $nanoseconds) = \hrtime(false);
return (int) ($seconds * 1000 + $nanoseconds / 1000000);
}
return (int) (\microtime(true) * 1000);
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Amp;
class InvalidYieldError extends \Error
{
/**
* @param \Generator $generator
* @param string $prefix
* @param \Throwable|null $previous
*/
public function __construct(\Generator $generator, string $prefix, \Throwable $previous = null)
{
$yielded = $generator->current();
$prefix .= \sprintf(
"; %s yielded at key %s",
\is_object($yielded) ? \get_class($yielded) : \gettype($yielded),
\var_export($generator->key(), true)
);
if (!$generator->valid()) {
parent::__construct($prefix, 0, $previous);
return;
}
$reflGen = new \ReflectionGenerator($generator);
$exeGen = $reflGen->getExecutingGenerator();
if ($isSubgenerator = ($exeGen !== $generator)) {
$reflGen = new \ReflectionGenerator($exeGen);
}
parent::__construct(\sprintf(
"%s on line %s in %s",
$prefix,
$reflGen->getExecutingLine(),
$reflGen->getExecutingFile()
), 0, $previous);
}
}

34
vendor/amphp/amp/lib/Iterator.php vendored Normal file
View File

@@ -0,0 +1,34 @@
<?php
namespace Amp;
/**
* Defines an asynchronous iterator over a set of values that is designed to be used within a coroutine.
*
* @template-covariant TValue
*/
interface Iterator
{
/**
* Succeeds with true if an emitted value is available by calling getCurrent() or false if the iterator has
* resolved. If the iterator fails, the returned promise will fail with the same exception.
*
* @return Promise
* @psalm-return Promise<bool>
*
* @throws \Error If the prior promise returned from this method has not resolved.
* @throws \Throwable The exception used to fail the iterator.
*/
public function advance(): Promise;
/**
* Gets the last emitted value or throws an exception if the iterator has completed.
*
* @return mixed Value emitted from the iterator.
* @psalm-return TValue
*
* @throws \Error If the iterator has resolved or advance() was not called before calling this method.
* @throws \Throwable The exception used to fail the iterator.
*/
public function getCurrent();
}

44
vendor/amphp/amp/lib/LazyPromise.php vendored Normal file
View File

@@ -0,0 +1,44 @@
<?php
namespace Amp;
/**
* Creates a promise that calls $promisor only when the result of the promise is requested (i.e. onResolve() is called
* on the promise). $promisor can return a promise or any value. If $promisor throws an exception, the promise fails
* with that exception. If $promisor returns a Generator, it will be run as a coroutine.
*/
final class LazyPromise implements Promise
{
/** @var callable|null */
private $promisor;
/** @var Promise|null */
private $promise;
/**
* @param callable $promisor Function which starts an async operation, returning a Promise (or any value).
* Generators will be run as a coroutine.
*/
public function __construct(callable $promisor)
{
$this->promisor = $promisor;
}
/**
* {@inheritdoc}
*/
public function onResolve(callable $onResolved)
{
if ($this->promise === null) {
\assert($this->promisor !== null);
$provider = $this->promisor;
$this->promisor = null;
$this->promise = call($provider);
}
\assert($this->promise !== null);
$this->promise->onResolve($onResolved);
}
}

Some files were not shown because too many files have changed in this diff Show More