Compare commits

...

29 Commits

Author SHA1 Message Date
Clemens Schwaighofer
46bc5f2da6 Json phpunit tests updates, fixes in test php with ignore for deprecated 2023-08-02 16:22:09 +09:00
Clemens Schwaighofer
d70182a84e Add JSON convert array to json with always return string
Allows the same post run error check like the other way around
2023-08-02 16:12:01 +09:00
Clemens Schwaighofer
7243f69826 Email Type class returns correct "false" instead of "bool" 2023-08-02 16:08:39 +09:00
Clemens Schwaighofer
1fc144e178 Composer Workspace global packages 2023-08-02 14:52:33 +09:00
Clemens Schwaighofer
c383a7b7b7 Update convert colors to return false and not bool
All convert color either return the color value or false.
To make sure any checker knows that we only return "value" or "false"
change all return bool to false
2023-08-02 07:29:49 +09:00
Clemens Schwaighofer
69077c384c phpdoc fix for DB\IO dbGetCursorNumRows 2023-08-01 09:00:43 +09:00
Clemens Schwaighofer
cfd49947ad Bug fix in ACL\Login: mnake sure ['base'] acl is int 2023-07-26 11:48:56 +09:00
Clemens Schwaighofer
6985dc4e9d ACL\Login fix for UNIT DEFAULT return
It has to be int or null but because the SQL result is undefined (string)
it needs to be converted on return if it is a numeric value, else
null will be returned (it is the edit access id PK so it has to be
numeric)
2023-07-24 09:11:32 +09:00
Clemens Schwaighofer
5f2668b011 ACL\Login
Remove log per class flag set inside Login.
If per class logging is needed here, set that BEFORE and AFTER the class
call
2023-07-21 19:03:24 +09:00
Clemens Schwaighofer
eba1ef9c59 Init DB\IO dbh with null to avoid any problems
There could have been some problems where the dbh var was not touched
even thought it was inited.
2023-07-21 17:48:09 +09:00
Clemens Schwaighofer
8497144053 Admin\Backend level check, DB\IO Error/Warning message update
on Admin\Backend init check that the provided default acl level is valid

DB\IO warning and error drop the "db :" prefix part as this is not needed
we have [DB_ERROR] and [DB_WARNING] sub prefixes anyway, also we run
dedicated log level alerts with context
2023-07-14 15:01:46 +09:00
Clemens Schwaighofer
2006798388 Init set empty db config if db config not found 2023-07-10 08:18:49 +09:00
Clemens Schwaighofer
bf63d850ca Add new DateTime class has date range weekened method
dateRangeHasWeekend with two dates, checks if between those two dates
a weekend (sat or sun) is set
2023-07-04 11:43:27 +09:00
Clemens Schwaighofer
53e267ce24 Updates and fixes from phan/phpstan runs 2023-06-28 15:30:08 +09:00
Clemens Schwaighofer
1754ecf2ee HtmlBuilder: change all return error to Throw, update Element/StrinReplace
We do not return old style bool on error, we throw Exceptions: HtmlBuilderExcpetion

Element has more classes to set tag, id, etc with basic checks for valid data

String Replace to set strings is one array with key -> value entries

Errors thrown on index for element/replace blocks
2023-06-28 14:16:44 +09:00
Clemens Schwaighofer
3c37899a48 Rename Replace to StringReplace to match the actual content
Replace is too general. it is String Replace
2023-06-27 18:33:04 +09:00
Clemens Schwaighofer
0436cfe3da HtmlBuilder classes for Object, Array, String Replace build
Object build is a replicata from the JS one
Array is similar but build on pure Array elements
String replace is just a simple string replacer for now

General\Error for overall error handling
General\Settings for Object/Array based checks and settings
2023-06-27 18:30:26 +09:00
Clemens Schwaighofer
3606de1a00 Back add some checks for the phfo function that matches the php one 2023-06-27 18:30:00 +09:00
Clemens Schwaighofer
3081439eda Switch edit_base CSS name from ADMIN_STYLESHEET to EDIT_BASE_STYLESHEET 2023-06-27 18:29:28 +09:00
Clemens Schwaighofer
7af0e74b85 Fix phpUnit test name for Security\SymmetricEncryption 2023-06-26 14:25:36 +09:00
Clemens Schwaighofer
7748b83a6b DB\IO add stack trace to debug/error/warning calls
To add the actuall call reference for DB IO debug calls we add the
call trace as context options
2023-06-16 13:17:49 +09:00
Clemens Schwaighofer
f83293ff1a Info comments for DB\IO convert options 2023-06-13 11:47:38 +09:00
Clemens Schwaighofer
9c3be2942e phan and phpstan fixes, also add a convert flag reset to original
dbResetConvertFlag resets to the settings given on init of class
2023-06-09 18:23:28 +09:00
Clemens Schwaighofer
ee62bd98ee Add auto type convert for DB\IO
set via db options "db_convert_type" as array with "on", "json", "numeric",
"bytea"

"on" only converts know good types: "bool", "int"

"json" will convert json/jsonb to array
"bytea" will decode escaped bytea to string (note: this might change to resource)

"numeric" will convert to float.
NOTE: if a numeric number is too large a covnersion might drop data.
Use with care.

Convert flags can be chagned with dbSetConvertFlag and dbUnsetConvertFlag

All convert flags are in "DB\Options\Convert" as enum.
2023-06-09 17:01:03 +09:00
Clemens Schwaighofer
02e9610fad Add a general log method to Logger class
the params order is the actual correct one:
log level, message, context, group_id, prefix

not that group_id and prefix are only used if log level is debug

Switched debug params order for context and prefix so prefix is last
2023-06-09 16:59:22 +09:00
Clemens Schwaighofer
8a41db4649 Output\Image cleanup for phpstan 2023-06-09 12:16:49 +09:00
Clemens Schwaighofer
e27ea3dc9f DB\IO phpdoc update 2023-06-09 10:24:26 +09:00
Clemens Schwaighofer
ec4bf54d81 DB\IO phpdoc layout update
Add line between params and method description
2023-06-09 10:21:02 +09:00
Clemens Schwaighofer
ec3ca787fa Logging: prepare message only if log level is high enough
Also some clean ups on internal method call parameters
2023-06-05 09:30:26 +09:00
287 changed files with 9279 additions and 2929 deletions

View File

@@ -458,6 +458,47 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
];
}
/**
* Undocumented function
*
* @return array
*/
public function dateRangeHasWeekendProvider(): array
{
return [
'no weekend' => [
'2023-07-03',
'2023-07-04',
false
],
'start weekend sat' => [
'2023-07-01',
'2023-07-04',
true
],
'start weekend sun' => [
'2023-07-02',
'2023-07-04',
true
],
'end weekend sat' => [
'2023-07-03',
'2023-07-08',
true
],
'end weekend sun' => [
'2023-07-03',
'2023-07-09',
true
],
'long period > 6 days' => [
'2023-07-03',
'2023-07-27',
true
]
];
}
/**
* date string convert test
*
@@ -780,6 +821,29 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
$output
);
}
/**
* Undocumented function
*
* @covers ::dateRangeHasWeekend
* @dataProvider dateRangeHasWeekendProvider
* @testdox dateRangeHasWeekend $start_date and $end_date are expected weekend $expected [$_dataName]
*
* @param string $start_date
* @param string $end_date
* @param bool $expected
* @return void
*/
public function testDateRangeHasWeekend(
string $start_date,
string $end_date,
bool $expected
): void {
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::dateRangeHasWeekend($start_date, $end_date)
);
}
}
// __END__

View File

@@ -16,7 +16,7 @@ final class CoreLibsConvertJsonTest extends TestCase
/**
* test list for json convert tests
*
* @return array
* @return array<mixed>
*/
public function jsonProvider(): array
{
@@ -54,10 +54,36 @@ final class CoreLibsConvertJsonTest extends TestCase
];
}
/**
* Undocumented function
*
* @return array<mixed>
*/
public function jsonArrayProvider(): array
{
return [
'valid json' => [
[
'm' => 2,
'f' => 'sub_2'
],
'{"m":2,"f":"sub_2"}',
],
'empty json array' => [
[],
'[]'
],
'empty json hash' => [
['' => ''],
'{"":""}'
]
];
}
/**
* json error list
*
* @return array JSON error list
* @return array<mixed> JSON error list
*/
public function jsonErrorProvider(): array
{
@@ -127,7 +153,7 @@ final class CoreLibsConvertJsonTest extends TestCase
*
* @param string|null $input
* @param bool $flag
* @param array $expected
* @param array<mixed> $expected
* @return void
*/
public function testJsonConvertToArray(?string $input, bool $flag, array $expected): void
@@ -146,7 +172,8 @@ final class CoreLibsConvertJsonTest extends TestCase
* @testdox jsonGetLastError $input will be $expected_i/$expected_s [$_dataName]
*
* @param string|null $input
* @param string $expected
* @param int $expected_i
* @param string $expected_s
* @return void
*/
public function testJsonGetLastError(?string $input, int $expected_i, string $expected_s): void
@@ -161,6 +188,25 @@ final class CoreLibsConvertJsonTest extends TestCase
\CoreLibs\Convert\Json::jsonGetLastError(true)
);
}
/**
* Undocumented function
*
* @covers ::jsonConvertArrayTo
* @dataProvider jsonArrayProvider
* @testdox jsonConvertArrayTo $input (Override: $flag) will be $expected [$_dataName]
*
* @param array<mixed> $input
* @param string $expected
* @return void
*/
public function testJsonConvertArrayto(array $input, string $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Convert\Json::jsonConvertArrayTo($input)
);
}
}
// __END__

View File

@@ -38,6 +38,7 @@ namespace tests;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use CoreLibs\Logging\Logger\Level;
use CoreLibs\DB\Options\Convert;
/**
* Test class for DB\IO + DB\SQL\PgSQL
@@ -4557,6 +4558,176 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbClose();
}
// testing auto convert
/**
* Undocumented function
*
* @covers ::dbSetConvertFlag
* @testdox Check convert type works
*
* @return void
*/
public function testConvertType(): void
{
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
);
$bytea_data = $db->dbEscapeBytea(
file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'CoreLibsDBIOTest.php') ?: ''
);
$query_insert = <<<SQL
INSERT INTO table_with_primary_key (
uid,
row_int, row_numeric, row_varchar, row_varchar_literal,
row_json, row_jsonb, row_bytea, row_timestamp,
row_date, row_interval, row_array_int, row_array_varchar
) VALUES (
$1,
$2, $3, $4, $5,
$6, $7, $8, $9,
$10, $11, $12, $13
)
SQL;
$db->dbExecParams(
$query_insert,
[
'CONVERT_TYPE_TEST',
1, 1.5, 'varchar', 'varchar literla',
json_encode(['json', 'a', 1, true, 'sub' => ['b', 'c']]),
json_encode(['jsonb', 'a', 1, true, 'sub' => ['b', 'c']]),
$bytea_data, date('Y-m-d H:i:s'), date('Y-m-d'), date('H:m:s'),
'{1,2,3}', '{"a","b","c"}'
]
);
$type_layout = [
'uid' => 'string',
'row_int' => 'int',
'row_numeric' => 'float',
'row_varchar' => 'string',
'row_varchar_literal' => 'string',
'row_json' => 'json',
'row_jsonb' => 'json',
'row_bytea' => 'bytea',
'row_timestamp' => 'string',
'row_date' => 'string',
'row_interval' => 'string',
'row_array_int' => 'string',
'row_array_varchar' => 'string'
];
$query_select = <<<SQL
SELECT
uid,
row_int, row_numeric, row_varchar, row_varchar_literal,
row_json, row_jsonb, row_bytea, row_timestamp,
row_date, row_interval, row_array_int, row_array_varchar
FROM
table_with_primary_key
WHERE
uid = $1
SQL;
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
// all hast to be string
foreach ($res as $key => $value) {
$this->assertIsString($value, 'Aseert string for column: ' . $key);
}
// convert base only
$db->dbSetConvertFlag(Convert::on);
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
foreach ($res as $key => $value) {
if (is_numeric($key)) {
$name = $db->dbGetFieldName($key);
} else {
$name = $key;
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
break;
}
}
$db->dbSetConvertFlag(Convert::numeric);
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
foreach ($res as $key => $value) {
if (is_numeric($key)) {
$name = $db->dbGetFieldName($key);
} else {
$name = $key;
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
break;
case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
break;
}
}
$db->dbSetConvertFlag(Convert::json);
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
foreach ($res as $key => $value) {
if (is_numeric($key)) {
$name = $db->dbGetFieldName($key);
} else {
$name = $key;
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
break;
case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name);
break;
case 'json':
case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
break;
}
}
$db->dbSetConvertFlag(Convert::bytea);
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
foreach ($res as $key => $value) {
if (is_numeric($key)) {
$name = $db->dbGetFieldName($key);
} else {
$name = $key;
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
break;
case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name);
break;
case 'json':
case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name);
break;
case 'bytea':
// for hex types it must not start with \x
$this->assertStringStartsNotWith(
'\x',
$value,
'Aseert bytes not starts with \x for column: ' . $key . '/' . $name
);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
break;
}
}
}
// - internal read data (post exec)
// dbGetNumRows, dbGetNumFields, dbGetFieldNames,
// dbGetQuery, dbGetQueryHash, dbGetDbh
@@ -4588,7 +4759,7 @@ final class CoreLibsDBIOTest extends TestCase
. "('Foxtrott', 'Tango', 789, '1982-10-15') ",
null,
//
2,
3,
4,
['row_varchar', 'row_varchar_literal', 'row_int', 'row_date'],
['varchar', 'varchar', 'int4', 'date'],
@@ -4705,9 +4876,16 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbExecParams($query, $params);
}
$this->assertInstanceOf(
'PgSql\Result',
$db->dbGetCursor(),
'Failed assert dbGetCursor'
);
$this->assertEquals(
$compare_query ?? $query,
$db->dbGetQuery()
$db->dbGetQuery(),
'Failed assert dbGetQuery'
);
$this->assertEquals(
// perhaps move that somewhere else?
@@ -4720,7 +4898,8 @@ final class CoreLibsDBIOTest extends TestCase
($params === null ?
$db->dbGetQueryHash($query) :
$db->dbGetQueryHash($query, $params)
)
),
'Failed assertdbGetQueryHash '
);
$this->assertEquals(
$expected_rows,
@@ -4742,6 +4921,35 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbGetFieldTypes(),
'Failed assert dbGetFieldTypes'
);
// check FieldNameTypes matches
$this->assertEquals(
array_combine(
$expected_col_names,
$expected_col_types
),
$db->dbGetFieldNameTypes(),
'Failed assert dbGetFieldNameTypes'
);
// check pos matches name
// name matches type
// pos matches type
foreach ($expected_col_names as $pos => $name) {
$this->assertEquals(
$name,
$db->dbGetFieldName($pos),
'Failed assert dbGetFieldName: ' . $pos . ' => ' . $name
);
$this->assertEquals(
$expected_col_types[$pos],
$db->dbGetFieldType($name),
'Failed assert dbGetFieldType: ' . $name . ' => ' . $expected_col_types[$pos]
);
$this->assertEquals(
$expected_col_types[$pos],
$db->dbGetFieldType($pos),
'Failed assert dbGetFieldType: ' . $pos . ' => ' . $expected_col_types[$pos]
);
}
$dbh = $db->dbGetDbh();
$this->assertIsObject(
$dbh

View File

@@ -691,6 +691,11 @@ final class CoreLibsLoggingLoggingTest extends TestCase
);
}
/**
* Undocumented function
*
* @return array
*/
public function providerLoggingLevelWrite(): array
{
return [
@@ -796,6 +801,46 @@ final class CoreLibsLoggingLoggingTest extends TestCase
);
}
// check log level that writer writes in correct level
// also that non debug ignores prefix/group
/**
* Undocumented function
*
* @covers ::log
* @testdox log() general call test
*
* @return void
*/
public function testLoggingLog(): void
{
// init logger
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testLoggingLog',
'log_folder' => self::LOG_FOLDER,
'log_per_level' => true,
]);
$log_ok = $log->log(Level::Debug, 'DEBUG', group_id: 'GROUP_ID', prefix: 'PREFIX:');
$this->assertTrue($log_ok, 'assert ::log (debug) OK');
$this->assertEquals(
$log->getLogFile(),
$log->getLogFileId() . '_DEBUG.log'
);
$log_ok = $log->log(Level::Info, 'INFO', group_id: 'GROUP_ID', prefix: 'PREFIX:');
$this->assertTrue($log_ok, 'assert ::log (info) OK');
$this->assertEquals(
$log->getLogFile(),
$log->getLogFileId() . '_INFO.log'
);
}
// must test flow:
// init normal
// log -> check file name
// set per date
// log -> check file name
// and same for per_run
// deprecated calls check?
}

View File

@@ -13,7 +13,7 @@ use CoreLibs\Security\SymmetricEncryption;
* @coversDefaultClass \CoreLibs\Security\SymmetricEncryption
* @testdox \CoreLibs\Security\SymmetricEncryption method tests
*/
final class CoreLibsSecuritySymmetricEncryption extends TestCase
final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
{
/**
* Undocumented function

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Template\HtmlBuilder\Block;
/**
* Test class for Template\HtmlBuilder\Block
* @coversDefaultClass \CoreLibs\Template\HtmlBuilder\Block
* @testdox \CoreLibs\Template\HtmlBuilder\Block method tests
*/
final class CoreLibsTemplateHtmlBuilderBlockTest extends TestCase
{
public function testCreateBlock(): void
{
$el = Block::cel('div', 'id', 'content', ['css'], ['onclick' => 'foo();']);
$this->assertEquals(
'<div id="id" class="css" onclick="foo();">content</div>',
Block::buildHtml($el)
);
}
// ael
// aelx|addSub
// resetSub
// acssel/rcssel/scssel
// buildHtml
// buildHtmlFromList|printHtmlFromArray
}
// __END__

View File

@@ -0,0 +1,546 @@
<?php
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Template\HtmlBuilder\Element;
use CoreLibs\Template\HtmlBuilder\General\Error;
use CoreLibs\Template\HtmlBuilder\General\HtmlBuilderExcpetion;
/**
* Test class for Template\HtmlBuilder\Element
* @coversDefaultClass \CoreLibs\Template\HtmlBuilder\Element
* @testdox \CoreLibs\Template\HtmlBuilder\Element method tests
*/
final class CoreLibsTemplateHtmlBuilderElementTest extends TestCase
{
public function providerCreateElements(): array
{
return [
'simple div' => [
'tag' => 'div',
'id' => 'id',
'content' => 'content',
'css' => ['css'],
'options' => ['onclick' => 'foo();'],
'expected' => '<div id="id" class="css" onclick="foo();">content</div>'
],
'simple input' => [
'tag' => 'input',
'id' => 'id',
'content' => null,
'css' => ['css'],
'options' => ['name' => 'name', 'onclick' => 'foo();'],
'expected' => '<input id="id" name="name" class="css" onclick="foo();">'
]
];
}
/**
* Undocumented function
*
* @covers ::Element
* @covers ::buildHtml
* @covers ::getTag
* @covers ::getId
* @covers ::getContent
* @covers ::getOptions
* @covers ::getCss
* @dataProvider providerCreateElements
* @testdox create single new Element test [$_dataName]
*
* @param string $tag
* @param string|null $id
* @param string|null $content
* @param array|null $css
* @param array|null $options
* @param string $expected
* @return void
*/
public function testCreateElement(
string $tag,
?string $id,
?string $content,
?array $css,
?array $options,
string $expected
): void {
$el = new Element($tag, $id ?? '', $content ?? '', $css ?? [], $options ?? []);
$this->assertEquals(
$expected,
$el->buildHtml(),
'element creation failed'
);
$this->assertEquals(
$tag,
$el->getTag(),
'get tag failed'
);
if ($id !== null) {
$this->assertEquals(
$id,
$el->getId(),
'get id failed'
);
}
if ($content !== null) {
$this->assertEquals(
$content,
$el->getContent(),
'get content failed'
);
}
if ($css !== null) {
$this->assertEquals(
$css,
$el->getCss(),
'get css failed'
);
}
if ($options !== null) {
$this->assertEquals(
$options,
$el->getOptions(),
'get options failed'
);
}
if (!empty($options['name'])) {
$this->assertEquals(
$options['name'],
$el->getName(),
'get name failed'
);
}
}
/**
* css add/remove
*
* @cover ::getCss
* @cover ::addCss
* @cover ::removeCss
* @testdox test handling of adding and removing css classes
*
* @return void
*/
public function testCssHandling(): void
{
$el = new Element('div', 'css-test', 'CSS content');
$this->assertEqualsCanonicalizing(
[],
$el->getCss(),
'check empty css'
);
$el->addCss('foo');
$this->assertEqualsCanonicalizing(
['foo'],
$el->getCss(),
'check added one css'
);
$el->removeCss('foo');
$this->assertEqualsCanonicalizing(
[],
$el->getCss(),
'check remove added css'
);
// add serveral
// remove some of them
$el->addCss('a', 'b', 'c');
$this->assertEqualsCanonicalizing(
['a', 'b', 'c'],
$el->getCss(),
'check added some css'
);
$el->removeCss('a', 'c');
// $this->assertArray
$this->assertEqualsCanonicalizing(
['b'],
$el->getCss(),
'check remove some css'
);
// chained add and remove
$el->addCss('a', 'b', 'c', 'd')->removeCss('b', 'd');
$this->assertEqualsCanonicalizing(
['a', 'c'],
$el->getCss(),
'check chain add remove some css'
);
$el->resetCss();
$this->assertEqualsCanonicalizing(
[],
$el->getCss(),
'check reset css'
);
// remove something that does not eixst
$el->addCss('exists');
$el->removeCss('not');
$this->assertEqualsCanonicalizing(
['exists'],
$el->getCss(),
'check remove not exitsing'
);
}
/**
* nested test
*
* @testdox nested test and loop assign detection
*
* @return void
*/
public function testBuildNested(): void
{
Error::resetMessages();
$el = new Element('div', 'build-test');
$el_sub = new Element('div', 'sub-1');
$el->addSub($el_sub);
$this->assertEquals(
'<div id="build-test"><div id="sub-1"></div></div>',
$el->buildHtml(),
'nested build failed'
);
// this would create a loop, throws error
$this->expectException(HtmlBuilderExcpetion::class);
$this->expectExceptionMessage("Cannot assign Element to itself, this would create an infinite loop");
$el_sub->addSub($el_sub);
$this->assertEquals(
'<div id="sub-1"></div>',
$el_sub->buildHtml(),
'loop detection failed'
);
$this->assertTrue(
Error::hasError(),
'failed to throw error post loop detection'
);
$this->assertEquals(
[[
'level' => 'Error',
'id' => '100',
'message' => 'Cannot assign Element to itself, this would create an infinite loop',
'context' => ['tag' => 'div', 'id' => 'sub-1']
]],
Error::getMessages(),
'check error is 100 failed'
);
// get sub
$this->assertEquals(
[$el_sub],
$el->getSub(),
'get sub failed'
);
// reset sub
$el->resetSub();
$this->assertEquals(
[],
$el->getSub(),
'reset sub failed'
);
}
/**
* Undocumented function
*
* @testdox updated nested connection
*
* @return void
*/
public function testNestedChangeContent(): void
{
$el = new Element('div', 'build-test');
$el_s_1 = new Element('div', 'sub-1');
$el_s_2 = new Element('div', 'sub-2');
$el_s_3 = new Element('div', 'sub-3');
$el_s_4 = new Element('div', 'sub-4');
$el->addSub($el_s_1, $el_s_2);
// only sub -1, -2
$this->assertEquals(
'<div id="build-test"><div id="sub-1"></div><div id="sub-2"></div></div>',
$el->buildHtml(),
'check basic nested'
);
// now add -3, -4 to both -1 and -2
$el_s_1->addSub($el_s_3, $el_s_4);
$el_s_2->addSub($el_s_3, $el_s_4);
$this->assertEquals(
'<div id="build-test"><div id="sub-1"><div id="sub-3"></div><div id="sub-4">'
. '</div></div><div id="sub-2"><div id="sub-3"></div><div id="sub-4"></div>'
. '</div></div>',
$el->buildHtml(),
'check nested added'
);
// now add some css to el_s_3, will update in both sets
$el_s_3->addCss('red');
$this->assertEquals(
'<div id="build-test"><div id="sub-1"><div id="sub-3" class="red"></div><div id="sub-4">'
. '</div></div><div id="sub-2"><div id="sub-3" class="red"></div><div id="sub-4"></div>'
. '</div></div>',
$el->buildHtml(),
'check nested u@dated'
);
}
/**
* Undocumented function
*
* @testdox test change tag/id/content
*
* @return void
*/
public function testChangeElementData(): void
{
$el = new Element('div', 'id', 'Content');
// content change
$this->assertEquals(
'Content',
$el->getContent(),
'set content'
);
$el->setContent('New Content');
$this->assertEquals(
'New Content',
$el->getContent(),
'changed content'
);
$this->assertEquals(
'div',
$el->getTag(),
'set tag'
);
$el->setTag('span');
$this->assertEquals(
'span',
$el->getTag(),
'changed tag'
);
$this->assertEquals(
'id',
$el->getId(),
'set id'
);
$el->setId('id-2');
$this->assertEquals(
'id-2',
$el->getId(),
'changed id'
);
}
/**
* Undocumented function
*
* @testdox test change options
*
* @return void
*/
public function testChangeOptions(): void
{
$el = new Element('button', 'id', 'Action', ['css'], ['value' => '3']);
$this->assertEquals(
['value' => '3'],
$el->getOptions(),
'set option'
);
$el->setOptions([
'value' => '2'
]);
$this->assertEquals(
['value' => '2'],
$el->getOptions(),
'changed option'
);
// add a new one
$el->setOptions([
'Foo' => 'bar',
'Moo' => 'cow'
]);
$this->assertEquals(
[
'value' => '2',
'Foo' => 'bar',
'Moo' => 'cow'
],
$el->getOptions(),
'changed option'
);
}
// build output
// build from array list
/**
* Undocumented function
*
* @testdox build element tree from object
*
* @return void
*/
public function testBuildHtmlObject(): void
{
// build a simple block
// div -> div -> button
// -> div -> span
// -> div -> input
$el = new Element('div', 'master', '', ['master']);
$el->addSub(
Element::addElement(
new Element('div', 'div-button', '', ['dv-bt']),
new Element('button', 'button-id', 'Click me', ['bt-red'], [
'OnClick' => 'action();',
'value' => 'click',
'type' => 'button'
]),
),
Element::addElement(
new Element('div', 'div-span', '', ['dv-sp']),
new Element('span', 'span-id', 'Big important message<br>other', ['red']),
),
Element::addElement(
new Element('div', 'div-input', '', ['dv-in']),
new Element('input', 'input-id', '', ['in-blue'], [
'OnClick' => 'otherAction();',
'value' => 'Touch',
'type' => 'button'
]),
),
);
$this->assertEquals(
'<div id="master" class="master">'
. '<div id="div-button" class="dv-bt">'
. '<button id="button-id" name="button-id" class="bt-red" OnClick="action();" '
. 'value="click" type="button">Click me</button>'
. '</div>'
. '<div id="div-span" class="dv-sp">'
. '<span id="span-id" class="red">Big important message<br>other</span>'
. '</div>'
. '<div id="div-input" class="dv-in">'
. '<input id="input-id" name="input-id" '
. 'class="in-blue" OnClick="otherAction();" value="Touch" type="button">'
. '</div>'
. '</div>',
$el->buildHtml()
);
}
/**
* Undocumented function
*
* @testdox build elements from array list
*
* @return void
*/
public function testbuildHtmlArray(): void
{
$this->assertEquals(
'<div id="id-1">A</div>'
. '<div id="id-2">B</div>'
. '<div id="id-3">C</div>',
Element::buildHtmlFromList([
new Element('div', 'id-1', 'A'),
new Element('div', 'id-2', 'B'),
new Element('div', 'id-3', 'C'),
])
);
}
/**
* Undocumented function
*
* @testdox check for invalid tag detection, possible invalid id, possible invalid css
*
* @return void
*/
public function testInvalidElement(): void
{
Error::resetMessages();
$this->expectException(HtmlBuilderExcpetion::class);
$this->expectExceptionMessage("Could not create Element");
$el = new Element('');
$this->assertTrue(
Error::hasError(),
'failed to set error invalid tag detection'
);
$this->assertEquals(
[[
'level' => 'Error',
'id' => '201',
'message' => 'invalid or empty tag',
'context' => ['tag' => '']
]],
Error::getMessages(),
'check error message failed'
);
// if we set invalid tag
$el = new Element('div');
$this->expectException(HtmlBuilderExcpetion::class);
$this->expectExceptionMessageMatches("/^Invalid or empty tag: /");
$this->expectExceptionMessage("Invalid or empty tag: 123123");
$el->setTag('123123');
$this->assertTrue(
Error::hasError(),
'failed to set error invalid tag detection'
);
$this->assertEquals(
[[
'level' => 'Error',
'id' => '201',
'message' => 'invalid or empty tag',
'context' => ['tag' => '']
]],
Error::getMessages(),
'check error message failed'
);
// invalid id (warning)
Error::resetMessages();
$el = new Element('div', '-$a15');
$this->assertTrue(
Error::hasWarning(),
'failed to set warning invalid id detection'
);
$this->assertEquals(
[[
'level' => 'Warning',
'id' => '202',
'message' => 'possible invalid id',
'context' => ['id' => '-$a15', 'tag' => 'div']
]],
Error::getMessages(),
'check error message failed'
);
// invalid name
Error::resetMessages();
$el = new Element('div', 'valid', '', [], ['name' => '-$asdf&']);
$this->assertTrue(
Error::hasWarning(),
'failed to set warning invalid name detection'
);
$this->assertEquals(
[[
'level' => 'Warning',
'id' => '203',
'message' => 'possible invalid name',
'context' => ['name' => '-$asdf&', 'id' => 'valid', 'tag' => 'div']
]],
Error::getMessages(),
'check error message failed'
);
}
// static add element
// print object/array
}
// __END__

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Template\HtmlBuilder\StringReplace;
/**
* Test class for Template\HtmlBuilder\StringReplace
* @coversDefaultClass \CoreLibs\Template\HtmlBuilder\StringReplace
* @testdox \CoreLibs\Template\HtmlBuilder\StringReplace method tests
*/
final class CoreLibsTemplateHtmlBuilderStringReplaceTest extends TestCase
{
/**
* Undocumented function
*
* @covers ::replaceData
* @testdox test basic replaceData
*
* @return void
*/
public function testReplaceData(): void
{
$html_block = <<<HTML
<div id="{ID}" class="{CSS}">
{CONTENT}
</div>
HTML;
$this->assertEquals(
<<<HTML
<div id="block-id" class="blue,red">
Some content here<br>with bla bla inside
</div>
HTML,
StringReplace::replaceData(
$html_block,
[
'ID' => 'block-id',
'CSS' => join(',', ['blue', 'red']),
'{CONTENT}' => 'Some content here<br>with bla bla inside',
]
)
);
}
/**
* Undocumented function
*
* @testdox replaceData error
*
* @return void
*/
/* public function testReplaceDataErrors(): void
{
$this->expectException(HtmlBuilderExcpetion::class);
$this->expectExceptionMessage("Replace and content array count differ");
StringReplace::replaceData('<span>{FOO}</span>', ['{FOO}', '{BAR}'], ['foo']);
} */
}
// __END__

178
composer.lock generated
View File

@@ -481,25 +481,29 @@
},
{
"name": "doctrine/deprecations",
"version": "v1.0.0",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/deprecations.git",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
"reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
"reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
"shasum": ""
},
"require": {
"php": "^7.1|^8.0"
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^9",
"phpunit/phpunit": "^7.5|^8.5|^9.5",
"psr/log": "^1|^2|^3"
"phpstan/phpstan": "1.4.10 || 1.10.15",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psalm/plugin-phpunit": "0.18.4",
"psr/log": "^1 || ^2 || ^3",
"vimeo/psalm": "4.30.0 || 5.12.0"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
@@ -518,9 +522,9 @@
"homepage": "https://www.doctrine-project.org/",
"support": {
"issues": "https://github.com/doctrine/deprecations/issues",
"source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
"source": "https://github.com/doctrine/deprecations/tree/v1.1.1"
},
"time": "2022-05-02T15:47:09+00:00"
"time": "2023-06-03T09:27:29+00:00"
},
{
"name": "felixfbecker/advanced-json-rpc",
@@ -782,16 +786,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.5",
"version": "v4.16.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e"
"reference": "19526a33fb561ef417e822e85f08a00db4059c17"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
"reference": "19526a33fb561ef417e822e85f08a00db4059c17",
"shasum": ""
},
"require": {
@@ -832,9 +836,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
},
"time": "2023-05-19T20:20:00+00:00"
"time": "2023-06-25T14:52:30+00:00"
},
{
"name": "phan/phan",
@@ -1027,16 +1031,16 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.7.1",
"version": "1.7.2",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "dfc078e8af9c99210337325ff5aa152872c98714"
"reference": "b2fe4d22a5426f38e014855322200b97b5362c0d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dfc078e8af9c99210337325ff5aa152872c98714",
"reference": "dfc078e8af9c99210337325ff5aa152872c98714",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b2fe4d22a5426f38e014855322200b97b5362c0d",
"reference": "b2fe4d22a5426f38e014855322200b97b5362c0d",
"shasum": ""
},
"require": {
@@ -1079,9 +1083,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.1"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.2"
},
"time": "2023-03-27T19:02:04+00:00"
"time": "2023-05-30T18:13:47+00:00"
},
{
"name": "phpstan/extension-installer",
@@ -1129,22 +1133,23 @@
},
{
"name": "phpstan/phpdoc-parser",
"version": "1.21.0",
"version": "1.23.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "6df62b08faef4f899772bc7c3bbabb93d2b7a21c"
"reference": "a2b24135c35852b348894320d47b3902a94bc494"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6df62b08faef4f899772bc7c3bbabb93d2b7a21c",
"reference": "6df62b08faef4f899772bc7c3bbabb93d2b7a21c",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a2b24135c35852b348894320d47b3902a94bc494",
"reference": "a2b24135c35852b348894320d47b3902a94bc494",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/annotations": "^2.0",
"nikic/php-parser": "^4.15",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/extension-installer": "^1.0",
@@ -1169,22 +1174,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.21.0"
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.0"
},
"time": "2023-05-17T13:13:44+00:00"
"time": "2023-07-23T22:17:56+00:00"
},
{
"name": "phpstan/phpstan",
"version": "1.10.15",
"version": "1.10.26",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd"
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/762c4dac4da6f8756eebb80e528c3a47855da9bd",
"reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f",
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f",
"shasum": ""
},
"require": {
@@ -1233,7 +1238,7 @@
"type": "tidelift"
}
],
"time": "2023-05-09T15:28:01+00:00"
"time": "2023-07-19T12:44:37+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -1471,16 +1476,16 @@
},
{
"name": "spatie/array-to-xml",
"version": "3.1.6",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/array-to-xml.git",
"reference": "e210b98957987c755372465be105d32113f339a4"
"reference": "f9ab39c808500c347d5a8b6b13310bd5221e39e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/array-to-xml/zipball/e210b98957987c755372465be105d32113f339a4",
"reference": "e210b98957987c755372465be105d32113f339a4",
"url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f9ab39c808500c347d5a8b6b13310bd5221e39e7",
"reference": "f9ab39c808500c347d5a8b6b13310bd5221e39e7",
"shasum": ""
},
"require": {
@@ -1518,7 +1523,7 @@
"xml"
],
"support": {
"source": "https://github.com/spatie/array-to-xml/tree/3.1.6"
"source": "https://github.com/spatie/array-to-xml/tree/3.2.0"
},
"funding": [
{
@@ -1530,27 +1535,27 @@
"type": "github"
}
],
"time": "2023-05-11T14:04:07+00:00"
"time": "2023-07-19T18:30:26+00:00"
},
{
"name": "symfony/console",
"version": "v6.2.11",
"version": "v6.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "5aa03db8ef0a5457c316ec580e69562d97734c77"
"reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/5aa03db8ef0a5457c316ec580e69562d97734c77",
"reference": "5aa03db8ef0a5457c316ec580e69562d97734c77",
"url": "https://api.github.com/repos/symfony/console/zipball/aa5d64ad3f63f2e48964fc81ee45cb318a723898",
"reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^1.1|^2|^3",
"symfony/service-contracts": "^2.5|^3",
"symfony/string": "^5.4|^6.0"
},
"conflict": {
@@ -1572,12 +1577,6 @@
"symfony/process": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.0"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
},
"type": "library",
"autoload": {
"psr-4": {
@@ -1610,7 +1609,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v6.2.11"
"source": "https://github.com/symfony/console/tree/v6.3.2"
},
"funding": [
{
@@ -1626,20 +1625,20 @@
"type": "tidelift"
}
],
"time": "2023-05-26T08:16:21+00:00"
"time": "2023-07-19T20:17:28+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.2.1",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e"
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
"reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": ""
},
"require": {
@@ -1648,7 +1647,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.3-dev"
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -1677,7 +1676,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
},
"funding": [
{
@@ -1693,20 +1692,20 @@
"type": "tidelift"
}
],
"time": "2023-03-01T10:25:55+00:00"
"time": "2023-05-23T14:45:45+00:00"
},
{
"name": "symfony/filesystem",
"version": "v6.2.10",
"version": "v6.3.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894"
"reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/fd588debf7d1bc16a2c84b4b3b71145d9946b894",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae",
"reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae",
"shasum": ""
},
"require": {
@@ -1740,7 +1739,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.2.10"
"source": "https://github.com/symfony/filesystem/tree/v6.3.1"
},
"funding": [
{
@@ -1756,7 +1755,7 @@
"type": "tidelift"
}
],
"time": "2023-04-18T13:46:08+00:00"
"time": "2023-06-01T08:30:39+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -2173,16 +2172,16 @@
},
{
"name": "symfony/service-contracts",
"version": "v3.2.1",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "a8c9cedf55f314f3a186041d19537303766df09a"
"reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a",
"reference": "a8c9cedf55f314f3a186041d19537303766df09a",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4",
"reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4",
"shasum": ""
},
"require": {
@@ -2192,13 +2191,10 @@
"conflict": {
"ext-psr": "<1.1|>=2"
},
"suggest": {
"symfony/service-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.3-dev"
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -2238,7 +2234,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.2.1"
"source": "https://github.com/symfony/service-contracts/tree/v3.3.0"
},
"funding": [
{
@@ -2254,20 +2250,20 @@
"type": "tidelift"
}
],
"time": "2023-03-01T10:32:47+00:00"
"time": "2023-05-23T14:45:45+00:00"
},
{
"name": "symfony/string",
"version": "v6.2.8",
"version": "v6.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef"
"reference": "53d1a83225002635bca3482fcbf963001313fb68"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef",
"reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef",
"url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68",
"reference": "53d1a83225002635bca3482fcbf963001313fb68",
"shasum": ""
},
"require": {
@@ -2278,13 +2274,13 @@
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/translation-contracts": "<2.0"
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
"symfony/error-handler": "^5.4|^6.0",
"symfony/http-client": "^5.4|^6.0",
"symfony/intl": "^6.2",
"symfony/translation-contracts": "^2.0|^3.0",
"symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^5.4|^6.0"
},
"type": "library",
@@ -2324,7 +2320,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.2.8"
"source": "https://github.com/symfony/string/tree/v6.3.2"
},
"funding": [
{
@@ -2340,7 +2336,7 @@
"type": "tidelift"
}
],
"time": "2023-03-20T16:06:02+00:00"
"time": "2023-07-05T08:41:27+00:00"
},
{
"name": "tysonandre/var_representation_polyfill",
@@ -2406,16 +2402,16 @@
},
{
"name": "vimeo/psalm",
"version": "5.12.0",
"version": "5.14.1",
"source": {
"type": "git",
"url": "https://github.com/vimeo/psalm.git",
"reference": "f90118cdeacd0088e7215e64c0c99ceca819e176"
"reference": "b9d355e0829c397b9b3b47d0c0ed042a8a70284d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vimeo/psalm/zipball/f90118cdeacd0088e7215e64c0c99ceca819e176",
"reference": "f90118cdeacd0088e7215e64c0c99ceca819e176",
"url": "https://api.github.com/repos/vimeo/psalm/zipball/b9d355e0829c397b9b3b47d0c0ed042a8a70284d",
"reference": "b9d355e0829c397b9b3b47d0c0ed042a8a70284d",
"shasum": ""
},
"require": {
@@ -2436,8 +2432,8 @@
"felixfbecker/language-server-protocol": "^1.5.2",
"fidry/cpu-core-counter": "^0.4.1 || ^0.5.1",
"netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
"nikic/php-parser": "^4.14",
"php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0",
"nikic/php-parser": "^4.16",
"php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
"sebastian/diff": "^4.0 || ^5.0",
"spatie/array-to-xml": "^2.17.0 || ^3.0",
"symfony/console": "^4.1.6 || ^5.0 || ^6.0",
@@ -2506,9 +2502,9 @@
],
"support": {
"issues": "https://github.com/vimeo/psalm/issues",
"source": "https://github.com/vimeo/psalm/tree/5.12.0"
"source": "https://github.com/vimeo/psalm/tree/5.14.1"
},
"time": "2023-05-22T21:19:03+00:00"
"time": "2023-08-01T05:16:55+00:00"
},
{
"name": "webmozart/assert",

View File

@@ -1,47 +0,0 @@
parameters:
ignoreErrors:
-
message: "#^Parameter \\#1 \\$connection of function pg_escape_bytea expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#"
count: 1
path: www/lib/CoreLibs/DB/SQL/PgSQL.php
-
message: "#^Parameter \\#1 \\$connection of function pg_escape_identifier expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#"
count: 1
path: www/lib/CoreLibs/DB/SQL/PgSQL.php
-
message: "#^Parameter \\#1 \\$connection of function pg_escape_literal expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#"
count: 1
path: www/lib/CoreLibs/DB/SQL/PgSQL.php
-
message: "#^Parameter \\#1 \\$connection of function pg_escape_string expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#"
count: 1
path: www/lib/CoreLibs/DB/SQL/PgSQL.php
-
message: "#^Parameter \\#1 \\$connection of function pg_execute expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#"
count: 1
path: www/lib/CoreLibs/DB/SQL/PgSQL.php
-
message: "#^Parameter \\#1 \\$connection of function pg_parameter_status expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#"
count: 1
path: www/lib/CoreLibs/DB/SQL/PgSQL.php
-
message: "#^Parameter \\#1 \\$connection of function pg_prepare expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#"
count: 1
path: www/lib/CoreLibs/DB/SQL/PgSQL.php
-
message: "#^Parameter \\#1 \\$connection of function pg_query expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#"
count: 1
path: www/lib/CoreLibs/DB/SQL/PgSQL.php
-
message: "#^Parameter \\#1 \\$connection of function pg_query_params expects PgSql\\\\Connection\\|string, object\\|resource given\\.$#"
count: 1
path: www/lib/CoreLibs/DB/SQL/PgSQL.php

View File

@@ -441,31 +441,35 @@
},
{
"name": "doctrine/deprecations",
"version": "v1.0.0",
"version_normalized": "1.0.0.0",
"version": "v1.1.1",
"version_normalized": "1.1.1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/deprecations.git",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
"reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
"reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
"shasum": ""
},
"require": {
"php": "^7.1|^8.0"
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^9",
"phpunit/phpunit": "^7.5|^8.5|^9.5",
"psr/log": "^1|^2|^3"
"phpstan/phpstan": "1.4.10 || 1.10.15",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psalm/plugin-phpunit": "0.18.4",
"psr/log": "^1 || ^2 || ^3",
"vimeo/psalm": "4.30.0 || 5.12.0"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"time": "2022-05-02T15:47:09+00:00",
"time": "2023-06-03T09:27:29+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -481,7 +485,7 @@
"homepage": "https://www.doctrine-project.org/",
"support": {
"issues": "https://github.com/doctrine/deprecations/issues",
"source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
"source": "https://github.com/doctrine/deprecations/tree/v1.1.1"
},
"install-path": "../doctrine/deprecations"
},
@@ -760,17 +764,17 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.5",
"version_normalized": "4.15.5.0",
"version": "v4.16.0",
"version_normalized": "4.16.0.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e"
"reference": "19526a33fb561ef417e822e85f08a00db4059c17"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
"reference": "19526a33fb561ef417e822e85f08a00db4059c17",
"shasum": ""
},
"require": {
@@ -781,7 +785,7 @@
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
},
"time": "2023-05-19T20:20:00+00:00",
"time": "2023-06-25T14:52:30+00:00",
"bin": [
"bin/php-parse"
],
@@ -813,7 +817,7 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
},
"install-path": "../nikic/php-parser"
},
@@ -1017,17 +1021,17 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.7.1",
"version_normalized": "1.7.1.0",
"version": "1.7.2",
"version_normalized": "1.7.2.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "dfc078e8af9c99210337325ff5aa152872c98714"
"reference": "b2fe4d22a5426f38e014855322200b97b5362c0d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dfc078e8af9c99210337325ff5aa152872c98714",
"reference": "dfc078e8af9c99210337325ff5aa152872c98714",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b2fe4d22a5426f38e014855322200b97b5362c0d",
"reference": "b2fe4d22a5426f38e014855322200b97b5362c0d",
"shasum": ""
},
"require": {
@@ -1046,7 +1050,7 @@
"rector/rector": "^0.13.9",
"vimeo/psalm": "^4.25"
},
"time": "2023-03-27T19:02:04+00:00",
"time": "2023-05-30T18:13:47+00:00",
"type": "library",
"extra": {
"branch-alias": {
@@ -1072,7 +1076,7 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.1"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.2"
},
"install-path": "../phpdocumentor/type-resolver"
},
@@ -1125,23 +1129,24 @@
},
{
"name": "phpstan/phpdoc-parser",
"version": "1.21.0",
"version_normalized": "1.21.0.0",
"version": "1.23.0",
"version_normalized": "1.23.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "6df62b08faef4f899772bc7c3bbabb93d2b7a21c"
"reference": "a2b24135c35852b348894320d47b3902a94bc494"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6df62b08faef4f899772bc7c3bbabb93d2b7a21c",
"reference": "6df62b08faef4f899772bc7c3bbabb93d2b7a21c",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a2b24135c35852b348894320d47b3902a94bc494",
"reference": "a2b24135c35852b348894320d47b3902a94bc494",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/annotations": "^2.0",
"nikic/php-parser": "^4.15",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/extension-installer": "^1.0",
@@ -1151,7 +1156,7 @@
"phpunit/phpunit": "^9.5",
"symfony/process": "^5.2"
},
"time": "2023-05-17T13:13:44+00:00",
"time": "2023-07-23T22:17:56+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -1168,23 +1173,23 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.21.0"
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.0"
},
"install-path": "../phpstan/phpdoc-parser"
},
{
"name": "phpstan/phpstan",
"version": "1.10.15",
"version_normalized": "1.10.15.0",
"version": "1.10.26",
"version_normalized": "1.10.26.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd"
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/762c4dac4da6f8756eebb80e528c3a47855da9bd",
"reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f",
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f",
"shasum": ""
},
"require": {
@@ -1193,7 +1198,7 @@
"conflict": {
"phpstan/phpstan-shim": "*"
},
"time": "2023-05-09T15:28:01+00:00",
"time": "2023-07-19T12:44:37+00:00",
"bin": [
"phpstan",
"phpstan.phar"
@@ -1538,17 +1543,17 @@
},
{
"name": "spatie/array-to-xml",
"version": "3.1.6",
"version_normalized": "3.1.6.0",
"version": "3.2.0",
"version_normalized": "3.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/array-to-xml.git",
"reference": "e210b98957987c755372465be105d32113f339a4"
"reference": "f9ab39c808500c347d5a8b6b13310bd5221e39e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/array-to-xml/zipball/e210b98957987c755372465be105d32113f339a4",
"reference": "e210b98957987c755372465be105d32113f339a4",
"url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f9ab39c808500c347d5a8b6b13310bd5221e39e7",
"reference": "f9ab39c808500c347d5a8b6b13310bd5221e39e7",
"shasum": ""
},
"require": {
@@ -1560,7 +1565,7 @@
"pestphp/pest": "^1.21",
"spatie/pest-plugin-snapshots": "^1.1"
},
"time": "2023-05-11T14:04:07+00:00",
"time": "2023-07-19T18:30:26+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -1588,7 +1593,7 @@
"xml"
],
"support": {
"source": "https://github.com/spatie/array-to-xml/tree/3.1.6"
"source": "https://github.com/spatie/array-to-xml/tree/3.2.0"
},
"funding": [
{
@@ -1604,24 +1609,24 @@
},
{
"name": "symfony/console",
"version": "v6.2.11",
"version_normalized": "6.2.11.0",
"version": "v6.3.2",
"version_normalized": "6.3.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "5aa03db8ef0a5457c316ec580e69562d97734c77"
"reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/5aa03db8ef0a5457c316ec580e69562d97734c77",
"reference": "5aa03db8ef0a5457c316ec580e69562d97734c77",
"url": "https://api.github.com/repos/symfony/console/zipball/aa5d64ad3f63f2e48964fc81ee45cb318a723898",
"reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^1.1|^2|^3",
"symfony/service-contracts": "^2.5|^3",
"symfony/string": "^5.4|^6.0"
},
"conflict": {
@@ -1643,13 +1648,7 @@
"symfony/process": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.0"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
},
"time": "2023-05-26T08:16:21+00:00",
"time": "2023-07-19T20:17:28+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -1683,7 +1682,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v6.2.11"
"source": "https://github.com/symfony/console/tree/v6.3.2"
},
"funding": [
{
@@ -1703,27 +1702,27 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.2.1",
"version_normalized": "3.2.1.0",
"version": "v3.3.0",
"version_normalized": "3.3.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e"
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
"reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"time": "2023-03-01T10:25:55+00:00",
"time": "2023-05-23T14:45:45+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.3-dev"
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -1753,7 +1752,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
},
"funding": [
{
@@ -1773,17 +1772,17 @@
},
{
"name": "symfony/filesystem",
"version": "v6.2.10",
"version_normalized": "6.2.10.0",
"version": "v6.3.1",
"version_normalized": "6.3.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894"
"reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/fd588debf7d1bc16a2c84b4b3b71145d9946b894",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae",
"reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae",
"shasum": ""
},
"require": {
@@ -1791,7 +1790,7 @@
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
"time": "2023-04-18T13:46:08+00:00",
"time": "2023-06-01T08:30:39+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -1819,7 +1818,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.2.10"
"source": "https://github.com/symfony/filesystem/tree/v6.3.1"
},
"funding": [
{
@@ -2267,17 +2266,17 @@
},
{
"name": "symfony/service-contracts",
"version": "v3.2.1",
"version_normalized": "3.2.1.0",
"version": "v3.3.0",
"version_normalized": "3.3.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "a8c9cedf55f314f3a186041d19537303766df09a"
"reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a",
"reference": "a8c9cedf55f314f3a186041d19537303766df09a",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4",
"reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4",
"shasum": ""
},
"require": {
@@ -2287,14 +2286,11 @@
"conflict": {
"ext-psr": "<1.1|>=2"
},
"suggest": {
"symfony/service-implementation": ""
},
"time": "2023-03-01T10:32:47+00:00",
"time": "2023-05-23T14:45:45+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.3-dev"
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -2335,7 +2331,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.2.1"
"source": "https://github.com/symfony/service-contracts/tree/v3.3.0"
},
"funding": [
{
@@ -2355,17 +2351,17 @@
},
{
"name": "symfony/string",
"version": "v6.2.8",
"version_normalized": "6.2.8.0",
"version": "v6.3.2",
"version_normalized": "6.3.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef"
"reference": "53d1a83225002635bca3482fcbf963001313fb68"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef",
"reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef",
"url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68",
"reference": "53d1a83225002635bca3482fcbf963001313fb68",
"shasum": ""
},
"require": {
@@ -2376,16 +2372,16 @@
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/translation-contracts": "<2.0"
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
"symfony/error-handler": "^5.4|^6.0",
"symfony/http-client": "^5.4|^6.0",
"symfony/intl": "^6.2",
"symfony/translation-contracts": "^2.0|^3.0",
"symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^5.4|^6.0"
},
"time": "2023-03-20T16:06:02+00:00",
"time": "2023-07-05T08:41:27+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -2424,7 +2420,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.2.8"
"source": "https://github.com/symfony/string/tree/v6.3.2"
},
"funding": [
{
@@ -2509,17 +2505,17 @@
},
{
"name": "vimeo/psalm",
"version": "5.12.0",
"version_normalized": "5.12.0.0",
"version": "5.14.1",
"version_normalized": "5.14.1.0",
"source": {
"type": "git",
"url": "https://github.com/vimeo/psalm.git",
"reference": "f90118cdeacd0088e7215e64c0c99ceca819e176"
"reference": "b9d355e0829c397b9b3b47d0c0ed042a8a70284d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vimeo/psalm/zipball/f90118cdeacd0088e7215e64c0c99ceca819e176",
"reference": "f90118cdeacd0088e7215e64c0c99ceca819e176",
"url": "https://api.github.com/repos/vimeo/psalm/zipball/b9d355e0829c397b9b3b47d0c0ed042a8a70284d",
"reference": "b9d355e0829c397b9b3b47d0c0ed042a8a70284d",
"shasum": ""
},
"require": {
@@ -2540,8 +2536,8 @@
"felixfbecker/language-server-protocol": "^1.5.2",
"fidry/cpu-core-counter": "^0.4.1 || ^0.5.1",
"netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
"nikic/php-parser": "^4.14",
"php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0",
"nikic/php-parser": "^4.16",
"php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
"sebastian/diff": "^4.0 || ^5.0",
"spatie/array-to-xml": "^2.17.0 || ^3.0",
"symfony/console": "^4.1.6 || ^5.0 || ^6.0",
@@ -2570,7 +2566,7 @@
"ext-curl": "In order to send data to shepherd",
"ext-igbinary": "^2.0.5 is required, used to serialize caching data"
},
"time": "2023-05-22T21:19:03+00:00",
"time": "2023-08-01T05:16:55+00:00",
"bin": [
"psalm",
"psalm-language-server",
@@ -2612,7 +2608,7 @@
],
"support": {
"issues": "https://github.com/vimeo/psalm/issues",
"source": "https://github.com/vimeo/psalm/tree/5.12.0"
"source": "https://github.com/vimeo/psalm/tree/5.14.1"
},
"install-path": "../vimeo/psalm"
},

View File

@@ -65,9 +65,9 @@
'dev_requirement' => true,
),
'doctrine/deprecations' => array(
'pretty_version' => 'v1.0.0',
'version' => '1.0.0.0',
'reference' => '0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de',
'pretty_version' => 'v1.1.1',
'version' => '1.1.1.0',
'reference' => '612a3ee5ab0d5dd97b7cf3874a6efe24325efac3',
'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/deprecations',
'aliases' => array(),
@@ -128,9 +128,9 @@
'dev_requirement' => true,
),
'nikic/php-parser' => array(
'pretty_version' => 'v4.15.5',
'version' => '4.15.5.0',
'reference' => '11e2663a5bc9db5d714eedb4277ee300403b4a9e',
'pretty_version' => 'v4.16.0',
'version' => '4.16.0.0',
'reference' => '19526a33fb561ef417e822e85f08a00db4059c17',
'type' => 'library',
'install_path' => __DIR__ . '/../nikic/php-parser',
'aliases' => array(),
@@ -164,9 +164,9 @@
'dev_requirement' => true,
),
'phpdocumentor/type-resolver' => array(
'pretty_version' => '1.7.1',
'version' => '1.7.1.0',
'reference' => 'dfc078e8af9c99210337325ff5aa152872c98714',
'pretty_version' => '1.7.2',
'version' => '1.7.2.0',
'reference' => 'b2fe4d22a5426f38e014855322200b97b5362c0d',
'type' => 'library',
'install_path' => __DIR__ . '/../phpdocumentor/type-resolver',
'aliases' => array(),
@@ -182,18 +182,18 @@
'dev_requirement' => true,
),
'phpstan/phpdoc-parser' => array(
'pretty_version' => '1.21.0',
'version' => '1.21.0.0',
'reference' => '6df62b08faef4f899772bc7c3bbabb93d2b7a21c',
'pretty_version' => '1.23.0',
'version' => '1.23.0.0',
'reference' => 'a2b24135c35852b348894320d47b3902a94bc494',
'type' => 'library',
'install_path' => __DIR__ . '/../phpstan/phpdoc-parser',
'aliases' => array(),
'dev_requirement' => true,
),
'phpstan/phpstan' => array(
'pretty_version' => '1.10.15',
'version' => '1.10.15.0',
'reference' => '762c4dac4da6f8756eebb80e528c3a47855da9bd',
'pretty_version' => '1.10.26',
'version' => '1.10.26.0',
'reference' => '5d660cbb7e1b89253a47147ae44044f49832351f',
'type' => 'library',
'install_path' => __DIR__ . '/../phpstan/phpstan',
'aliases' => array(),
@@ -211,7 +211,7 @@
'psalm/psalm' => array(
'dev_requirement' => true,
'provided' => array(
0 => '5.12.0',
0 => '5.14.1',
),
),
'psr/container' => array(
@@ -257,36 +257,36 @@
'dev_requirement' => true,
),
'spatie/array-to-xml' => array(
'pretty_version' => '3.1.6',
'version' => '3.1.6.0',
'reference' => 'e210b98957987c755372465be105d32113f339a4',
'pretty_version' => '3.2.0',
'version' => '3.2.0.0',
'reference' => 'f9ab39c808500c347d5a8b6b13310bd5221e39e7',
'type' => 'library',
'install_path' => __DIR__ . '/../spatie/array-to-xml',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/console' => array(
'pretty_version' => 'v6.2.11',
'version' => '6.2.11.0',
'reference' => '5aa03db8ef0a5457c316ec580e69562d97734c77',
'pretty_version' => 'v6.3.2',
'version' => '6.3.2.0',
'reference' => 'aa5d64ad3f63f2e48964fc81ee45cb318a723898',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/console',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.2.1',
'version' => '3.2.1.0',
'reference' => 'e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e',
'pretty_version' => 'v3.3.0',
'version' => '3.3.0.0',
'reference' => '7c3aff79d10325257a001fcf92d991f24fc967cf',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/filesystem' => array(
'pretty_version' => 'v6.2.10',
'version' => '6.2.10.0',
'reference' => 'fd588debf7d1bc16a2c84b4b3b71145d9946b894',
'pretty_version' => 'v6.3.1',
'version' => '6.3.1.0',
'reference' => 'edd36776956f2a6fcf577edb5b05eb0e3bdc52ae',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/filesystem',
'aliases' => array(),
@@ -338,18 +338,18 @@
'dev_requirement' => true,
),
'symfony/service-contracts' => array(
'pretty_version' => 'v3.2.1',
'version' => '3.2.1.0',
'reference' => 'a8c9cedf55f314f3a186041d19537303766df09a',
'pretty_version' => 'v3.3.0',
'version' => '3.3.0.0',
'reference' => '40da9cc13ec349d9e4966ce18b5fbcd724ab10a4',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/service-contracts',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/string' => array(
'pretty_version' => 'v6.2.8',
'version' => '6.2.8.0',
'reference' => '193e83bbd6617d6b2151c37fff10fa7168ebddef',
'pretty_version' => 'v6.3.2',
'version' => '6.3.2.0',
'reference' => '53d1a83225002635bca3482fcbf963001313fb68',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/string',
'aliases' => array(),
@@ -365,9 +365,9 @@
'dev_requirement' => true,
),
'vimeo/psalm' => array(
'pretty_version' => '5.12.0',
'version' => '5.12.0.0',
'reference' => 'f90118cdeacd0088e7215e64c0c99ceca819e176',
'pretty_version' => '5.14.1',
'version' => '5.14.1.0',
'reference' => 'b9d355e0829c397b9b3b47d0c0ed042a8a70284d',
'type' => 'library',
'install_path' => __DIR__ . '/../vimeo/psalm',
'aliases' => array(),

View File

@@ -19,13 +19,16 @@ Enable Doctrine deprecations to be sent to a PSR3 logger:
```
Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)`
messages.
messages by setting the `DOCTRINE_DEPRECATIONS` environment variable to `trigger`.
Alternatively, call:
```php
\Doctrine\Deprecations\Deprecation::enableWithTriggerError();
```
If you only want to enable deprecation tracking, without logging or calling `trigger_error` then call:
If you only want to enable deprecation tracking, without logging or calling `trigger_error`
then set the `DOCTRINE_DEPRECATIONS` environment variable to `track`.
Alternatively, call:
```php
\Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();

View File

@@ -1,22 +1,28 @@
{
"name": "doctrine/deprecations",
"type": "library",
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
"homepage": "https://www.doctrine-project.org/",
"license": "MIT",
"type": "library",
"homepage": "https://www.doctrine-project.org/",
"require": {
"php": "^7.1|^8.0"
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5|^8.5|^9.5",
"psr/log": "^1|^2|^3",
"doctrine/coding-standard": "^9"
"doctrine/coding-standard": "^9",
"phpstan/phpstan": "1.4.10 || 1.10.15",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psalm/plugin-phpunit": "0.18.4",
"psr/log": "^1 || ^2 || ^3",
"vimeo/psalm": "4.30.0 || 5.12.0"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"autoload": {
"psr-4": {"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"}
"psr-4": {
"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
}
},
"autoload-dev": {
"psr-4": {

View File

@@ -8,6 +8,7 @@ use Psr\Log\LoggerInterface;
use function array_key_exists;
use function array_reduce;
use function assert;
use function debug_backtrace;
use function sprintf;
use function strpos;
@@ -46,8 +47,8 @@ class Deprecation
private const TYPE_TRIGGER_ERROR = 2;
private const TYPE_PSR_LOGGER = 4;
/** @var int */
private static $type = self::TYPE_NONE;
/** @var int-mask-of<self::TYPE_*>|null */
private static $type;
/** @var LoggerInterface|null */
private static $logger;
@@ -56,6 +57,9 @@ class Deprecation
private static $ignoredPackages = [];
/** @var array<string,int> */
private static $triggeredDeprecations = [];
/** @var array<string,bool> */
private static $ignoredLinks = [];
/** @var bool */
@@ -68,21 +72,27 @@ class Deprecation
* deprecation. It is additionally used to de-duplicate the trigger of the
* same deprecation during a request.
*
* @param mixed $args
* @param float|int|string $args
*/
public static function trigger(string $package, string $link, string $message, ...$args): void
{
if (self::$type === self::TYPE_NONE) {
$type = self::$type ?? self::getTypeFromEnv();
if ($type === self::TYPE_NONE) {
return;
}
if (array_key_exists($link, self::$ignoredLinks)) {
self::$ignoredLinks[$link]++;
} else {
self::$ignoredLinks[$link] = 1;
if (isset(self::$ignoredLinks[$link])) {
return;
}
if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
if (array_key_exists($link, self::$triggeredDeprecations)) {
self::$triggeredDeprecations[$link]++;
} else {
self::$triggeredDeprecations[$link] = 1;
}
if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) {
return;
}
@@ -114,18 +124,20 @@ class Deprecation
* deprecation tracking is enabled even during deduplication, because it
* needs to call {@link debug_backtrace()}
*
* @param mixed $args
* @param float|int|string $args
*/
public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void
{
if (self::$type === self::TYPE_NONE) {
$type = self::$type ?? self::getTypeFromEnv();
if ($type === self::TYPE_NONE) {
return;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
// first check that the caller is not from a tests folder, in which case we always let deprecations pass
if (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) {
if (isset($backtrace[1]['file'], $backtrace[0]['file']) && strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) {
$path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR;
if (strpos($backtrace[0]['file'], $path) === false) {
@@ -137,13 +149,17 @@ class Deprecation
}
}
if (array_key_exists($link, self::$ignoredLinks)) {
self::$ignoredLinks[$link]++;
} else {
self::$ignoredLinks[$link] = 1;
if (isset(self::$ignoredLinks[$link])) {
return;
}
if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
if (array_key_exists($link, self::$triggeredDeprecations)) {
self::$triggeredDeprecations[$link]++;
} else {
self::$triggeredDeprecations[$link] = 1;
}
if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) {
return;
}
@@ -157,31 +173,35 @@ class Deprecation
}
/**
* @param array<mixed> $backtrace
* @param list<array{function: string, line?: int, file?: string, class?: class-string, type?: string, args?: mixed[], object?: object}> $backtrace
*/
private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void
{
if ((self::$type & self::TYPE_PSR_LOGGER) > 0) {
$type = self::$type ?? self::getTypeFromEnv();
if (($type & self::TYPE_PSR_LOGGER) > 0) {
$context = [
'file' => $backtrace[0]['file'],
'line' => $backtrace[0]['line'],
'file' => $backtrace[0]['file'] ?? null,
'line' => $backtrace[0]['line'] ?? null,
'package' => $package,
'link' => $link,
];
assert(self::$logger !== null);
self::$logger->notice($message, $context);
}
if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) {
if (! (($type & self::TYPE_TRIGGER_ERROR) > 0)) {
return;
}
$message .= sprintf(
' (%s:%d called by %s:%d, %s, package %s)',
self::basename($backtrace[0]['file']),
$backtrace[0]['line'],
self::basename($backtrace[1]['file']),
$backtrace[1]['line'],
self::basename($backtrace[0]['file'] ?? 'native code'),
$backtrace[0]['line'] ?? 0,
self::basename($backtrace[1]['file'] ?? 'native code'),
$backtrace[1]['line'] ?? 0,
$link,
$package
);
@@ -205,16 +225,19 @@ class Deprecation
public static function enableTrackingDeprecations(): void
{
self::$type = self::$type ?? 0;
self::$type |= self::TYPE_TRACK_DEPRECATIONS;
}
public static function enableWithTriggerError(): void
{
self::$type = self::$type ?? 0;
self::$type |= self::TYPE_TRIGGER_ERROR;
}
public static function enableWithPsrLogger(LoggerInterface $logger): void
{
self::$type = self::$type ?? 0;
self::$type |= self::TYPE_PSR_LOGGER;
self::$logger = $logger;
}
@@ -229,9 +252,10 @@ class Deprecation
self::$type = self::TYPE_NONE;
self::$logger = null;
self::$deduplication = true;
self::$ignoredLinks = [];
foreach (self::$ignoredLinks as $link => $count) {
self::$ignoredLinks[$link] = 0;
foreach (self::$triggeredDeprecations as $link => $count) {
self::$triggeredDeprecations[$link] = 0;
}
}
@@ -243,13 +267,13 @@ class Deprecation
public static function ignoreDeprecations(string ...$links): void
{
foreach ($links as $link) {
self::$ignoredLinks[$link] = 0;
self::$ignoredLinks[$link] = true;
}
}
public static function getUniqueTriggeredDeprecationsCount(): int
{
return array_reduce(self::$ignoredLinks, static function (int $carry, int $count) {
return array_reduce(self::$triggeredDeprecations, static function (int $carry, int $count) {
return $carry + $count;
}, 0);
}
@@ -261,6 +285,28 @@ class Deprecation
*/
public static function getTriggeredDeprecations(): array
{
return self::$ignoredLinks;
return self::$triggeredDeprecations;
}
/**
* @return int-mask-of<self::TYPE_*>
*/
private static function getTypeFromEnv(): int
{
switch ($_SERVER['DOCTRINE_DEPRECATIONS'] ?? $_ENV['DOCTRINE_DEPRECATIONS'] ?? null) {
case 'trigger':
self::$type = self::TYPE_TRIGGER_ERROR;
break;
case 'track':
self::$type = self::TYPE_TRACK_DEPRECATIONS;
break;
default:
self::$type = self::TYPE_NONE;
break;
}
return self::$type;
}
}

View File

@@ -0,0 +1,9 @@
parameters:
level: 6
paths:
- lib
- tests
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon

30
vendor/doctrine/deprecations/psalm.xml vendored Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
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"
findUnusedBaselineEntry="true"
findUnusedCode="false"
>
<projectFiles>
<directory name="lib/Doctrine/Deprecations" />
<directory name="tests/Doctrine/Deprecations" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<plugins>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
<issueHandlers>
<DeprecatedMethod>
<errorLevel type="suppress">
<!-- Remove when dropping support for PHPUnit 9.6 -->
<referencedMethod name="PHPUnit\Framework\TestCase::expectDeprecation"/>
<referencedMethod name="PHPUnit\Framework\TestCase::expectDeprecationMessage"/>
</errorLevel>
</DeprecatedMethod>
</issueHandlers>
</psalm>

View File

@@ -1008,7 +1008,7 @@ array_pair:
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
| T_ELLIPSIS expr { $$ = new Expr\ArrayItem($2, null, false, attributes(), true); }
;
encaps_list:

View File

@@ -1194,7 +1194,7 @@ array_pair:
| expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
| T_ELLIPSIS expr { $$ = new Expr\ArrayItem($2, null, false, attributes(), true); }
| /* empty */ { $$ = null; }
;

View File

@@ -6,7 +6,10 @@ use PhpParser\NodeAbstract;
class Name extends NodeAbstract
{
/** @var string[] Parts of the name */
/**
* @var string[] Parts of the name
* @deprecated Use getParts() instead
*/
public $parts;
private static $specialClassNames = [
@@ -30,6 +33,15 @@ class Name extends NodeAbstract
return ['parts'];
}
/**
* Get parts of name (split by the namespace separator).
*
* @return string[] Parts of name
*/
public function getParts(): array {
return $this->parts;
}
/**
* Gets the first part of the name, i.e. everything before the first namespace separator.
*

View File

@@ -2627,7 +2627,7 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
552 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true);
},
553 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];

View File

@@ -2818,7 +2818,7 @@ class Php7 extends \PhpParser\ParserAbstract
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
592 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true);
},
593 => function ($stackPos) {
$this->semValue = null;

View File

@@ -15,7 +15,7 @@ namespace phpDocumentor\Reflection\PseudoTypes;
use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Float_;
use phpDocumentor\Reflection\Types\String_;
use function sprintf;
@@ -36,7 +36,7 @@ class StringValue implements PseudoType
public function underlyingType(): Type
{
return new Float_();
return new String_();
}
public function __toString(): string

View File

@@ -15,6 +15,8 @@ For the complete list of supported PHPDoc features check out PHPStan documentati
* [PHPDoc Types](https://phpstan.org/writing-php-code/phpdoc-types) (list of PHPDoc types)
* [phpdoc-parser API Reference](https://phpstan.github.io/phpdoc-parser/namespace-PHPStan.PhpDocParser.html) with all the AST node types etc.
This parser also supports parsing [Doctrine Annotations](https://github.com/doctrine/annotations). The AST nodes live in the [PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine namespace](https://phpstan.github.io/phpdoc-parser/namespace-PHPStan.PhpDocParser.Ast.PhpDoc.Doctrine.html). The support needs to be turned on by setting `bool $parseDoctrineAnnotations` to `true` in `Lexer` and `PhpDocParser` class constructors.
## Installation
```
@@ -91,12 +93,13 @@ $phpDocNode = $phpDocParser->parse($tokens); // PhpDocNode
$cloningTraverser = new NodeTraverser([new CloningVisitor()]);
/** @var PhpDocNode $newPhpDocNode */
$printer = new Printer();
[$newPhpDocNode] = $cloningTraverser->traverse([$phpDocNode]);
// change something in $newPhpDocNode
$newPhpDocNode->getParamTagValues()[0]->type = new IdentifierTypeNode('Ipsum');
// print changed PHPDoc
$printer = new Printer();
$newPhpDoc = $printer->printFormatPreserving($newPhpDocNode, $phpDocNode, $tokens);
echo $newPhpDoc; // '/** @param Ipsum $a */'
```

View File

@@ -6,6 +6,7 @@
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/annotations": "^2.0",
"nikic/php-parser": "^4.15",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/extension-installer": "^1.0",

View File

@@ -8,7 +8,7 @@ namespace PHPStan\PhpDocParser\Ast;
* Copyright (c) 2011, Nikita Popov
* All rights reserved.
*/
abstract class AbstractNodeVisitor implements NodeVisitor
abstract class AbstractNodeVisitor implements NodeVisitor // phpcs:ignore SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix
{
public function beforeTraverse(array $nodes): ?array

View File

@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use function implode;
class DoctrineAnnotation implements Node
{
use NodeAttributes;
/** @var string */
public $name;
/** @var list<DoctrineArgument> */
public $arguments;
/**
* @param list<DoctrineArgument> $arguments
*/
public function __construct(string $name, array $arguments)
{
$this->name = $name;
$this->arguments = $arguments;
}
public function __toString(): string
{
$arguments = implode(', ', $this->arguments);
return $this->name . '(' . $arguments . ')';
}
}

View File

@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
/**
* @phpstan-type ValueType = DoctrineAnnotation|IdentifierTypeNode|DoctrineArray|ConstExprNode
*/
class DoctrineArgument implements Node
{
use NodeAttributes;
/** @var IdentifierTypeNode|null */
public $key;
/** @var ValueType */
public $value;
/**
* @param ValueType $value
*/
public function __construct(?IdentifierTypeNode $key, $value)
{
$this->key = $key;
$this->value = $value;
}
public function __toString(): string
{
if ($this->key === null) {
return (string) $this->value;
}
return $this->key . '=' . $this->value;
}
}

View File

@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use function implode;
class DoctrineArray implements Node
{
use NodeAttributes;
/** @var list<DoctrineArrayItem> */
public $items;
/**
* @param list<DoctrineArrayItem> $items
*/
public function __construct(array $items)
{
$this->items = $items;
}
public function __toString(): string
{
$items = implode(', ', $this->items);
return '{' . $items . '}';
}
}

View File

@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
/**
* @phpstan-import-type ValueType from DoctrineArgument
* @phpstan-type KeyType = ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|ConstFetchNode|null
*/
class DoctrineArrayItem implements Node
{
use NodeAttributes;
/** @var KeyType */
public $key;
/** @var ValueType */
public $value;
/**
* @param KeyType $key
* @param ValueType $value
*/
public function __construct($key, $value)
{
$this->key = $key;
$this->value = $value;
}
public function __toString(): string
{
if ($this->key === null) {
return (string) $this->value;
}
return $this->key . '=' . $this->value;
}
}

View File

@@ -0,0 +1,36 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use function trim;
class DoctrineTagValueNode implements PhpDocTagValueNode
{
use NodeAttributes;
/** @var DoctrineAnnotation */
public $annotation;
/** @var string (may be empty) */
public $description;
public function __construct(
DoctrineAnnotation $annotation,
string $description
)
{
$this->annotation = $annotation;
$this->description = $description;
}
public function __toString(): string
{
return trim("{$this->annotation} {$this->description}");
}
}

View File

@@ -3,6 +3,7 @@
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode;
use function trim;
class PhpDocTagNode implements PhpDocChildNode
@@ -25,6 +26,10 @@ class PhpDocTagNode implements PhpDocChildNode
public function __toString(): string
{
if ($this->value instanceof DoctrineTagValueNode) {
return (string) $this->value;
}
return trim("{$this->name} {$this->value}");
}

View File

@@ -30,23 +30,24 @@ class Lexer
public const TOKEN_OPEN_PHPDOC = 15;
public const TOKEN_CLOSE_PHPDOC = 16;
public const TOKEN_PHPDOC_TAG = 17;
public const TOKEN_FLOAT = 18;
public const TOKEN_INTEGER = 19;
public const TOKEN_SINGLE_QUOTED_STRING = 20;
public const TOKEN_DOUBLE_QUOTED_STRING = 21;
public const TOKEN_IDENTIFIER = 22;
public const TOKEN_THIS_VARIABLE = 23;
public const TOKEN_VARIABLE = 24;
public const TOKEN_HORIZONTAL_WS = 25;
public const TOKEN_PHPDOC_EOL = 26;
public const TOKEN_OTHER = 27;
public const TOKEN_END = 28;
public const TOKEN_COLON = 29;
public const TOKEN_WILDCARD = 30;
public const TOKEN_OPEN_CURLY_BRACKET = 31;
public const TOKEN_CLOSE_CURLY_BRACKET = 32;
public const TOKEN_NEGATED = 33;
public const TOKEN_ARROW = 34;
public const TOKEN_DOCTRINE_TAG = 18;
public const TOKEN_FLOAT = 19;
public const TOKEN_INTEGER = 20;
public const TOKEN_SINGLE_QUOTED_STRING = 21;
public const TOKEN_DOUBLE_QUOTED_STRING = 22;
public const TOKEN_IDENTIFIER = 23;
public const TOKEN_THIS_VARIABLE = 24;
public const TOKEN_VARIABLE = 25;
public const TOKEN_HORIZONTAL_WS = 26;
public const TOKEN_PHPDOC_EOL = 27;
public const TOKEN_OTHER = 28;
public const TOKEN_END = 29;
public const TOKEN_COLON = 30;
public const TOKEN_WILDCARD = 31;
public const TOKEN_OPEN_CURLY_BRACKET = 32;
public const TOKEN_CLOSE_CURLY_BRACKET = 33;
public const TOKEN_NEGATED = 34;
public const TOKEN_ARROW = 35;
public const TOKEN_LABELS = [
self::TOKEN_REFERENCE => '\'&\'',
@@ -72,6 +73,7 @@ class Lexer
self::TOKEN_OPEN_PHPDOC => '\'/**\'',
self::TOKEN_CLOSE_PHPDOC => '\'*/\'',
self::TOKEN_PHPDOC_TAG => 'TOKEN_PHPDOC_TAG',
self::TOKEN_DOCTRINE_TAG => 'TOKEN_DOCTRINE_TAG',
self::TOKEN_PHPDOC_EOL => 'TOKEN_PHPDOC_EOL',
self::TOKEN_FLOAT => 'TOKEN_FLOAT',
self::TOKEN_INTEGER => 'TOKEN_INTEGER',
@@ -90,9 +92,17 @@ class Lexer
public const TYPE_OFFSET = 1;
public const LINE_OFFSET = 2;
/** @var bool */
private $parseDoctrineAnnotations;
/** @var string|null */
private $regexp;
public function __construct(bool $parseDoctrineAnnotations = false)
{
$this->parseDoctrineAnnotations = $parseDoctrineAnnotations;
}
/**
* @return list<array{string, int, int}>
*/
@@ -160,17 +170,21 @@ class Lexer
self::TOKEN_PHPDOC_TAG => '@(?:[a-z][a-z0-9-\\\\]+:)?[a-z][a-z0-9-\\\\]*+',
self::TOKEN_PHPDOC_EOL => '\\r?+\\n[\\x09\\x20]*+(?:\\*(?!/)\\x20?+)?',
self::TOKEN_FLOAT => '(?:-?[0-9]++(_[0-9]++)*\\.[0-9]*(_[0-9]++)*+(?:e-?[0-9]++(_[0-9]++)*)?)|(?:-?[0-9]*+(_[0-9]++)*\\.[0-9]++(_[0-9]++)*(?:e-?[0-9]++(_[0-9]++)*)?)|(?:-?[0-9]++(_[0-9]++)*e-?[0-9]++(_[0-9]++)*)',
self::TOKEN_INTEGER => '-?(?:(?:0b[0-1]++(_[0-1]++)*)|(?:0o[0-7]++(_[0-7]++)*)|(?:0x[0-9a-f]++(_[0-9a-f]++)*)|(?:[0-9]++(_[0-9]++)*))',
self::TOKEN_FLOAT => '[+\-]?(?:(?:[0-9]++(_[0-9]++)*\\.[0-9]*+(_[0-9]++)*(?:e[+\-]?[0-9]++(_[0-9]++)*)?)|(?:[0-9]*+(_[0-9]++)*\\.[0-9]++(_[0-9]++)*(?:e[+\-]?[0-9]++(_[0-9]++)*)?)|(?:[0-9]++(_[0-9]++)*e[+\-]?[0-9]++(_[0-9]++)*))',
self::TOKEN_INTEGER => '[+\-]?(?:(?:0b[0-1]++(_[0-1]++)*)|(?:0o[0-7]++(_[0-7]++)*)|(?:0x[0-9a-f]++(_[0-9a-f]++)*)|(?:[0-9]++(_[0-9]++)*))',
self::TOKEN_SINGLE_QUOTED_STRING => '\'(?:\\\\[^\\r\\n]|[^\'\\r\\n\\\\])*+\'',
self::TOKEN_DOUBLE_QUOTED_STRING => '"(?:\\\\[^\\r\\n]|[^"\\r\\n\\\\])*+"',
self::TOKEN_WILDCARD => '\\*',
// anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL
self::TOKEN_OTHER => '(?:(?!\\*/)[^\\s])++',
];
if ($this->parseDoctrineAnnotations) {
$patterns[self::TOKEN_DOCTRINE_TAG] = '@[a-z_\\\\][a-z0-9_\:\\\\]*[a-z_][a-z0-9_]*';
}
// anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL
$patterns[self::TOKEN_OTHER] = '(?:(?!\\*/)[^\\s])++';
foreach ($patterns as $type => &$pattern) {
$pattern = '(?:' . $pattern . ')(*MARK:' . $type . ')';
}

View File

@@ -245,22 +245,14 @@ class ConstExprParser
*/
private function enrichWithAttributes(TokenIterator $tokens, Ast\ConstExpr\ConstExprNode $node, int $startLine, int $startIndex): Ast\ConstExpr\ConstExprNode
{
$endLine = $tokens->currentTokenLine();
$endIndex = $tokens->currentTokenIndex();
if ($this->useLinesAttributes) {
$node->setAttribute(Ast\Attribute::START_LINE, $startLine);
$node->setAttribute(Ast\Attribute::END_LINE, $endLine);
$node->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
}
if ($this->useIndexAttributes) {
$tokensArray = $tokens->getTokens();
$endIndex--;
if ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$endIndex--;
}
$node->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
$node->setAttribute(Ast\Attribute::END_INDEX, $endIndex);
$node->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
}
return $node;

View File

@@ -2,15 +2,25 @@
namespace PHPStan\PhpDocParser\Parser;
use LogicException;
use PHPStan\PhpDocParser\Ast;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\ShouldNotHappenException;
use function array_key_exists;
use function array_values;
use function count;
use function rtrim;
use function str_replace;
use function trim;
/**
* @phpstan-import-type ValueType from Doctrine\DoctrineArgument as DoctrineValueType
*/
class PhpDocParser
{
@@ -31,12 +41,18 @@ class PhpDocParser
/** @var bool */
private $preserveTypeAliasesWithInvalidTypes;
/** @var bool */
private $parseDoctrineAnnotations;
/** @var bool */
private $useLinesAttributes;
/** @var bool */
private $useIndexAttributes;
/** @var bool */
private $textBetweenTagsBelongsToDescription;
/**
* @param array{lines?: bool, indexes?: bool} $usedAttributes
*/
@@ -45,15 +61,19 @@ class PhpDocParser
ConstExprParser $constantExprParser,
bool $requireWhitespaceBeforeDescription = false,
bool $preserveTypeAliasesWithInvalidTypes = false,
array $usedAttributes = []
array $usedAttributes = [],
bool $parseDoctrineAnnotations = false,
bool $textBetweenTagsBelongsToDescription = false
)
{
$this->typeParser = $typeParser;
$this->constantExprParser = $constantExprParser;
$this->requireWhitespaceBeforeDescription = $requireWhitespaceBeforeDescription;
$this->preserveTypeAliasesWithInvalidTypes = $preserveTypeAliasesWithInvalidTypes;
$this->parseDoctrineAnnotations = $parseDoctrineAnnotations;
$this->useLinesAttributes = $usedAttributes['lines'] ?? false;
$this->useIndexAttributes = $usedAttributes['indexes'] ?? false;
$this->textBetweenTagsBelongsToDescription = $textBetweenTagsBelongsToDescription;
}
@@ -64,10 +84,44 @@ class PhpDocParser
$children = [];
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
$children[] = $this->parseChild($tokens);
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL) && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
if ($this->parseDoctrineAnnotations) {
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
$lastChild = $this->parseChild($tokens);
$children[] = $lastChild;
while (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
if (
$lastChild instanceof Ast\PhpDoc\PhpDocTagNode
&& (
$lastChild->value instanceof Doctrine\DoctrineTagValueNode
|| $lastChild->value instanceof Ast\PhpDoc\GenericTagValueNode
)
) {
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
break;
}
$lastChild = $this->parseChild($tokens);
$children[] = $lastChild;
continue;
}
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
break;
}
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
break;
}
$lastChild = $this->parseChild($tokens);
$children[] = $lastChild;
}
}
} else {
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
$children[] = $this->parseChild($tokens);
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL) && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
$children[] = $this->parseChild($tokens);
}
}
}
@@ -105,6 +159,7 @@ class PhpDocParser
}
/** @phpstan-impure */
private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode
{
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) {
@@ -113,6 +168,26 @@ class PhpDocParser
return $this->enrichWithAttributes($tokens, $this->parseTag($tokens), $startLine, $startIndex);
}
if ($tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_TAG)) {
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
$tag = $tokens->currentTokenValue();
$tokens->next();
$tagStartLine = $tokens->currentTokenLine();
$tagStartIndex = $tokens->currentTokenIndex();
return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocTagNode(
$tag,
$this->enrichWithAttributes(
$tokens,
$this->parseDoctrineTagValue($tokens, $tag),
$tagStartLine,
$tagStartIndex
)
), $startLine, $startIndex);
}
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
$text = $this->parseText($tokens);
@@ -127,27 +202,14 @@ class PhpDocParser
*/
private function enrichWithAttributes(TokenIterator $tokens, Ast\Node $tag, int $startLine, int $startIndex): Ast\Node
{
$endLine = $tokens->currentTokenLine();
$endIndex = $tokens->currentTokenIndex();
if ($this->useLinesAttributes) {
$tag->setAttribute(Ast\Attribute::START_LINE, $startLine);
$tag->setAttribute(Ast\Attribute::END_LINE, $endLine);
$tag->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
}
if ($this->useIndexAttributes) {
$tokensArray = $tokens->getTokens();
if ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_CLOSE_PHPDOC) {
$endIndex--;
if ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$endIndex--;
}
} elseif ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_PHPDOC_EOL) {
$endIndex--;
}
$tag->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
$tag->setAttribute(Ast\Attribute::END_INDEX, $endIndex);
$tag->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
}
return $tag;
@@ -158,29 +220,144 @@ class PhpDocParser
{
$text = '';
while (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
$text .= $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END);
$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
if ($this->textBetweenTagsBelongsToDescription) {
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
}
$savepoint = false;
// if the next token is EOL, everything below is skipped and empty string is returned
while ($this->textBetweenTagsBelongsToDescription || !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
$text .= $tmpText;
// stop if we're not at EOL - meaning it's the end of PHPDoc
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
break;
}
if ($this->textBetweenTagsBelongsToDescription) {
if (!$savepoint) {
$tokens->pushSavePoint();
$savepoint = true;
} elseif ($tmpText !== '') {
$tokens->dropSavePoint();
$tokens->pushSavePoint();
}
}
$tokens->pushSavePoint();
$tokens->next();
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END)) {
// if we're at EOL, check what's next
// if next is a PHPDoc tag, EOL, or end of PHPDoc, stop
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) {
$tokens->rollback();
break;
}
// otherwise if the next is text, continue building the description string
$tokens->dropSavePoint();
$text .= "\n";
$text .= $tokens->getDetectedNewline() ?? "\n";
}
if ($savepoint) {
$tokens->rollback();
$text = rtrim($text, $tokens->getDetectedNewline() ?? "\n");
}
return new Ast\PhpDoc\PhpDocTextNode(trim($text, " \t"));
}
private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens): string
{
$text = '';
$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
if ($this->textBetweenTagsBelongsToDescription) {
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
}
$savepoint = false;
// if the next token is EOL, everything below is skipped and empty string is returned
while ($this->textBetweenTagsBelongsToDescription || !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
$text .= $tmpText;
// stop if we're not at EOL - meaning it's the end of PHPDoc
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
if (!$tokens->isPrecededByHorizontalWhitespace()) {
return trim($text . $this->parseText($tokens)->text, " \t");
}
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) {
$tokens->pushSavePoint();
$child = $this->parseChild($tokens);
if ($child instanceof Ast\PhpDoc\PhpDocTagNode) {
if (
$child->value instanceof Ast\PhpDoc\GenericTagValueNode
|| $child->value instanceof Doctrine\DoctrineTagValueNode
) {
$tokens->rollback();
break;
}
if ($child->value instanceof Ast\PhpDoc\InvalidTagValueNode) {
$tokens->rollback();
$tokens->pushSavePoint();
$tokens->next();
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$tokens->rollback();
break;
}
$tokens->rollback();
return trim($text . $this->parseText($tokens)->text, " \t");
}
}
$tokens->rollback();
return trim($text . $this->parseText($tokens)->text, " \t");
}
break;
}
if ($this->textBetweenTagsBelongsToDescription) {
if (!$savepoint) {
$tokens->pushSavePoint();
$savepoint = true;
} elseif ($tmpText !== '') {
$tokens->dropSavePoint();
$tokens->pushSavePoint();
}
}
$tokens->pushSavePoint();
$tokens->next();
// if we're at EOL, check what's next
// if next is a PHPDoc tag, EOL, or end of PHPDoc, stop
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) {
$tokens->rollback();
break;
}
// otherwise if the next is text, continue building the description string
$tokens->dropSavePoint();
$text .= $tokens->getDetectedNewline() ?? "\n";
}
if ($savepoint) {
$tokens->rollback();
$text = rtrim($text, $tokens->getDetectedNewline() ?? "\n");
}
return trim($text, " \t");
}
public function parseTag(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagNode
{
$tag = $tokens->currentTokenValue();
@@ -312,7 +489,17 @@ class PhpDocParser
break;
default:
if ($this->parseDoctrineAnnotations) {
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$tagValue = $this->parseDoctrineTagValue($tokens, $tag);
} else {
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescriptionAfterDoctrineTag($tokens));
}
break;
}
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens));
break;
}
@@ -327,6 +514,297 @@ class PhpDocParser
}
private function parseDoctrineTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\PhpDocTagValueNode
{
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
return new Doctrine\DoctrineTagValueNode(
$this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineAnnotation($tag, $this->parseDoctrineArguments($tokens, false)),
$startLine,
$startIndex
),
$this->parseOptionalDescriptionAfterDoctrineTag($tokens)
);
}
/**
* @return list<Doctrine\DoctrineArgument>
*/
private function parseDoctrineArguments(TokenIterator $tokens, bool $deep): array
{
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
return [];
}
if (!$deep) {
$tokens->addEndOfLineToSkippedTokens();
}
$arguments = [];
try {
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
do {
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
break;
}
$arguments[] = $this->parseDoctrineArgument($tokens);
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
} finally {
if (!$deep) {
$tokens->removeEndOfLineFromSkippedTokens();
}
}
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
return $arguments;
}
private function parseDoctrineArgument(TokenIterator $tokens): Doctrine\DoctrineArgument
{
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)),
$startLine,
$startIndex
);
}
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
try {
$tokens->pushSavePoint();
$currentValue = $tokens->currentTokenValue();
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
$key = $this->enrichWithAttributes(
$tokens,
new IdentifierTypeNode($currentValue),
$startLine,
$startIndex
);
$tokens->consumeTokenType(Lexer::TOKEN_EQUAL);
$value = $this->parseDoctrineArgumentValue($tokens);
$tokens->dropSavePoint();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArgument($key, $value),
$startLine,
$startIndex
);
} catch (ParserException $e) {
$tokens->rollback();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)),
$startLine,
$startIndex
);
}
}
/**
* @return DoctrineValueType
*/
private function parseDoctrineArgumentValue(TokenIterator $tokens)
{
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG)) {
$name = $tokens->currentTokenValue();
$tokens->next();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineAnnotation($name, $this->parseDoctrineArguments($tokens, true)),
$startLine,
$startIndex
);
}
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET)) {
$items = [];
do {
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
break;
}
$items[] = $this->parseDoctrineArrayItem($tokens);
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArray($items),
$startLine,
$startIndex
);
}
$currentTokenValue = $tokens->currentTokenValue();
$tokens->pushSavePoint(); // because of ConstFetchNode
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) {
$identifier = $this->enrichWithAttributes(
$tokens,
new Ast\Type\IdentifierTypeNode($currentTokenValue),
$startLine,
$startIndex
);
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
$tokens->dropSavePoint();
return $identifier;
}
$tokens->rollback(); // because of ConstFetchNode
} else {
$tokens->dropSavePoint(); // because of ConstFetchNode
}
$exception = new ParserException(
$tokens->currentTokenValue(),
$tokens->currentTokenType(),
$tokens->currentTokenOffset(),
Lexer::TOKEN_IDENTIFIER,
null,
$tokens->currentTokenLine()
);
try {
$constExpr = $this->constantExprParser->parse($tokens, true);
if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
throw $exception;
}
return $constExpr;
} catch (LogicException $e) {
throw $exception;
}
}
private function parseDoctrineArrayItem(TokenIterator $tokens): Doctrine\DoctrineArrayItem
{
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
try {
$tokens->pushSavePoint();
$key = $this->parseDoctrineArrayKey($tokens);
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL)) {
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_COLON)) {
$tokens->consumeTokenType(Lexer::TOKEN_EQUAL); // will throw exception
}
}
$value = $this->parseDoctrineArgumentValue($tokens);
$tokens->dropSavePoint();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArrayItem($key, $value),
$startLine,
$startIndex
);
} catch (ParserException $e) {
$tokens->rollback();
return $this->enrichWithAttributes(
$tokens,
new Doctrine\DoctrineArrayItem(null, $this->parseDoctrineArgumentValue($tokens)),
$startLine,
$startIndex
);
}
}
/**
* @return ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|ConstFetchNode
*/
private function parseDoctrineArrayKey(TokenIterator $tokens)
{
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();
if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
$key = new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $tokens->currentTokenValue()));
$tokens->next();
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED);
$tokens->next();
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED);
$tokens->next();
} else {
$currentTokenValue = $tokens->currentTokenValue();
$tokens->pushSavePoint(); // because of ConstFetchNode
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) {
$tokens->dropSavePoint();
throw new ParserException(
$tokens->currentTokenValue(),
$tokens->currentTokenType(),
$tokens->currentTokenOffset(),
Lexer::TOKEN_IDENTIFIER,
null,
$tokens->currentTokenLine()
);
}
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
$tokens->dropSavePoint();
return $this->enrichWithAttributes(
$tokens,
new IdentifierTypeNode($currentTokenValue),
$startLine,
$startIndex
);
}
$tokens->rollback();
$constExpr = $this->constantExprParser->parse($tokens, true);
if (!$constExpr instanceof Ast\ConstExpr\ConstFetchNode) {
throw new ParserException(
$tokens->currentTokenValue(),
$tokens->currentTokenType(),
$tokens->currentTokenOffset(),
Lexer::TOKEN_IDENTIFIER,
null,
$tokens->currentTokenLine()
);
}
return $constExpr;
}
return $this->enrichWithAttributes($tokens, $key, $startLine, $startIndex);
}
/**
* @return Ast\PhpDoc\ParamTagValueNode|Ast\PhpDoc\TypelessParamTagValueNode
*/

View File

@@ -9,6 +9,7 @@ use function assert;
use function count;
use function in_array;
use function strlen;
use function substr;
class TokenIterator
{
@@ -22,6 +23,12 @@ class TokenIterator
/** @var int[] */
private $savePoints = [];
/** @var list<int> */
private $skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS];
/** @var string|null */
private $newline = null;
/**
* @param list<array{string, int, int}> $tokens
*/
@@ -30,11 +37,7 @@ class TokenIterator
$this->tokens = $tokens;
$this->index = $index;
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== Lexer::TOKEN_HORIZONTAL_WS) {
return;
}
$this->index++;
$this->skipIrrelevantTokens();
}
@@ -103,6 +106,21 @@ class TokenIterator
}
public function endIndexOfLastRelevantToken(): int
{
$endIndex = $this->currentTokenIndex();
$endIndex--;
while (in_array($this->tokens[$endIndex][Lexer::TYPE_OFFSET], $this->skippedTokenTypes, true)) {
if (!isset($this->tokens[$endIndex - 1])) {
break;
}
$endIndex--;
}
return $endIndex;
}
public function isCurrentTokenValue(string $tokenValue): bool
{
return $this->tokens[$this->index][Lexer::VALUE_OFFSET] === $tokenValue;
@@ -130,13 +148,14 @@ class TokenIterator
$this->throwError($tokenType);
}
$this->index++;
if (($this->tokens[$this->index][Lexer::TYPE_OFFSET] ?? -1) !== Lexer::TOKEN_HORIZONTAL_WS) {
return;
if ($tokenType === Lexer::TOKEN_PHPDOC_EOL) {
if ($this->newline === null) {
$this->detectNewline();
}
}
$this->index++;
$this->skipIrrelevantTokens();
}
@@ -150,12 +169,7 @@ class TokenIterator
}
$this->index++;
if (($this->tokens[$this->index][Lexer::TYPE_OFFSET] ?? -1) !== Lexer::TOKEN_HORIZONTAL_WS) {
return;
}
$this->index++;
$this->skipIrrelevantTokens();
}
@@ -167,10 +181,7 @@ class TokenIterator
}
$this->index++;
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$this->index++;
}
$this->skipIrrelevantTokens();
return true;
}
@@ -183,16 +194,30 @@ class TokenIterator
return false;
}
$this->index++;
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$this->index++;
if ($tokenType === Lexer::TOKEN_PHPDOC_EOL) {
if ($this->newline === null) {
$this->detectNewline();
}
}
$this->index++;
$this->skipIrrelevantTokens();
return true;
}
private function detectNewline(): void
{
$value = $this->currentTokenValue();
if (substr($value, 0, 2) === "\r\n") {
$this->newline = "\r\n";
} elseif (substr($value, 0, 1) === "\n") {
$this->newline = "\n";
}
}
public function getSkippedHorizontalWhiteSpaceIfAny(): string
{
if ($this->index > 0 && $this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
@@ -217,12 +242,34 @@ class TokenIterator
public function next(): void
{
$this->index++;
$this->skipIrrelevantTokens();
}
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== Lexer::TOKEN_HORIZONTAL_WS) {
private function skipIrrelevantTokens(): void
{
if (!isset($this->tokens[$this->index])) {
return;
}
$this->index++;
while (in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $this->skippedTokenTypes, true)) {
if (!isset($this->tokens[$this->index + 1])) {
break;
}
$this->index++;
}
}
public function addEndOfLineToSkippedTokens(): void
{
$this->skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS, Lexer::TOKEN_PHPDOC_EOL];
}
public function removeEndOfLineFromSkippedTokens(): void
{
$this->skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS];
}
/** @phpstan-impure */
@@ -319,6 +366,11 @@ class TokenIterator
return false;
}
public function getDetectedNewline(): ?string
{
return $this->newline;
}
/**
* Whether the given position is immediately surrounded by parenthesis.
*/

View File

@@ -70,23 +70,14 @@ class TypeParser
*/
public function enrichWithAttributes(TokenIterator $tokens, Ast\Node $type, int $startLine, int $startIndex): Ast\Node
{
$endLine = $tokens->currentTokenLine();
$endIndex = $tokens->currentTokenIndex();
if ($this->useLinesAttributes) {
$type->setAttribute(Ast\Attribute::START_LINE, $startLine);
$type->setAttribute(Ast\Attribute::END_LINE, $endLine);
$type->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
}
if ($this->useIndexAttributes) {
$tokensArray = $tokens->getTokens();
$endIndex--;
if ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$endIndex--;
}
$type->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
$type->setAttribute(Ast\Attribute::END_INDEX, $endIndex);
$type->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
}
return $type;

View File

@@ -10,6 +10,11 @@ use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagMethodValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagPropertyValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineAnnotation;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArgument;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArray;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArrayItem;
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
@@ -95,6 +100,8 @@ final class Printer
GenericTypeNode::class . '->genericTypes' => ', ',
ConstExprArrayNode::class . '->items' => ', ',
MethodTagValueNode::class . '->parameters' => ', ',
DoctrineArray::class . '->items' => ', ',
DoctrineAnnotation::class . '->arguments' => ', ',
];
/**
@@ -106,6 +113,8 @@ final class Printer
CallableTypeNode::class . '->parameters' => ['(', '', ''],
ArrayShapeNode::class . '->items' => ['{', '', ''],
ObjectShapeNode::class . '->items' => ['{', '', ''],
DoctrineArray::class . '->items' => ['{', '', ''],
DoctrineAnnotation::class . '->arguments' => ['(', '', ''],
];
/** @var array<string, list<class-string<TypeNode>>> */
@@ -186,6 +195,10 @@ final class Printer
return $node->text;
}
if ($node instanceof PhpDocTagNode) {
if ($node->value instanceof DoctrineTagValueNode) {
return $this->print($node->value);
}
return trim(sprintf('%s %s', $node->name, $this->print($node->value)));
}
if ($node instanceof PhpDocTagValueNode) {
@@ -211,6 +224,18 @@ final class Printer
$isOptional = $node->isOptional ? '=' : '';
return trim("{$type}{$isReference}{$isVariadic}{$node->parameterName}") . $isOptional;
}
if ($node instanceof DoctrineAnnotation) {
return (string) $node;
}
if ($node instanceof DoctrineArgument) {
return (string) $node;
}
if ($node instanceof DoctrineArray) {
return (string) $node;
}
if ($node instanceof DoctrineArrayItem) {
return (string) $node;
}
throw new LogicException(sprintf('Unknown node type %s', get_class($node)));
}
@@ -491,7 +516,7 @@ final class Printer
[$isMultiline, $beforeAsteriskIndent, $afterAsteriskIndent] = $this->isMultiline($tokenIndex, $originalNodes, $originalTokens);
if ($insertStr === "\n * ") {
$insertStr = sprintf("\n%s*%s", $beforeAsteriskIndent, $afterAsteriskIndent);
$insertStr = sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
}
foreach ($diff as $i => $diffElem) {
@@ -524,7 +549,7 @@ final class Printer
}
if ($insertNewline) {
$result .= $insertStr . sprintf("\n%s*%s", $beforeAsteriskIndent, $afterAsteriskIndent);
$result .= $insertStr . sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
} else {
$result .= $insertStr;
}
@@ -534,7 +559,8 @@ final class Printer
}
$parenthesesNeeded = isset($this->parenthesesListMap[$mapKey])
&& in_array(get_class($newNode), $this->parenthesesListMap[$mapKey], true);
&& in_array(get_class($newNode), $this->parenthesesListMap[$mapKey], true)
&& !in_array(get_class($originalNode), $this->parenthesesListMap[$mapKey], true);
$addParentheses = $parenthesesNeeded && !$originalTokens->hasParentheses($itemStartPos, $itemEndPos);
if ($addParentheses) {
$result .= '(';
@@ -567,7 +593,7 @@ final class Printer
$itemEndPos = $tokenIndex - 1;
if ($insertNewline) {
$result .= $insertStr . sprintf("\n%s*%s", $beforeAsteriskIndent, $afterAsteriskIndent);
$result .= $insertStr . sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
} else {
$result .= $insertStr;
}
@@ -636,7 +662,7 @@ final class Printer
if (!$first) {
$result .= $insertStr;
if ($insertNewline) {
$result .= sprintf("\n%s*%s", $beforeAsteriskIndent, $afterAsteriskIndent);
$result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
}
}
@@ -777,6 +803,12 @@ final class Printer
$mapKey = get_class($node) . '->' . $subNodeName;
$parenthesesNeeded = isset($this->parenthesesMap[$mapKey])
&& in_array(get_class($subNode), $this->parenthesesMap[$mapKey], true);
if ($subNode->getAttribute(Attribute::ORIGINAL_NODE) !== null) {
$parenthesesNeeded = $parenthesesNeeded
&& !in_array(get_class($subNode->getAttribute(Attribute::ORIGINAL_NODE)), $this->parenthesesMap[$mapKey], true);
}
$addParentheses = $parenthesesNeeded && !$originalTokens->hasParentheses($subStartPos, $subEndPos);
if ($addParentheses) {
$result .= '(';

Binary file not shown.

View File

@@ -1,16 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmRaZmcACgkQUcZzBf/C
5cAn/Q//fbWiR/qaSvlHpk73KH7iDfoHwNvRrHSQODZdMa4PGiEbL+SXsKnRxFDo
kEJZwgU5qi3WMflt7Ml3dYDlQDgoDerdaiySYFoBcv1NXDWKoF7+Egy1AHxpfNq+
FMCkZNR2ulSaYUCofM4GkTNap4yVkPCy289ZU6yUmRnJxF+hh/CFfdVPAPbwh/a6
UqV3R2ENJZSbtA1pzSTBpUPQGQ9qcsqngKyNyxk1hEd9opdMg2eSFvO1e1ZZm/Tk
Kgh5wCbsbSJuRPGO4vbiybTeO/qXPDlHV6oA5SHnjJ4H24phCsHdyJHHvLQmrUeR
BKHgnH1y/b5J9cgr9OgEQJK9TMHHd6dii9//Qp+0rUZIDZ4Ym2lDSA/Vn/D9GoV3
zo4QYzW3TvE3QMdnLcX/ZtaLliPdDYIaYUXOiyaYwLFGVxSWZWOC5IN0G0bLJb39
Ca/z839nkWdMqg68q/oHC2Nk/v/KZnKg1RlRjYhj53T6nr0JDEiaYMyETSOIFsVX
AcCQnLLwMndUAibJAyORDnTk+ipg0SecFoPvvhea1BtlTfhSDIlrT4OPKZ5nExzd
nR/zGbIH8lCvsBc+hq+Kgodtfs5nauwEOwlVUwet26xL1YKOd0jxz+Zp6tgk0wba
cMf5L9fm85j83DQYr7Ukaaj81kmMujRWDo/dRojKhUlJUrNnjXA=
=jTtX
iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmS32poACgkQUcZzBf/C
5cCvmg/+JJmyX663fa+FHy7ED2SexVuChivpbp82dyLx1gRAl15rtNG4zjxNRfnW
6GpsysMhKqrN7p6xur18ZkLqdFKAjeNnpTunnh/ADetcrs8wzLNyAy7luQtyXAuj
SOv5f/Yitg9yvZ2GHrbzchQuSjkbUR2KroBYsRhwVTH7pMIgdvysRBYiENfbz350
n91WOCApDnVCygzEhBbhkwA/xklJnUxkRJX3AlbbCwES9K64ELyGd0BqJ1Ohy2a7
4cFjwRJq9/tXf99fyncamN8xyBdvYBXSNRNMPYcjKqKIZCOePlR8Q3b7nt154w+e
w2qnAevOB4dYzJaSjwJlaVQYR1YIQ7NlYkGboONq/lrtJlEejDdiRmGvgHZ8nSYW
Ob2JwqgYDfUPfsnXAwXM+whpUNJi30MDB7MSw3SiDlyw690HheT/DCKOJ9yNUiOB
TSGkbIGW/ASett78gowjwniYdryE5ufUPwZbkSaFC3CDysHfs6Jgc+lxe3wnOHtD
WyPl1TqDRNuLOZ26TgxI3gGEYqMcVDYQfmuiOakoebHx6j0bpvyEaP51j0/JFpu6
okKulXgC1DUluKFWMPhobPQRZ8zC29macnU74JvmJIiUhfiP2Pl16D+XcjFW++zH
EDEghcCdgz0pIF6UI5j02rbNAfu7Oo685pnYeXq0DexgXjqoFOE=
=NF4z
-----END PGP SIGNATURE-----

View File

@@ -2,6 +2,21 @@
All notable changes to `array-to-xml` will be documented in this file
## 3.1.6 - 2023-05-11
### What's Changed
- V3 - Code smell ('incorrect' method call) by @ExeQue in https://github.com/spatie/array-to-xml/pull/208
- Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/array-to-xml/pull/210
- Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/array-to-xml/pull/214
- Add addXmlDeclaration parameter by @silnex in https://github.com/spatie/array-to-xml/pull/216
### New Contributors
- @silnex made their first contribution in https://github.com/spatie/array-to-xml/pull/216
**Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.1.5...3.1.6
## 3.1.5 - 2022-12-24
### What's Changed

View File

@@ -239,6 +239,58 @@ This will result in:
</helloyouluckypeople>
```
### Using Closure values
The package can use Closure values:
```php
$users = [
[
'name' => 'one',
'age' => 10,
],
[
'name' => 'two',
'age' => 12,
],
];
$array = [
'users' => function () use ($users) {
$new_users = [];
foreach ($users as $user) {
$new_users[] = array_merge(
$user,
[
'double_age' => $user['age'] * 2,
]
);
}
return $new_users;
},
];
ArrayToXml::convert($array)
```
This will result in:
```xml
<?xml version="1.0"?>
<root>
<users>
<name>one</name>
<age>10</age>
<double_age>20</double_age>
</users>
<users>
<name>two</name>
<age>12</age>
<double_age>24</double_age>
</users>
</root>
```
### Handling numeric keys
The package can also can handle numeric keys:

View File

@@ -2,6 +2,7 @@
namespace Spatie\ArrayToXml;
use Closure;
use DOMDocument;
use DOMElement;
use DOMException;
@@ -143,6 +144,10 @@ class ArrayToXml
protected function convertElement(DOMElement $element, mixed $value): void
{
if ($value instanceof Closure) {
$value = $value();
}
$sequential = $this->isArrayAllKeySequential($value);
if (! is_array($value)) {

View File

@@ -105,11 +105,14 @@ class Application implements ResetInterface
/**
* @final
*/
public function setDispatcher(EventDispatcherInterface $dispatcher)
public function setDispatcher(EventDispatcherInterface $dispatcher): void
{
$this->dispatcher = $dispatcher;
}
/**
* @return void
*/
public function setCommandLoader(CommandLoaderInterface $commandLoader)
{
$this->commandLoader = $commandLoader;
@@ -118,12 +121,15 @@ class Application implements ResetInterface
public function getSignalRegistry(): SignalRegistry
{
if (!$this->signalRegistry) {
throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
throw new RuntimeException('Signals are not supported. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
}
return $this->signalRegistry;
}
/**
* @return void
*/
public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
{
$this->signalsToDispatchEvent = $signalsToDispatchEvent;
@@ -317,10 +323,16 @@ class Application implements ResetInterface
return $exitCode;
}
/**
* @return void
*/
public function reset()
{
}
/**
* @return void
*/
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
@@ -334,6 +346,9 @@ class Application implements ResetInterface
return $this->helperSet ??= $this->getDefaultHelperSet();
}
/**
* @return void
*/
public function setDefinition(InputDefinition $definition)
{
$this->definition = $definition;
@@ -404,6 +419,8 @@ class Application implements ResetInterface
/**
* Sets whether to catch exceptions or not during commands execution.
*
* @return void
*/
public function setCatchExceptions(bool $boolean)
{
@@ -420,6 +437,8 @@ class Application implements ResetInterface
/**
* Sets whether to automatically exit after a command execution or not.
*
* @return void
*/
public function setAutoExit(bool $boolean)
{
@@ -436,7 +455,9 @@ class Application implements ResetInterface
/**
* Sets the application name.
**/
*
* @return void
*/
public function setName(string $name)
{
$this->name = $name;
@@ -452,6 +473,8 @@ class Application implements ResetInterface
/**
* Sets the application version.
*
* @return void
*/
public function setVersion(string $version)
{
@@ -490,6 +513,8 @@ class Application implements ResetInterface
* If a Command is not enabled it will not be added.
*
* @param Command[] $commands An array of commands
*
* @return void
*/
public function addCommands(array $commands)
{
@@ -687,9 +712,7 @@ class Application implements ResetInterface
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
// remove hidden commands
$alternatives = array_filter($alternatives, function ($name) {
return !$this->get($name)->isHidden();
});
$alternatives = array_filter($alternatives, fn ($name) => !$this->get($name)->isHidden());
if (1 == \count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
@@ -840,9 +863,7 @@ class Application implements ResetInterface
}
if (str_contains($message, "@anonymous\0")) {
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
}, $message);
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
}
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
@@ -903,6 +924,8 @@ class Application implements ResetInterface
/**
* Configures the input and output instances based on the user arguments and options.
*
* @return void
*/
protected function configureIO(InputInterface $input, OutputInterface $output)
{
@@ -977,44 +1000,62 @@ class Application implements ResetInterface
}
}
if ($this->signalsToDispatchEvent) {
$commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];
$commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];
if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) {
if (!$this->signalRegistry) {
throw new RuntimeException('Unable to subscribe to signal events. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
}
if ($commandSignals || null !== $this->dispatcher) {
if (!$this->signalRegistry) {
throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
}
if (Terminal::hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
if (Terminal::hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
foreach ([\SIGINT, \SIGTERM] as $signal) {
$this->signalRegistry->register($signal, static function () use ($sttyMode) {
shell_exec('stty '.$sttyMode);
});
}
foreach ([\SIGINT, \SIGTERM] as $signal) {
$this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode));
}
}
if (null !== $this->dispatcher) {
if ($this->dispatcher) {
// We register application signals, so that we can dispatch the event
foreach ($this->signalsToDispatchEvent as $signal) {
$event = new ConsoleSignalEvent($command, $input, $output, $signal);
$this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) {
$this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) {
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
$exitCode = $event->getExitCode();
// No more handlers, we try to simulate PHP default behavior
if (!$hasNext) {
if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) {
exit(0);
// If the command is signalable, we call the handleSignal() method
if (\in_array($signal, $commandSignals, true)) {
$exitCode = $command->handleSignal($signal, $exitCode);
// BC layer for Symfony <= 5
if (null === $exitCode) {
trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command));
$exitCode = 0;
}
}
if (false !== $exitCode) {
exit($exitCode);
}
});
}
// then we register command signals, but not if already handled after the dispatcher
$commandSignals = array_diff($commandSignals, $this->signalsToDispatchEvent);
}
foreach ($commandSignals as $signal) {
$this->signalRegistry->register($signal, [$command, 'handleSignal']);
$this->signalRegistry->register($signal, function (int $signal) use ($command): void {
$exitCode = $command->handleSignal($signal);
// BC layer for Symfony <= 5
if (null === $exitCode) {
trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command));
$exitCode = 0;
}
if (false !== $exitCode) {
exit($exitCode);
}
});
}
}
@@ -1170,7 +1211,7 @@ class Application implements ResetInterface
}
}
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
$alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold);
ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);
return array_keys($alternatives);
@@ -1261,7 +1302,7 @@ class Application implements ResetInterface
return $namespaces;
}
private function init()
private function init(): void
{
if ($this->initialized) {
return;

View File

@@ -1,6 +1,13 @@
CHANGELOG
=========
6.3
---
* Add support for choosing exit code while handling signal, or to not exit at all
* Add `ProgressBar::setPlaceholderFormatter` to set a placeholder attached to a instance, instead of being global.
* Add `ReStructuredTextDescriptor`
6.2
---

View File

@@ -141,12 +141,17 @@ class Command
* Ignores validation errors.
*
* This is mainly useful for the help command.
*
* @return void
*/
public function ignoreValidationErrors()
{
$this->ignoreValidationErrors = true;
}
/**
* @return void
*/
public function setApplication(Application $application = null)
{
if (1 > \func_num_args()) {
@@ -162,6 +167,9 @@ class Command
$this->fullDefinition = null;
}
/**
* @return void
*/
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
@@ -198,6 +206,8 @@ class Command
/**
* Configures the current command.
*
* @return void
*/
protected function configure()
{
@@ -228,6 +238,8 @@ class Command
* This method is executed before the InputDefinition is validated.
* This means that this is the only place where the command can
* interactively ask for values of missing required arguments.
*
* @return void
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
@@ -242,6 +254,8 @@ class Command
*
* @see InputInterface::bind()
* @see InputInterface::validate()
*
* @return void
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
@@ -378,7 +392,7 @@ class Command
*
* @internal
*/
public function mergeApplicationDefinition(bool $mergeArgs = true)
public function mergeApplicationDefinition(bool $mergeArgs = true): void
{
if (null === $this->application) {
return;
@@ -702,7 +716,7 @@ class Command
*
* @throws InvalidArgumentException When the name is invalid
*/
private function validateName(string $name)
private function validateName(string $name): void
{
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));

View File

@@ -74,7 +74,7 @@ final class CompleteCommand extends Command
;
}
protected function initialize(InputInterface $input, OutputInterface $output)
protected function initialize(InputInterface $input, OutputInterface $output): void
{
$this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL);
}
@@ -134,12 +134,12 @@ final class CompleteCommand extends Command
$completionInput->bind($command->getDefinition());
if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) {
$this->log(' Completing option names for the <comment>'.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).'</> command.');
$this->log(' Completing option names for the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> command.');
$suggestions->suggestOptions($command->getDefinition()->getOptions());
} else {
$this->log([
' Completing using the <comment>'.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).'</> class.',
' Completing using the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> class.',
' Completing <comment>'.$completionInput->getCompletionType().'</> for <comment>'.$completionInput->getCompletionName().'</>',
]);
if (null !== $compval = $completionInput->getCompletionValue()) {
@@ -155,7 +155,7 @@ final class CompleteCommand extends Command
$this->log('<info>Suggestions:</>');
if ($options = $suggestions->getOptionSuggestions()) {
$this->log(' --'.implode(' --', array_map(function ($o) { return $o->getName(); }, $options)));
$this->log(' --'.implode(' --', array_map(fn ($o) => $o->getName(), $options)));
} elseif ($values = $suggestions->getValueSuggestions()) {
$this->log(' '.implode(' ', $values));
} else {

View File

@@ -39,7 +39,7 @@ final class DumpCompletionCommand extends Command
private array $supportedShells;
protected function configure()
protected function configure(): void
{
$fullCommand = $_SERVER['PHP_SELF'];
$commandName = basename($fullCommand);

View File

@@ -27,6 +27,9 @@ class HelpCommand extends Command
{
private Command $command;
/**
* @return void
*/
protected function configure()
{
$this->ignoreValidationErrors();
@@ -34,12 +37,8 @@ class HelpCommand extends Command
$this
->setName('help')
->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', function () {
return array_keys((new ApplicationDescription($this->getApplication()))->getCommands());
}),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () {
return (new DescriptorHelper())->getFormats();
}),
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', fn () => array_keys((new ApplicationDescription($this->getApplication()))->getCommands())),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
])
->setDescription('Display help for a command')
@@ -58,6 +57,9 @@ EOF
;
}
/**
* @return void
*/
public function setCommand(Command $command)
{
$this->command = $command;

View File

@@ -25,18 +25,17 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class ListCommand extends Command
{
/**
* @return void
*/
protected function configure()
{
$this
->setName('list')
->setDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, function () {
return array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces());
}),
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () {
return (new DescriptorHelper())->getFormats();
}),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()),
new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
])
->setDescription('List commands')

View File

@@ -32,7 +32,7 @@ trait LockableTrait
private function lock(string $name = null, bool $blocking = false): bool
{
if (!class_exists(SemaphoreStore::class)) {
throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
throw new LogicException('To enable the locking feature you must install the symfony/lock component. Try running "composer require symfony/lock".');
}
if (null !== $this->lock) {
@@ -58,7 +58,7 @@ trait LockableTrait
/**
* Releases the command lock if there is one.
*/
private function release()
private function release(): void
{
if ($this->lock) {
$this->lock->release();

View File

@@ -25,6 +25,10 @@ interface SignalableCommandInterface
/**
* The method will be called when the application is signaled.
*
* @param int|false $previousExitCode
* @return int|false The exit code to return or false to continue the normal execution
*/
public function handleSignal(int $signal): void;
public function handleSignal(int $signal, /* int|false $previousExitCode = 0 */);
}

View File

@@ -34,7 +34,7 @@ final class CompletionInput extends ArgvInput
private $tokens;
private $currentIndex;
private $completionType;
private $completionName = null;
private $completionName;
private $completionValue = '';
/**

View File

@@ -29,6 +29,9 @@ use Symfony\Component\DependencyInjection\TypedReference;
*/
class AddConsoleCommandPass implements CompilerPassInterface
{
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
$commandServices = $container->findTaggedServiceIds('console.command', true);

View File

@@ -79,7 +79,7 @@ class ApplicationDescription
return $this->commands[$name] ?? $this->aliases[$name];
}
private function inspectApplication()
private function inspectApplication(): void
{
$this->commands = [];
$this->namespaces = [];

View File

@@ -26,12 +26,9 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
abstract class Descriptor implements DescriptorInterface
{
/**
* @var OutputInterface
*/
protected $output;
protected OutputInterface $output;
public function describe(OutputInterface $output, object $object, array $options = [])
public function describe(OutputInterface $output, object $object, array $options = []): void
{
$this->output = $output;
@@ -45,10 +42,7 @@ abstract class Descriptor implements DescriptorInterface
};
}
/**
* Writes content to output.
*/
protected function write(string $content, bool $decorated = false)
protected function write(string $content, bool $decorated = false): void
{
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
}
@@ -56,25 +50,25 @@ abstract class Descriptor implements DescriptorInterface
/**
* Describes an InputArgument instance.
*/
abstract protected function describeInputArgument(InputArgument $argument, array $options = []);
abstract protected function describeInputArgument(InputArgument $argument, array $options = []): void;
/**
* Describes an InputOption instance.
*/
abstract protected function describeInputOption(InputOption $option, array $options = []);
abstract protected function describeInputOption(InputOption $option, array $options = []): void;
/**
* Describes an InputDefinition instance.
*/
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []);
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []): void;
/**
* Describes a Command instance.
*/
abstract protected function describeCommand(Command $command, array $options = []);
abstract protected function describeCommand(Command $command, array $options = []): void;
/**
* Describes an Application instance.
*/
abstract protected function describeApplication(Application $application, array $options = []);
abstract protected function describeApplication(Application $application, array $options = []): void;
}

View File

@@ -20,5 +20,8 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
interface DescriptorInterface
{
/**
* @return void
*/
public function describe(OutputInterface $output, object $object, array $options = []);
}

View File

@@ -26,12 +26,12 @@ use Symfony\Component\Console\Input\InputOption;
*/
class JsonDescriptor extends Descriptor
{
protected function describeInputArgument(InputArgument $argument, array $options = [])
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
$this->writeData($this->getInputArgumentData($argument), $options);
}
protected function describeInputOption(InputOption $option, array $options = [])
protected function describeInputOption(InputOption $option, array $options = []): void
{
$this->writeData($this->getInputOptionData($option), $options);
if ($option->isNegatable()) {
@@ -39,17 +39,17 @@ class JsonDescriptor extends Descriptor
}
}
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
$this->writeData($this->getInputDefinitionData($definition), $options);
}
protected function describeCommand(Command $command, array $options = [])
protected function describeCommand(Command $command, array $options = []): void
{
$this->writeData($this->getCommandData($command, $options['short'] ?? false), $options);
}
protected function describeApplication(Application $application, array $options = [])
protected function describeApplication(Application $application, array $options = []): void
{
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace, true);
@@ -81,7 +81,7 @@ class JsonDescriptor extends Descriptor
/**
* Writes data as json.
*/
private function writeData(array $data, array $options)
private function writeData(array $data, array $options): void
{
$flags = $options['json_encoding'] ?? 0;

View File

@@ -28,7 +28,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class MarkdownDescriptor extends Descriptor
{
public function describe(OutputInterface $output, object $object, array $options = [])
public function describe(OutputInterface $output, object $object, array $options = []): void
{
$decorated = $output->isDecorated();
$output->setDecorated(false);
@@ -38,12 +38,12 @@ class MarkdownDescriptor extends Descriptor
$output->setDecorated($decorated);
}
protected function write(string $content, bool $decorated = true)
protected function write(string $content, bool $decorated = true): void
{
parent::write($content, $decorated);
}
protected function describeInputArgument(InputArgument $argument, array $options = [])
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
$this->write(
'#### `'.($argument->getName() ?: '<none>')."`\n\n"
@@ -54,7 +54,7 @@ class MarkdownDescriptor extends Descriptor
);
}
protected function describeInputOption(InputOption $option, array $options = [])
protected function describeInputOption(InputOption $option, array $options = []): void
{
$name = '--'.$option->getName();
if ($option->isNegatable()) {
@@ -75,15 +75,13 @@ class MarkdownDescriptor extends Descriptor
);
}
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
if ($showArguments = \count($definition->getArguments()) > 0) {
$this->write('### Arguments');
foreach ($definition->getArguments() as $argument) {
$this->write("\n\n");
if (null !== $describeInputArgument = $this->describeInputArgument($argument)) {
$this->write($describeInputArgument);
}
$this->describeInputArgument($argument);
}
}
@@ -95,14 +93,12 @@ class MarkdownDescriptor extends Descriptor
$this->write('### Options');
foreach ($definition->getOptions() as $option) {
$this->write("\n\n");
if (null !== $describeInputOption = $this->describeInputOption($option)) {
$this->write($describeInputOption);
}
$this->describeInputOption($option);
}
}
}
protected function describeCommand(Command $command, array $options = [])
protected function describeCommand(Command $command, array $options = []): void
{
if ($options['short'] ?? false) {
$this->write(
@@ -110,9 +106,7 @@ class MarkdownDescriptor extends Descriptor
.str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
.'### Usage'."\n\n"
.array_reduce($command->getAliases(), function ($carry, $usage) {
return $carry.'* `'.$usage.'`'."\n";
})
.array_reduce($command->getAliases(), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
);
return;
@@ -125,9 +119,7 @@ class MarkdownDescriptor extends Descriptor
.str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
.'### Usage'."\n\n"
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) {
return $carry.'* `'.$usage.'`'."\n";
})
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
);
if ($help = $command->getProcessedHelp()) {
@@ -142,7 +134,7 @@ class MarkdownDescriptor extends Descriptor
}
}
protected function describeApplication(Application $application, array $options = [])
protected function describeApplication(Application $application, array $options = []): void
{
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace);
@@ -157,16 +149,12 @@ class MarkdownDescriptor extends Descriptor
}
$this->write("\n\n");
$this->write(implode("\n", array_map(function ($commandName) use ($description) {
return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName()));
}, $namespace['commands'])));
$this->write(implode("\n", array_map(fn ($commandName) => sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands'])));
}
foreach ($description->getCommands() as $command) {
$this->write("\n\n");
if (null !== $describeCommand = $this->describeCommand($command, $options)) {
$this->write($describeCommand);
}
$this->describeCommand($command, $options);
}
}

View File

@@ -0,0 +1,272 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\String\UnicodeString;
class ReStructuredTextDescriptor extends Descriptor
{
// <h1>
private string $partChar = '=';
// <h2>
private string $chapterChar = '-';
// <h3>
private string $sectionChar = '~';
// <h4>
private string $subsectionChar = '.';
// <h5>
private string $subsubsectionChar = '^';
// <h6>
private string $paragraphsChar = '"';
private array $visibleNamespaces = [];
public function describe(OutputInterface $output, object $object, array $options = []): void
{
$decorated = $output->isDecorated();
$output->setDecorated(false);
parent::describe($output, $object, $options);
$output->setDecorated($decorated);
}
/**
* Override parent method to set $decorated = true.
*/
protected function write(string $content, bool $decorated = true): void
{
parent::write($content, $decorated);
}
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
$this->write(
$argument->getName() ?: '<none>'."\n".str_repeat($this->paragraphsChar, Helper::width($argument->getName()))."\n\n"
.($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
.'- **Is required**: '.($argument->isRequired() ? 'yes' : 'no')."\n"
.'- **Is array**: '.($argument->isArray() ? 'yes' : 'no')."\n"
.'- **Default**: ``'.str_replace("\n", '', var_export($argument->getDefault(), true)).'``'
);
}
protected function describeInputOption(InputOption $option, array $options = []): void
{
$name = '\-\-'.$option->getName();
if ($option->isNegatable()) {
$name .= '|\-\-no-'.$option->getName();
}
if ($option->getShortcut()) {
$name .= '|-'.str_replace('|', '|-', $option->getShortcut());
}
$optionDescription = $option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n\n", $option->getDescription())."\n\n" : '';
$optionDescription = (new UnicodeString($optionDescription))->ascii();
$this->write(
$name."\n".str_repeat($this->paragraphsChar, Helper::width($name))."\n\n"
.$optionDescription
.'- **Accept value**: '.($option->acceptValue() ? 'yes' : 'no')."\n"
.'- **Is value required**: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
.'- **Is multiple**: '.($option->isArray() ? 'yes' : 'no')."\n"
.'- **Is negatable**: '.($option->isNegatable() ? 'yes' : 'no')."\n"
.'- **Default**: ``'.str_replace("\n", '', var_export($option->getDefault(), true)).'``'."\n"
);
}
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
if ($showArguments = ((bool) $definition->getArguments())) {
$this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9))."\n\n";
foreach ($definition->getArguments() as $argument) {
$this->write("\n\n");
$this->describeInputArgument($argument);
}
}
if ($nonDefaultOptions = $this->getNonDefaultOptions($definition)) {
if ($showArguments) {
$this->write("\n\n");
}
$this->write("Options\n".str_repeat($this->subsubsectionChar, 7)."\n\n");
foreach ($nonDefaultOptions as $option) {
$this->describeInputOption($option);
$this->write("\n");
}
}
}
protected function describeCommand(Command $command, array $options = []): void
{
if ($options['short'] ?? false) {
$this->write(
'``'.$command->getName()."``\n"
.str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
."Usage\n".str_repeat($this->paragraphsChar, 5)."\n\n"
.array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
);
return;
}
$command->mergeApplicationDefinition(false);
foreach ($command->getAliases() as $alias) {
$this->write('.. _'.$alias.":\n\n");
}
$this->write(
$command->getName()."\n"
.str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
."Usage\n".str_repeat($this->subsubsectionChar, 5)."\n\n"
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
);
if ($help = $command->getProcessedHelp()) {
$this->write("\n");
$this->write($help);
}
$definition = $command->getDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->write("\n\n");
$this->describeInputDefinition($definition);
}
}
protected function describeApplication(Application $application, array $options = []): void
{
$description = new ApplicationDescription($application, $options['namespace'] ?? null);
$title = $this->getApplicationTitle($application);
$this->write($title."\n".str_repeat($this->partChar, Helper::width($title)));
$this->createTableOfContents($description, $application);
$this->describeCommands($application, $options);
}
private function getApplicationTitle(Application $application): string
{
if ('UNKNOWN' === $application->getName()) {
return 'Console Tool';
}
if ('UNKNOWN' !== $application->getVersion()) {
return sprintf('%s %s', $application->getName(), $application->getVersion());
}
return $application->getName();
}
private function describeCommands($application, array $options): void
{
$title = 'Commands';
$this->write("\n\n$title\n".str_repeat($this->chapterChar, Helper::width($title))."\n\n");
foreach ($this->visibleNamespaces as $namespace) {
if ('_global' === $namespace) {
$commands = $application->all('');
$this->write('Global'."\n".str_repeat($this->sectionChar, Helper::width('Global'))."\n\n");
} else {
$commands = $application->all($namespace);
$this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
}
foreach ($this->removeAliasesAndHiddenCommands($commands) as $command) {
$this->describeCommand($command, $options);
$this->write("\n\n");
}
}
}
private function createTableOfContents(ApplicationDescription $description, Application $application): void
{
$this->setVisibleNamespaces($description);
$chapterTitle = 'Table of Contents';
$this->write("\n\n$chapterTitle\n".str_repeat($this->chapterChar, Helper::width($chapterTitle))."\n\n");
foreach ($this->visibleNamespaces as $namespace) {
if ('_global' === $namespace) {
$commands = $application->all('');
} else {
$commands = $application->all($namespace);
$this->write("\n\n");
$this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
}
$commands = $this->removeAliasesAndHiddenCommands($commands);
$this->write("\n\n");
$this->write(implode("\n", array_map(static fn ($commandName) => sprintf('- `%s`_', $commandName), array_keys($commands))));
}
}
private function getNonDefaultOptions(InputDefinition $definition): array
{
$globalOptions = [
'help',
'quiet',
'verbose',
'version',
'ansi',
'no-interaction',
];
$nonDefaultOptions = [];
foreach ($definition->getOptions() as $option) {
// Skip global options.
if (!\in_array($option->getName(), $globalOptions)) {
$nonDefaultOptions[] = $option;
}
}
return $nonDefaultOptions;
}
private function setVisibleNamespaces(ApplicationDescription $description): void
{
$commands = $description->getCommands();
foreach ($description->getNamespaces() as $namespace) {
try {
$namespaceCommands = $namespace['commands'];
foreach ($namespaceCommands as $key => $commandName) {
if (!\array_key_exists($commandName, $commands)) {
// If the array key does not exist, then this is an alias.
unset($namespaceCommands[$key]);
} elseif ($commands[$commandName]->isHidden()) {
unset($namespaceCommands[$key]);
}
}
if (!$namespaceCommands) {
// If the namespace contained only aliases or hidden commands, skip the namespace.
continue;
}
} catch (\Exception) {
}
$this->visibleNamespaces[] = $namespace['id'];
}
}
private function removeAliasesAndHiddenCommands(array $commands): array
{
foreach ($commands as $key => $command) {
if ($command->isHidden() || \in_array($key, $command->getAliases(), true)) {
unset($commands[$key]);
}
}
unset($commands['completion']);
return $commands;
}
}

View File

@@ -28,7 +28,7 @@ use Symfony\Component\Console\Input\InputOption;
*/
class TextDescriptor extends Descriptor
{
protected function describeInputArgument(InputArgument $argument, array $options = [])
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
@@ -48,7 +48,7 @@ class TextDescriptor extends Descriptor
), $options);
}
protected function describeInputOption(InputOption $option, array $options = [])
protected function describeInputOption(InputOption $option, array $options = []): void
{
if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
@@ -83,7 +83,7 @@ class TextDescriptor extends Descriptor
), $options);
}
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
foreach ($definition->getArguments() as $argument) {
@@ -122,7 +122,7 @@ class TextDescriptor extends Descriptor
}
}
protected function describeCommand(Command $command, array $options = [])
protected function describeCommand(Command $command, array $options = []): void
{
$command->mergeApplicationDefinition(false);
@@ -157,7 +157,7 @@ class TextDescriptor extends Descriptor
}
}
protected function describeApplication(Application $application, array $options = [])
protected function describeApplication(Application $application, array $options = []): void
{
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace);
@@ -193,9 +193,7 @@ class TextDescriptor extends Descriptor
}
// calculate max. width based on available commands per namespace
$width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) {
return array_intersect($namespace['commands'], array_keys($commands));
}, array_values($namespaces)))));
$width = $this->getColumnWidth(array_merge(...array_values(array_map(fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces)))));
if ($describedNamespace) {
$this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
@@ -204,9 +202,7 @@ class TextDescriptor extends Descriptor
}
foreach ($namespaces as $namespace) {
$namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) {
return isset($commands[$name]);
});
$namespace['commands'] = array_filter($namespace['commands'], fn ($name) => isset($commands[$name]));
if (!$namespace['commands']) {
continue;
@@ -230,7 +226,7 @@ class TextDescriptor extends Descriptor
}
}
private function writeText(string $content, array $options = [])
private function writeText(string $content, array $options = []): void
{
$this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,

View File

@@ -120,27 +120,27 @@ class XmlDescriptor extends Descriptor
return $dom;
}
protected function describeInputArgument(InputArgument $argument, array $options = [])
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
$this->writeDocument($this->getInputArgumentDocument($argument));
}
protected function describeInputOption(InputOption $option, array $options = [])
protected function describeInputOption(InputOption $option, array $options = []): void
{
$this->writeDocument($this->getInputOptionDocument($option));
}
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
$this->writeDocument($this->getInputDefinitionDocument($definition));
}
protected function describeCommand(Command $command, array $options = [])
protected function describeCommand(Command $command, array $options = []): void
{
$this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false));
}
protected function describeApplication(Application $application, array $options = [])
protected function describeApplication(Application $application, array $options = []): void
{
$this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false));
}
@@ -148,7 +148,7 @@ class XmlDescriptor extends Descriptor
/**
* Appends document children to parent node.
*/
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent): void
{
foreach ($importedParent->childNodes as $childNode) {
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
@@ -158,7 +158,7 @@ class XmlDescriptor extends Descriptor
/**
* Writes DOM document.
*/
private function writeDocument(\DOMDocument $dom)
private function writeDocument(\DOMDocument $dom): void
{
$dom->formatOutput = true;
$this->write($dom->saveXML());

View File

@@ -21,15 +21,36 @@ use Symfony\Component\Console\Output\OutputInterface;
final class ConsoleSignalEvent extends ConsoleEvent
{
private int $handlingSignal;
private int|false $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal)
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal, int|false $exitCode = 0)
{
parent::__construct($command, $input, $output);
$this->handlingSignal = $handlingSignal;
$this->exitCode = $exitCode;
}
public function getHandlingSignal(): int
{
return $this->handlingSignal;
}
public function setExitCode(int $exitCode): void
{
if ($exitCode < 0 || $exitCode > 255) {
throw new \InvalidArgumentException('Exit code must be between 0 and 255.');
}
$this->exitCode = $exitCode;
}
public function abortExit(): void
{
$this->exitCode = false;
}
public function getExitCode(): int|false
{
return $this->exitCode;
}
}

View File

@@ -31,6 +31,9 @@ class ErrorListener implements EventSubscriberInterface
$this->logger = $logger;
}
/**
* @return void
*/
public function onConsoleError(ConsoleErrorEvent $event)
{
if (null === $this->logger) {
@@ -48,6 +51,9 @@ class ErrorListener implements EventSubscriberInterface
$this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
}
/**
* @return void
*/
public function onConsoleTerminate(ConsoleTerminateEvent $event)
{
if (null === $this->logger) {

View File

@@ -81,6 +81,9 @@ class OutputFormatter implements WrappableOutputFormatterInterface
$this->styleStack = new OutputFormatterStyleStack();
}
/**
* @return void
*/
public function setDecorated(bool $decorated)
{
$this->decorated = $decorated;
@@ -91,6 +94,9 @@ class OutputFormatter implements WrappableOutputFormatterInterface
return $this->decorated;
}
/**
* @return void
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style)
{
$this->styles[strtolower($name)] = $style;
@@ -115,6 +121,9 @@ class OutputFormatter implements WrappableOutputFormatterInterface
return $this->formatAndWrap($message, 0);
}
/**
* @return string
*/
public function formatAndWrap(?string $message, int $width)
{
if (null === $message) {

View File

@@ -20,6 +20,8 @@ interface OutputFormatterInterface
{
/**
* Sets the decorated flag.
*
* @return void
*/
public function setDecorated(bool $decorated);
@@ -30,6 +32,8 @@ interface OutputFormatterInterface
/**
* Sets a new style.
*
* @return void
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style);

View File

@@ -38,6 +38,9 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options);
}
/**
* @return void
*/
public function setForeground(string $color = null)
{
if (1 > \func_num_args()) {
@@ -46,6 +49,9 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options);
}
/**
* @return void
*/
public function setBackground(string $color = null)
{
if (1 > \func_num_args()) {
@@ -59,12 +65,18 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->href = $url;
}
/**
* @return void
*/
public function setOption(string $option)
{
$this->options[] = $option;
$this->color = new Color($this->foreground, $this->background, $this->options);
}
/**
* @return void
*/
public function unsetOption(string $option)
{
$pos = array_search($option, $this->options);
@@ -75,6 +87,9 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
$this->color = new Color($this->foreground, $this->background, $this->options);
}
/**
* @return void
*/
public function setOptions(array $options)
{
$this->color = new Color($this->foreground, $this->background, $this->options = $options);

View File

@@ -20,26 +20,36 @@ interface OutputFormatterStyleInterface
{
/**
* Sets style foreground color.
*
* @return void
*/
public function setForeground(?string $color);
/**
* Sets style background color.
*
* @return void
*/
public function setBackground(?string $color);
/**
* Sets some specific style option.
*
* @return void
*/
public function setOption(string $option);
/**
* Unsets some specific style option.
*
* @return void
*/
public function unsetOption(string $option);
/**
* Sets multiple style options at once.
*
* @return void
*/
public function setOptions(array $options);

View File

@@ -34,6 +34,8 @@ class OutputFormatterStyleStack implements ResetInterface
/**
* Resets stack (ie. empty internal arrays).
*
* @return void
*/
public function reset()
{
@@ -42,6 +44,8 @@ class OutputFormatterStyleStack implements ResetInterface
/**
* Pushes a style in the stack.
*
* @return void
*/
public function push(OutputFormatterStyleInterface $style)
{

View File

@@ -14,6 +14,7 @@ namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException;
@@ -38,6 +39,7 @@ class DescriptorHelper extends Helper
->register('xml', new XmlDescriptor())
->register('json', new JsonDescriptor())
->register('md', new MarkdownDescriptor())
->register('rst', new ReStructuredTextDescriptor())
;
}
@@ -48,6 +50,8 @@ class DescriptorHelper extends Helper
* * format: string, the output format name
* * raw_text: boolean, sets output type as raw
*
* @return void
*
* @throws InvalidArgumentException when the given format is not supported
*/
public function describe(OutputInterface $output, ?object $object, array $options = [])

View File

@@ -40,14 +40,12 @@ final class Dumper
return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true));
};
} else {
$this->handler = function ($var): string {
return match (true) {
null === $var => 'null',
true === $var => 'true',
false === $var => 'false',
\is_string($var) => '"'.$var.'"',
default => rtrim(print_r($var, true)),
};
$this->handler = fn ($var): string => match (true) {
null === $var => 'null',
true === $var => 'true',
false === $var => 'false',
\is_string($var) => '"'.$var.'"',
default => rtrim(print_r($var, true)),
};
}
}

View File

@@ -21,8 +21,11 @@ use Symfony\Component\String\UnicodeString;
*/
abstract class Helper implements HelperInterface
{
protected $helperSet = null;
protected $helperSet;
/**
* @return void
*/
public function setHelperSet(HelperSet $helperSet = null)
{
if (1 > \func_num_args()) {
@@ -88,6 +91,9 @@ abstract class Helper implements HelperInterface
return mb_substr($string, $from, $length, $encoding);
}
/**
* @return string
*/
public static function formatTime(int|float $secs)
{
static $timeFormats = [
@@ -117,6 +123,9 @@ abstract class Helper implements HelperInterface
}
}
/**
* @return string
*/
public static function formatMemory(int $memory)
{
if ($memory >= 1024 * 1024 * 1024) {
@@ -134,6 +143,9 @@ abstract class Helper implements HelperInterface
return sprintf('%d B', $memory);
}
/**
* @return string
*/
public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string)
{
$isDecorated = $formatter->isDecorated();

View File

@@ -20,6 +20,8 @@ interface HelperInterface
{
/**
* Sets the helper set associated with this helper.
*
* @return void
*/
public function setHelperSet(?HelperSet $helperSet);

View File

@@ -35,6 +35,9 @@ class HelperSet implements \IteratorAggregate
}
}
/**
* @return void
*/
public function set(HelperInterface $helper, string $alias = null)
{
$this->helpers[$helper->getName()] = $helper;

View File

@@ -23,6 +23,9 @@ abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
protected $input;
/**
* @return void
*/
public function setInput(InputInterface $input)
{
$this->input = $input;

View File

@@ -59,6 +59,7 @@ final class ProgressBar
private Terminal $terminal;
private ?string $previousMessage = null;
private Cursor $cursor;
private array $placeholders = [];
private static array $formatters;
private static array $formats;
@@ -94,12 +95,12 @@ final class ProgressBar
}
/**
* Sets a placeholder formatter for a given name.
* Sets a placeholder formatter for a given name, globally for all instances of ProgressBar.
*
* This method also allow you to override an existing placeholder.
*
* @param string $name The placeholder name (including the delimiter char like %)
* @param callable $callable A PHP callable
* @param string $name The placeholder name (including the delimiter char like %)
* @param callable(ProgressBar):string $callable A PHP callable
*/
public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
{
@@ -120,6 +121,26 @@ final class ProgressBar
return self::$formatters[$name] ?? null;
}
/**
* Sets a placeholder formatter for a given name, for this instance only.
*
* @param callable(ProgressBar):string $callable A PHP callable
*/
public function setPlaceholderFormatter(string $name, callable $callable): void
{
$this->placeholders[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name.
*
* @param string $name The placeholder name (including the delimiter char like %)
*/
public function getPlaceholderFormatter(string $name): ?callable
{
return $this->placeholders[$name] ?? $this::getPlaceholderFormatterDefinition($name);
}
/**
* Sets a format for a given name.
*
@@ -157,12 +178,12 @@ final class ProgressBar
* @param string $message The text to associate with the placeholder
* @param string $name The name of the placeholder
*/
public function setMessage(string $message, string $name = 'message')
public function setMessage(string $message, string $name = 'message'): void
{
$this->messages[$name] = $message;
}
public function getMessage(string $name = 'message')
public function getMessage(string $name = 'message'): string
{
return $this->messages[$name];
}
@@ -215,7 +236,7 @@ final class ProgressBar
return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step));
}
public function setBarWidth(int $size)
public function setBarWidth(int $size): void
{
$this->barWidth = max(1, $size);
}
@@ -225,7 +246,7 @@ final class ProgressBar
return $this->barWidth;
}
public function setBarCharacter(string $char)
public function setBarCharacter(string $char): void
{
$this->barChar = $char;
}
@@ -235,7 +256,7 @@ final class ProgressBar
return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar);
}
public function setEmptyBarCharacter(string $char)
public function setEmptyBarCharacter(string $char): void
{
$this->emptyBarChar = $char;
}
@@ -245,7 +266,7 @@ final class ProgressBar
return $this->emptyBarChar;
}
public function setProgressCharacter(string $char)
public function setProgressCharacter(string $char): void
{
$this->progressChar = $char;
}
@@ -255,7 +276,7 @@ final class ProgressBar
return $this->progressChar;
}
public function setFormat(string $format)
public function setFormat(string $format): void
{
$this->format = null;
$this->internalFormat = $format;
@@ -266,7 +287,7 @@ final class ProgressBar
*
* @param int|null $freq The frequency in steps
*/
public function setRedrawFrequency(?int $freq)
public function setRedrawFrequency(?int $freq): void
{
$this->redrawFreq = null !== $freq ? max(1, $freq) : null;
}
@@ -325,7 +346,7 @@ final class ProgressBar
*
* @param int $step Number of steps to advance
*/
public function advance(int $step = 1)
public function advance(int $step = 1): void
{
$this->setProgress($this->step + $step);
}
@@ -333,12 +354,12 @@ final class ProgressBar
/**
* Sets whether to overwrite the progressbar, false for new line.
*/
public function setOverwrite(bool $overwrite)
public function setOverwrite(bool $overwrite): void
{
$this->overwrite = $overwrite;
}
public function setProgress(int $step)
public function setProgress(int $step): void
{
if ($this->max && $step > $this->max) {
$this->max = $step;
@@ -371,7 +392,7 @@ final class ProgressBar
}
}
public function setMaxSteps(int $max)
public function setMaxSteps(int $max): void
{
$this->format = null;
$this->max = max(0, $max);
@@ -431,7 +452,7 @@ final class ProgressBar
$this->overwrite('');
}
private function setRealFormat(string $format)
private function setRealFormat(string $format): void
{
// try to use the _nomax variant if available
if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
@@ -513,9 +534,7 @@ final class ProgressBar
return $display;
},
'elapsed' => function (self $bar) {
return Helper::formatTime(time() - $bar->getStartTime());
},
'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime()),
'remaining' => function (self $bar) {
if (!$bar->getMaxSteps()) {
throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
@@ -530,18 +549,10 @@ final class ProgressBar
return Helper::formatTime($bar->getEstimated());
},
'memory' => function (self $bar) {
return Helper::formatMemory(memory_get_usage(true));
},
'current' => function (self $bar) {
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT);
},
'max' => function (self $bar) {
return $bar->getMaxSteps();
},
'percent' => function (self $bar) {
return floor($bar->getProgressPercent() * 100);
},
'memory' => fn (self $bar) => Helper::formatMemory(memory_get_usage(true)),
'current' => fn (self $bar) => str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT),
'max' => fn (self $bar) => $bar->getMaxSteps(),
'percent' => fn (self $bar) => floor($bar->getProgressPercent() * 100),
];
}
@@ -568,7 +579,7 @@ final class ProgressBar
$regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
$callback = function ($matches) {
if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
if ($formatter = $this->getPlaceholderFormatter($matches[1])) {
$text = $formatter($this, $this->output);
} elseif (isset($this->messages[$matches[1]])) {
$text = $this->messages[$matches[1]];
@@ -585,9 +596,7 @@ final class ProgressBar
$line = preg_replace_callback($regex, $callback, $this->format);
// gets string length for each sub line with multiline format
$linesLength = array_map(function ($subLine) {
return Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r")));
}, explode("\n", $line));
$linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))), explode("\n", $line));
$linesWidth = max($linesLength);

View File

@@ -70,6 +70,8 @@ class ProgressIndicator
/**
* Sets the current indicator message.
*
* @return void
*/
public function setMessage(?string $message)
{
@@ -80,6 +82,8 @@ class ProgressIndicator
/**
* Starts the indicator output.
*
* @return void
*/
public function start(string $message)
{
@@ -98,6 +102,8 @@ class ProgressIndicator
/**
* Advances the indicator.
*
* @return void
*/
public function advance()
{
@@ -123,6 +129,8 @@ class ProgressIndicator
/**
* Finish the indicator with message.
*
* @return void
*/
public function finish(string $message)
{
@@ -148,6 +156,8 @@ class ProgressIndicator
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*
* @return void
*/
public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
{
@@ -166,7 +176,7 @@ class ProgressIndicator
return self::$formatters[$name] ?? null;
}
private function display()
private function display(): void
{
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
@@ -195,7 +205,7 @@ class ProgressIndicator
/**
* Overwrites a previous message to the output.
*/
private function overwrite(string $message)
private function overwrite(string $message): void
{
if ($this->output->isDecorated()) {
$this->output->write("\x0D\x1B[2K");
@@ -216,18 +226,10 @@ class ProgressIndicator
private static function initPlaceholderFormatters(): array
{
return [
'indicator' => function (self $indicator) {
return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
},
'message' => function (self $indicator) {
return $indicator->message;
},
'elapsed' => function (self $indicator) {
return Helper::formatTime(time() - $indicator->startTime);
},
'memory' => function () {
return Helper::formatMemory(memory_get_usage(true));
},
'indicator' => fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)],
'message' => fn (self $indicator) => $indicator->message,
'elapsed' => fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime),
'memory' => fn () => Helper::formatMemory(memory_get_usage(true)),
];
}
}

View File

@@ -68,9 +68,7 @@ class QuestionHelper extends Helper
return $this->doAsk($output, $question);
}
$interviewer = function () use ($output, $question) {
return $this->doAsk($output, $question);
};
$interviewer = fn () => $this->doAsk($output, $question);
return $this->validateAttempts($interviewer, $output, $question);
} catch (MissingInputException $exception) {
@@ -91,6 +89,8 @@ class QuestionHelper extends Helper
/**
* Prevents usage of stty.
*
* @return void
*/
public static function disableStty()
{
@@ -170,7 +170,7 @@ class QuestionHelper extends Helper
}
if ($validator = $question->getValidator()) {
return \call_user_func($question->getValidator(), $default);
return \call_user_func($validator, $default);
} elseif ($question instanceof ChoiceQuestion) {
$choices = $question->getChoices();
@@ -190,6 +190,8 @@ class QuestionHelper extends Helper
/**
* Outputs the question prompt.
*
* @return void
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
@@ -226,6 +228,8 @@ class QuestionHelper extends Helper
/**
* Outputs an error message.
*
* @return void
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
@@ -325,9 +329,7 @@ class QuestionHelper extends Helper
$matches = array_filter(
$autocomplete($ret),
function ($match) use ($ret) {
return '' === $ret || str_starts_with($match, $ret);
}
fn ($match) => '' === $ret || str_starts_with($match, $ret)
);
$numMatches = \count($matches);
$ofs = -1;

View File

@@ -25,6 +25,9 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*/
class SymfonyQuestionHelper extends QuestionHelper
{
/**
* @return void
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
@@ -80,6 +83,9 @@ class SymfonyQuestionHelper extends QuestionHelper
$output->write($prompt);
}
/**
* @return void
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if ($output instanceof SymfonyStyle) {

View File

@@ -66,6 +66,8 @@ class Table
/**
* Sets a style definition.
*
* @return void
*/
public static function setStyleDefinition(string $name, TableStyle $style)
{
@@ -310,6 +312,8 @@ class Table
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
* +---------------+-----------------------+------------------+
*
* @return void
*/
public function render()
{
@@ -450,7 +454,7 @@ class Table
*
* +-----+-----------+-------+
*/
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null): void
{
if (!$count = $this->numberOfColumns) {
return;
@@ -515,7 +519,7 @@ class Table
*
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*/
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null)
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null): void
{
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
$columns = $this->getRowColumns($row);
@@ -588,7 +592,7 @@ class Table
/**
* Calculate number of columns for this table.
*/
private function calculateNumberOfColumns(array $rows)
private function calculateNumberOfColumns(array $rows): void
{
$columns = [0];
foreach ($rows as $row) {
@@ -727,7 +731,7 @@ class Table
/**
* fill cells for a row that contains colspan > 1.
*/
private function fillCells(iterable $row)
private function fillCells(iterable $row): iterable
{
$newRow = [];
@@ -789,7 +793,7 @@ class Table
/**
* Calculates columns widths.
*/
private function calculateColumnsWidth(iterable $groups)
private function calculateColumnsWidth(iterable $groups): void
{
for ($column = 0; $column < $this->numberOfColumns; ++$column) {
$lengths = [];
@@ -843,7 +847,7 @@ class Table
/**
* Called after rendering to cleanup cache data.
*/
private function cleanup()
private function cleanup(): void
{
$this->effectiveColumnWidths = [];
unset($this->numberOfColumns);

View File

@@ -67,9 +67,7 @@ class TableCellStyle
{
return array_filter(
$this->getOptions(),
function ($key) {
return \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]);
},
fn ($key) => \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]),
\ARRAY_FILTER_USE_KEY
);
}

View File

@@ -55,11 +55,17 @@ class ArgvInput extends Input
parent::__construct($definition);
}
/**
* @return void
*/
protected function setTokens(array $tokens)
{
$this->tokens = $tokens;
}
/**
* @return void
*/
protected function parse()
{
$parseOptions = true;
@@ -89,7 +95,7 @@ class ArgvInput extends Input
/**
* Parses a short option.
*/
private function parseShortOption(string $token)
private function parseShortOption(string $token): void
{
$name = substr($token, 1);
@@ -110,7 +116,7 @@ class ArgvInput extends Input
*
* @throws RuntimeException When option given doesn't exist
*/
private function parseShortOptionSet(string $name)
private function parseShortOptionSet(string $name): void
{
$len = \strlen($name);
for ($i = 0; $i < $len; ++$i) {
@@ -133,7 +139,7 @@ class ArgvInput extends Input
/**
* Parses a long option.
*/
private function parseLongOption(string $token)
private function parseLongOption(string $token): void
{
$name = substr($token, 2);
@@ -152,7 +158,7 @@ class ArgvInput extends Input
*
* @throws RuntimeException When too many arguments are given
*/
private function parseArgument(string $token)
private function parseArgument(string $token): void
{
$c = \count($this->arguments);
@@ -196,7 +202,7 @@ class ArgvInput extends Input
*
* @throws RuntimeException When option given doesn't exist
*/
private function addShortOption(string $shortcut, mixed $value)
private function addShortOption(string $shortcut, mixed $value): void
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -210,7 +216,7 @@ class ArgvInput extends Input
*
* @throws RuntimeException When option given doesn't exist
*/
private function addLongOption(string $name, mixed $value)
private function addLongOption(string $name, mixed $value): void
{
if (!$this->definition->hasOption($name)) {
if (!$this->definition->hasNegation($name)) {

View File

@@ -113,6 +113,9 @@ class ArrayInput extends Input
return implode(' ', $params);
}
/**
* @return void
*/
protected function parse()
{
foreach ($this->parameters as $key => $value) {
@@ -134,7 +137,7 @@ class ArrayInput extends Input
*
* @throws InvalidOptionException When option given doesn't exist
*/
private function addShortOption(string $shortcut, mixed $value)
private function addShortOption(string $shortcut, mixed $value): void
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -149,7 +152,7 @@ class ArrayInput extends Input
* @throws InvalidOptionException When option given doesn't exist
* @throws InvalidOptionException When a required value is missing
*/
private function addLongOption(string $name, mixed $value)
private function addLongOption(string $name, mixed $value): void
{
if (!$this->definition->hasOption($name)) {
if (!$this->definition->hasNegation($name)) {
@@ -182,7 +185,7 @@ class ArrayInput extends Input
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
private function addArgument(string|int $name, mixed $value)
private function addArgument(string|int $name, mixed $value): void
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));

View File

@@ -43,6 +43,9 @@ abstract class Input implements InputInterface, StreamableInputInterface
}
}
/**
* @return void
*/
public function bind(InputDefinition $definition)
{
$this->arguments = [];
@@ -54,17 +57,20 @@ abstract class Input implements InputInterface, StreamableInputInterface
/**
* Processes command line arguments.
*
* @return void
*/
abstract protected function parse();
/**
* @return void
*/
public function validate()
{
$definition = $this->definition;
$givenArguments = $this->arguments;
$missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) {
return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
});
$missingArguments = array_filter(array_keys($definition->getArguments()), fn ($argument) => !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired());
if (\count($missingArguments) > 0) {
throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
@@ -76,6 +82,9 @@ abstract class Input implements InputInterface, StreamableInputInterface
return $this->interactive;
}
/**
* @return void
*/
public function setInteractive(bool $interactive)
{
$this->interactive = $interactive;
@@ -95,6 +104,9 @@ abstract class Input implements InputInterface, StreamableInputInterface
return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault();
}
/**
* @return void
*/
public function setArgument(string $name, mixed $value)
{
if (!$this->definition->hasArgument($name)) {
@@ -131,6 +143,9 @@ abstract class Input implements InputInterface, StreamableInputInterface
return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
}
/**
* @return void
*/
public function setOption(string $name, mixed $value)
{
if ($this->definition->hasNegation($name)) {
@@ -157,11 +172,19 @@ abstract class Input implements InputInterface, StreamableInputInterface
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
}
/**
* @param resource $stream
*
* @return void
*/
public function setStream($stream)
{
$this->stream = $stream;
}
/**
* @return resource
*/
public function getStream()
{
return $this->stream;

View File

@@ -91,6 +91,8 @@ class InputArgument
/**
* Sets the default value.
*
* @return void
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault(string|bool|int|float|array $default = null)

View File

@@ -21,6 +21,8 @@ interface InputAwareInterface
{
/**
* Sets the Console Input.
*
* @return void
*/
public function setInput(InputInterface $input);
}

View File

@@ -46,6 +46,8 @@ class InputDefinition
/**
* Sets the definition of the input.
*
* @return void
*/
public function setDefinition(array $definition)
{
@@ -67,6 +69,8 @@ class InputDefinition
* Sets the InputArgument objects.
*
* @param InputArgument[] $arguments An array of InputArgument objects
*
* @return void
*/
public function setArguments(array $arguments = [])
{
@@ -81,6 +85,8 @@ class InputDefinition
* Adds an array of InputArgument objects.
*
* @param InputArgument[] $arguments An array of InputArgument objects
*
* @return void
*/
public function addArguments(?array $arguments = [])
{
@@ -92,6 +98,8 @@ class InputDefinition
}
/**
* @return void
*
* @throws LogicException When incorrect argument is given
*/
public function addArgument(InputArgument $argument)
@@ -190,6 +198,8 @@ class InputDefinition
* Sets the InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*
* @return void
*/
public function setOptions(array $options = [])
{
@@ -203,6 +213,8 @@ class InputDefinition
* Adds an array of InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*
* @return void
*/
public function addOptions(array $options = [])
{
@@ -212,6 +224,8 @@ class InputDefinition
}
/**
* @return void
*
* @throws LogicException When option given already exist
*/
public function addOption(InputOption $option)

View File

@@ -61,6 +61,8 @@ interface InputInterface
/**
* Binds the current Input instance with the given arguments and options.
*
* @return void
*
* @throws RuntimeException
*/
public function bind(InputDefinition $definition);
@@ -68,6 +70,8 @@ interface InputInterface
/**
* Validates the input.
*
* @return void
*
* @throws RuntimeException When not enough arguments are given
*/
public function validate();
@@ -91,6 +95,8 @@ interface InputInterface
/**
* Sets an argument value by name.
*
* @return void
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function setArgument(string $name, mixed $value);
@@ -119,6 +125,8 @@ interface InputInterface
/**
* Sets an option value by name.
*
* @return void
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function setOption(string $name, mixed $value);
@@ -135,6 +143,8 @@ interface InputInterface
/**
* Sets the input interactivity.
*
* @return void
*/
public function setInteractive(bool $interactive);
}

View File

@@ -178,6 +178,9 @@ class InputOption
return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode);
}
/**
* @return void
*/
public function setDefault(string|bool|int|float|array $default = null)
{
if (1 > \func_num_args()) {

View File

@@ -25,6 +25,8 @@ interface StreamableInputInterface extends InputInterface
* This is mainly useful for testing purpose.
*
* @param resource $stream The input stream
*
* @return void
*/
public function setStream($stream);

View File

@@ -26,7 +26,7 @@ enum AnsiColorMode
case Ansi4;
/*
* 8-bit Ansi colors (240 differents colors + 16 duplicate color codes, ensuring backward compatibility).
* 8-bit Ansi colors (240 different colors + 16 duplicate color codes, ensuring backward compatibility).
* Output syntax is: "ESC[38;5;${foreGroundColorcode};48;5;${backGroundColorcode}m"
* Should be compatible with most terminals.
*/
@@ -78,25 +78,7 @@ enum AnsiColorMode
private function degradeHexColorToAnsi4(int $r, int $g, int $b): int
{
if (0 === round($this->getSaturation($r, $g, $b) / 50)) {
return 0;
}
return (int) ((round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255));
}
private function getSaturation(int $r, int $g, int $b): int
{
$r = $r / 255;
$g = $g / 255;
$b = $b / 255;
$v = max($r, $g, $b);
if (0 === $diff = $v - min($r, $g, $b)) {
return 0;
}
return (int) ((int) $diff * 100 / $v);
return round($b / 255) << 2 | (round($g / 255) << 1) | round($r / 255);
}
/**

View File

@@ -29,6 +29,9 @@ class BufferedOutput extends Output
return $content;
}
/**
* @return void
*/
protected function doWrite(string $message, bool $newline)
{
$this->buffer .= $message;

View File

@@ -64,18 +64,27 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
}
/**
* @return void
*/
public function setDecorated(bool $decorated)
{
parent::setDecorated($decorated);
$this->stderr->setDecorated($decorated);
}
/**
* @return void
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
parent::setFormatter($formatter);
$this->stderr->setFormatter($formatter);
}
/**
* @return void
*/
public function setVerbosity(int $level)
{
parent::setVerbosity($level);
@@ -87,6 +96,9 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
return $this->stderr;
}
/**
* @return void
*/
public function setErrorOutput(OutputInterface $error)
{
$this->stderr = $error;

View File

@@ -24,6 +24,9 @@ interface ConsoleOutputInterface extends OutputInterface
*/
public function getErrorOutput(): OutputInterface;
/**
* @return void
*/
public function setErrorOutput(OutputInterface $error);
public function section(): ConsoleSectionOutput;

View File

@@ -60,6 +60,8 @@ class ConsoleSectionOutput extends StreamOutput
* Clears previous output for this section.
*
* @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
*
* @return void
*/
public function clear(int $lines = null)
{
@@ -81,6 +83,8 @@ class ConsoleSectionOutput extends StreamOutput
/**
* Overwrites the previous output with a new message.
*
* @return void
*/
public function overwrite(string|iterable $message)
{
@@ -153,12 +157,15 @@ class ConsoleSectionOutput extends StreamOutput
/**
* @internal
*/
public function addNewLineOfInputSubmit()
public function addNewLineOfInputSubmit(): void
{
$this->content[] = \PHP_EOL;
++$this->lines;
}
/**
* @return void
*/
protected function doWrite(string $message, bool $newline)
{
if (!$this->isDecorated()) {

View File

@@ -26,6 +26,9 @@ class NullOutput implements OutputInterface
{
private NullOutputFormatter $formatter;
/**
* @return void
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
// do nothing
@@ -37,6 +40,9 @@ class NullOutput implements OutputInterface
return $this->formatter ??= new NullOutputFormatter();
}
/**
* @return void
*/
public function setDecorated(bool $decorated)
{
// do nothing
@@ -47,6 +53,9 @@ class NullOutput implements OutputInterface
return false;
}
/**
* @return void
*/
public function setVerbosity(int $level)
{
// do nothing
@@ -77,11 +86,17 @@ class NullOutput implements OutputInterface
return false;
}
/**
* @return void
*/
public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
{
// do nothing
}
/**
* @return void
*/
public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
{
// do nothing

View File

@@ -44,6 +44,9 @@ abstract class Output implements OutputInterface
$this->formatter->setDecorated($decorated);
}
/**
* @return void
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->formatter = $formatter;
@@ -54,6 +57,9 @@ abstract class Output implements OutputInterface
return $this->formatter;
}
/**
* @return void
*/
public function setDecorated(bool $decorated)
{
$this->formatter->setDecorated($decorated);
@@ -64,6 +70,9 @@ abstract class Output implements OutputInterface
return $this->formatter->isDecorated();
}
/**
* @return void
*/
public function setVerbosity(int $level)
{
$this->verbosity = $level;
@@ -94,11 +103,17 @@ abstract class Output implements OutputInterface
return self::VERBOSITY_DEBUG <= $this->verbosity;
}
/**
* @return void
*/
public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $options);
}
/**
* @return void
*/
public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
@@ -133,6 +148,8 @@ abstract class Output implements OutputInterface
/**
* Writes a message to the output.
*
* @return void
*/
abstract protected function doWrite(string $message, bool $newline);
}

View File

@@ -36,6 +36,8 @@ interface OutputInterface
* @param bool $newline Whether to add a newline
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants),
* 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*
* @return void
*/
public function write(string|iterable $messages, bool $newline = false, int $options = 0);
@@ -44,11 +46,15 @@ interface OutputInterface
*
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants),
* 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*
* @return void
*/
public function writeln(string|iterable $messages, int $options = 0);
/**
* Sets the verbosity of the output.
*
* @return void
*/
public function setVerbosity(int $level);
@@ -79,6 +85,8 @@ interface OutputInterface
/**
* Sets the decorated flag.
*
* @return void
*/
public function setDecorated(bool $decorated);
@@ -87,6 +95,9 @@ interface OutputInterface
*/
public function isDecorated(): bool;
/**
* @return void
*/
public function setFormatter(OutputFormatterInterface $formatter);
/**

View File

@@ -62,6 +62,9 @@ class StreamOutput extends Output
return $this->stream;
}
/**
* @return void
*/
protected function doWrite(string $message, bool $newline)
{
if ($newline) {

View File

@@ -45,6 +45,9 @@ class TrimmedBufferOutput extends Output
return $content;
}
/**
* @return void
*/
protected function doWrite(string $message, bool $newline)
{
$this->buffer .= $message;

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