1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-11-30 06:59:01 +01:00
MadelineProto/tools/AnnotationsBuilder.php

692 lines
31 KiB
PHP
Raw Normal View History

2022-12-30 21:54:44 +01:00
<?php
declare(strict_types=1);
Merge alpha into master (async, huge bugfixes and more) (#546) * Implement async and lots of bugfixes * Implement more async * Implement async, implement bugfixes for the connection module, for the datacenter module, huge bugfixes, huge perfomance improvements, media DCs for https, advanced selecting, custom var_dump, totally rewritten IOLoop and response mechanism, promises, improvements to the TL parser, custom mb_substr * Apply fixes from StyleCI * Bugfixes * Apply fixes from StyleCI * Bugfixes, implement combined promises * Apply fixes from StyleCI * Support passing method arguments as callable * Starting to write async upload logic * Apply fixes from StyleCI * Start implementing async file upload * Apply fixes from StyleCI * bugfix * Apply fixes from StyleCI * Start rewriting connection module * Add PHP file docblocks for all classes * Start working on new async stream API * Finish writing stream API * More stream API fixes * Apply fixes from StyleCI * Rewrite DataCenter and Connection modules * Clean up stream API documentation * Fixes * Apply fixes from StyleCI * Add referenced parameter to get length of buffer to read in getReadBuffer API * Moved all MessageHandler code in the Connection module, added a PHP version warning in the phar * Start fixing reads * Fix all protocol stream wrappers * Apply fixes from StyleCI * Implement disconnection, and remove end function * Working async RPC * Implement async file upload * Bugfix * Method recall bugfixes * Bugfixes * Trait bugfixes * Fix FIFO buffer * Bugfixes and speedtests * Async logging * Implement websocket streams * Implement loop API, signal API, clean closing and start changing layer * Small magna, websocket and HTTP fixes * Clean up loop API * Improved stack traces, 2FA and async * Login fixes * Added instructions for manual verification * Small fixes * More app info improvements * More app info improvements * TL and 2FA fixes * Update to layer 89 * More bugfixes * Implement broken media reporting * Remove debug comments * PHP 7.2 backwards compatibility * Bugfixes * Async key generation * Some simplifications * Transport fixes * Cleanup * async API * Performance fixes * Fixes to async API * Bugfixes * Implement one-time async loop * Authorization and logging fixes * Update to layer 91 * 7to5 fix * Null coalesce conversion * Implement socks5 proxy * Implement HTTP proxy * Fixes to HTTP proxy * MTProxy and socks5 fixes * Disable PHP 5 conversion * Proxies have higher priority * Avoid error handling in vendor * Override composer dependencies * Fix travis build * Final composer fixes * Proxy logic fixes * Fix get_updates update handling * Do not use parallel file driver if not supported * Refactor loader and implement HTTP fixes * Suppress errors in loader * HTTP and authorization fixes * HTTP fixes * Improved peer management * Use HTTP protocol on altervista * Small bugfixes * Minor fixes * Docufix * Docufix * Legacy fixes * Fix message queue * Avoid updating if using MTProxy * Improve logs and examples * Trim final newlines while converting parse mode * Reimplement noResponse flag * Async combined event handler and APIFactory fixes * Actually return config * Case-insensitive methods * Bugfix * Apply fixes from StyleCI (#545) * MTProxy fixes * PHP 5 warning * Improved PHP 5 warning * Use <br> along with newlines in web logs * Update docs
2018-12-26 20:51:14 +01:00
/**
* AnnotationsBuilder module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
2023-01-04 12:43:01 +01:00
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
Merge alpha into master (async, huge bugfixes and more) (#546) * Implement async and lots of bugfixes * Implement more async * Implement async, implement bugfixes for the connection module, for the datacenter module, huge bugfixes, huge perfomance improvements, media DCs for https, advanced selecting, custom var_dump, totally rewritten IOLoop and response mechanism, promises, improvements to the TL parser, custom mb_substr * Apply fixes from StyleCI * Bugfixes * Apply fixes from StyleCI * Bugfixes, implement combined promises * Apply fixes from StyleCI * Support passing method arguments as callable * Starting to write async upload logic * Apply fixes from StyleCI * Start implementing async file upload * Apply fixes from StyleCI * bugfix * Apply fixes from StyleCI * Start rewriting connection module * Add PHP file docblocks for all classes * Start working on new async stream API * Finish writing stream API * More stream API fixes * Apply fixes from StyleCI * Rewrite DataCenter and Connection modules * Clean up stream API documentation * Fixes * Apply fixes from StyleCI * Add referenced parameter to get length of buffer to read in getReadBuffer API * Moved all MessageHandler code in the Connection module, added a PHP version warning in the phar * Start fixing reads * Fix all protocol stream wrappers * Apply fixes from StyleCI * Implement disconnection, and remove end function * Working async RPC * Implement async file upload * Bugfix * Method recall bugfixes * Bugfixes * Trait bugfixes * Fix FIFO buffer * Bugfixes and speedtests * Async logging * Implement websocket streams * Implement loop API, signal API, clean closing and start changing layer * Small magna, websocket and HTTP fixes * Clean up loop API * Improved stack traces, 2FA and async * Login fixes * Added instructions for manual verification * Small fixes * More app info improvements * More app info improvements * TL and 2FA fixes * Update to layer 89 * More bugfixes * Implement broken media reporting * Remove debug comments * PHP 7.2 backwards compatibility * Bugfixes * Async key generation * Some simplifications * Transport fixes * Cleanup * async API * Performance fixes * Fixes to async API * Bugfixes * Implement one-time async loop * Authorization and logging fixes * Update to layer 91 * 7to5 fix * Null coalesce conversion * Implement socks5 proxy * Implement HTTP proxy * Fixes to HTTP proxy * MTProxy and socks5 fixes * Disable PHP 5 conversion * Proxies have higher priority * Avoid error handling in vendor * Override composer dependencies * Fix travis build * Final composer fixes * Proxy logic fixes * Fix get_updates update handling * Do not use parallel file driver if not supported * Refactor loader and implement HTTP fixes * Suppress errors in loader * HTTP and authorization fixes * HTTP fixes * Improved peer management * Use HTTP protocol on altervista * Small bugfixes * Minor fixes * Docufix * Docufix * Legacy fixes * Fix message queue * Avoid updating if using MTProxy * Improve logs and examples * Trim final newlines while converting parse mode * Reimplement noResponse flag * Async combined event handler and APIFactory fixes * Actually return config * Case-insensitive methods * Bugfix * Apply fixes from StyleCI (#545) * MTProxy fixes * PHP 5 warning * Improved PHP 5 warning * Use <br> along with newlines in web logs * Update docs
2018-12-26 20:51:14 +01:00
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
2019-10-31 15:07:35 +01:00
* @link https://docs.madelineproto.xyz MadelineProto documentation
Merge alpha into master (async, huge bugfixes and more) (#546) * Implement async and lots of bugfixes * Implement more async * Implement async, implement bugfixes for the connection module, for the datacenter module, huge bugfixes, huge perfomance improvements, media DCs for https, advanced selecting, custom var_dump, totally rewritten IOLoop and response mechanism, promises, improvements to the TL parser, custom mb_substr * Apply fixes from StyleCI * Bugfixes * Apply fixes from StyleCI * Bugfixes, implement combined promises * Apply fixes from StyleCI * Support passing method arguments as callable * Starting to write async upload logic * Apply fixes from StyleCI * Start implementing async file upload * Apply fixes from StyleCI * bugfix * Apply fixes from StyleCI * Start rewriting connection module * Add PHP file docblocks for all classes * Start working on new async stream API * Finish writing stream API * More stream API fixes * Apply fixes from StyleCI * Rewrite DataCenter and Connection modules * Clean up stream API documentation * Fixes * Apply fixes from StyleCI * Add referenced parameter to get length of buffer to read in getReadBuffer API * Moved all MessageHandler code in the Connection module, added a PHP version warning in the phar * Start fixing reads * Fix all protocol stream wrappers * Apply fixes from StyleCI * Implement disconnection, and remove end function * Working async RPC * Implement async file upload * Bugfix * Method recall bugfixes * Bugfixes * Trait bugfixes * Fix FIFO buffer * Bugfixes and speedtests * Async logging * Implement websocket streams * Implement loop API, signal API, clean closing and start changing layer * Small magna, websocket and HTTP fixes * Clean up loop API * Improved stack traces, 2FA and async * Login fixes * Added instructions for manual verification * Small fixes * More app info improvements * More app info improvements * TL and 2FA fixes * Update to layer 89 * More bugfixes * Implement broken media reporting * Remove debug comments * PHP 7.2 backwards compatibility * Bugfixes * Async key generation * Some simplifications * Transport fixes * Cleanup * async API * Performance fixes * Fixes to async API * Bugfixes * Implement one-time async loop * Authorization and logging fixes * Update to layer 91 * 7to5 fix * Null coalesce conversion * Implement socks5 proxy * Implement HTTP proxy * Fixes to HTTP proxy * MTProxy and socks5 fixes * Disable PHP 5 conversion * Proxies have higher priority * Avoid error handling in vendor * Override composer dependencies * Fix travis build * Final composer fixes * Proxy logic fixes * Fix get_updates update handling * Do not use parallel file driver if not supported * Refactor loader and implement HTTP fixes * Suppress errors in loader * HTTP and authorization fixes * HTTP fixes * Improved peer management * Use HTTP protocol on altervista * Small bugfixes * Minor fixes * Docufix * Docufix * Legacy fixes * Fix message queue * Avoid updating if using MTProxy * Improve logs and examples * Trim final newlines while converting parse mode * Reimplement noResponse flag * Async combined event handler and APIFactory fixes * Actually return config * Case-insensitive methods * Bugfix * Apply fixes from StyleCI (#545) * MTProxy fixes * PHP 5 warning * Improved PHP 5 warning * Use <br> along with newlines in web logs * Update docs
2018-12-26 20:51:14 +01:00
*/
2018-02-24 17:54:39 +01:00
namespace danog\MadelineProto;
2023-01-27 19:10:52 +01:00
use AssertionError;
2023-08-05 19:13:26 +02:00
use danog\ClassFinder\ClassFinder;
2019-10-31 20:48:06 +01:00
use danog\MadelineProto\TL\TL;
2022-12-30 19:21:36 +01:00
use ReflectionClass;
use ReflectionMethod;
2022-07-16 18:10:33 +02:00
use ReflectionNamedType;
2023-01-08 20:33:54 +01:00
use ReflectionType;
2022-07-16 18:10:33 +02:00
use ReflectionUnionType;
2018-02-24 17:54:39 +01:00
2023-01-27 18:12:33 +01:00
/**
* @internal
*/
2023-01-15 12:05:38 +01:00
final class AnnotationsBuilder
{
2020-10-03 12:36:08 +02:00
/**
2020-10-03 15:04:35 +02:00
* Reflection classes.
2020-10-03 12:36:08 +02:00
*/
private array $reflectionClasses = [];
/**
2020-10-03 15:04:35 +02:00
* Namespace.
2020-10-03 12:36:08 +02:00
*/
private string $namespace;
/**
2020-10-03 15:04:35 +02:00
* TL instance.
2020-10-03 12:36:08 +02:00
*/
private TL $TL;
2023-01-27 14:20:47 +01:00
private array $blacklist;
private array $blacklistHard;
2023-01-27 18:25:02 +01:00
private array $special;
2023-01-27 14:20:47 +01:00
public function __construct(Logger $logger, array $settings, array $reflectionClasses, string $namespace)
{
2019-12-14 16:47:04 +01:00
$this->reflectionClasses = $reflectionClasses;
$this->namespace = $namespace;
2020-10-03 15:36:03 +02:00
/** @psalm-suppress InvalidArgument */
2023-01-08 20:33:54 +01:00
$this->TL = new TL();
2023-08-13 16:21:59 +02:00
$this->TL->init($settings['TL']);
2023-10-01 20:05:04 +02:00
$this->blacklist = json_decode(file_get_contents(__DIR__.'/../docs/template/disallow.json'), true);
$this->blacklist['updates.getDifference'] = 'You cannot use this method directly, please use the [event handler](https://docs.madelineproto.xyz/docs/UPDATES.html), instead.';
$this->blacklist['updates.getChannelDifference'] = 'You cannot use this method directly, please use the [event handler](https://docs.madelineproto.xyz/docs/UPDATES.html), instead.';
$this->blacklist['updates.getState'] = 'You cannot use this method directly, please use the [event handler](https://docs.madelineproto.xyz/docs/UPDATES.html), instead.';
2023-01-27 14:20:47 +01:00
$this->blacklistHard = $this->blacklist;
unset($this->blacklistHard['messages.getHistory'], $this->blacklistHard['channels.getMessages'], $this->blacklistHard['messages.getMessages'], $this->blacklistHard['updates.getDifference'], $this->blacklistHard['updates.getChannelDifference'], $this->blacklistHard['updates.getState']);
2023-12-24 15:15:09 +01:00
file_put_contents(__DIR__.'/../src/Namespace/Blacklist.php', '<?php
2023-01-27 14:36:54 +01:00
namespace danog\MadelineProto\Namespace;
final class Blacklist {
2023-10-01 20:05:04 +02:00
public const BLACKLIST = '.var_export($this->blacklistHard, true).';
2023-01-27 14:36:54 +01:00
}
');
2023-01-27 18:25:02 +01:00
$special = [];
2023-01-27 19:00:11 +01:00
foreach (DocsBuilder::DEFAULT_TEMPLATES as $key => $types) {
$replace = match ($key) {
'User' => 'array|int|string',
'InputFile' => 'mixed',
'PhoneCall' => '\\danog\\MadelineProto\\VoIP|array',
default => 'array'
};
2023-01-27 18:25:02 +01:00
foreach ($types as $type) {
2023-01-27 19:00:11 +01:00
$special[$type] = $replace;
2023-01-27 18:25:02 +01:00
}
}
$this->special = $special;
}
2020-10-01 21:03:25 +02:00
public function mkAnnotations(): void
{
2022-12-30 19:21:36 +01:00
Logger::log('Generating annotations...', Logger::NOTICE);
$this->createInternalClasses();
}
2023-01-27 18:12:33 +01:00
private static function isVector(string $type): array
{
2023-10-01 20:05:04 +02:00
if (str_contains($type, '<')) {
return [true, str_replace(['Vector<', '>'], '', $type)];
2023-01-27 18:12:33 +01:00
}
return [false, $type];
}
2023-12-15 18:45:28 +01:00
private function prepareTLType(string $type, string|int|null $pow): string
{
2023-01-27 18:12:33 +01:00
[$isVector, $type] = self::isVector($type);
if ($isVector) {
2023-12-15 18:45:28 +01:00
return \is_int($pow) ? 'array|null' : 'array';
2023-01-27 18:12:33 +01:00
}
2023-09-06 18:39:48 +02:00
$type = match ($type) {
2023-01-27 14:20:47 +01:00
'string' => 'string',
'bytes' => 'string',
2023-07-08 17:11:21 +02:00
'waveform' => 'array',
2023-01-27 14:20:47 +01:00
'int' => 'int',
'long' => 'int',
2024-08-15 18:22:31 +02:00
'long|string' => 'int|string',
'strlong' => 'int',
2023-01-27 14:20:47 +01:00
'double' => 'float',
'float' => 'float',
'Bool' => 'bool',
2023-01-27 18:12:33 +01:00
'true' => 'bool',
2023-01-27 18:25:02 +01:00
'InputMessage' => 'array|int',
2023-07-21 19:21:34 +02:00
'InputMedia' => '\\danog\\MadelineProto\\EventHandler\\Media|array|string',
2023-04-29 14:26:32 +02:00
'InputCheckPasswordSRP' => 'string|array',
2023-01-27 19:10:52 +01:00
'DataJSON' => 'mixed',
'JSONValue' => 'mixed',
2023-01-27 19:00:11 +01:00
default => $this->special[$type] ?? 'array'
2023-01-27 14:20:47 +01:00
};
2023-12-15 18:45:28 +01:00
if ($type === 'mixed' || !$pow) {
2023-09-06 18:39:48 +02:00
return $type;
}
return $type.'|null';
2023-01-27 14:20:47 +01:00
}
2023-01-27 19:00:11 +01:00
private function prepareTLPsalmType(string $type, bool $input, int $depth = -2, array $stack = []): string
2023-01-27 18:12:33 +01:00
{
[$isVector, $type] = self::isVector($type);
$base = match ($type) {
'string' => 'string',
'bytes' => 'string',
2023-07-08 17:11:21 +02:00
'waveform' => 'non-empty-list<int<0, 31>>',
2023-01-27 18:12:33 +01:00
'int' => 'int',
'long' => 'int',
'int|string' => 'int|string',
'strlong' => 'int',
2023-01-27 18:12:33 +01:00
'double' => 'float',
'float' => 'float',
'Bool' => 'bool',
'true' => 'bool',
2023-01-27 19:00:11 +01:00
'Updates' => 'array',
2023-04-29 14:26:32 +02:00
'InputCheckPasswordSRP' => 'string|array',
2023-01-27 19:10:52 +01:00
'DataJSON' => 'mixed',
'JSONValue' => 'mixed',
2023-01-27 19:00:11 +01:00
default => $this->special[$type] ?? ''
2023-01-27 18:12:33 +01:00
};
2023-01-27 18:25:02 +01:00
if ($type === 'channels.AdminLogResults') {
2023-04-29 14:26:32 +02:00
$depth = 3;
2023-01-27 18:25:02 +01:00
}
2023-04-03 12:07:27 +02:00
if ($type === 'messages.Messages') {
2023-04-29 14:26:32 +02:00
$depth = 3;
2023-04-03 12:07:27 +02:00
}
if ($type === 'messages.Dialogs') {
2023-04-29 14:26:32 +02:00
$depth = 3;
2023-04-03 12:07:27 +02:00
}
if ($type === 'MessageMedia') {
$depth = 2;
}
2023-04-29 14:26:32 +02:00
if ($type === 'DecryptedMessage' || $type === 'messages.BotResults' || $type === 'InputBotInlineResult') {
$depth = 2;
}
if ($type === 'WebPage') {
$depth = 3;
}
if ($type === 'Document' || $type === 'Photo') {
$depth = 3;
}
2023-01-27 18:25:02 +01:00
if ($depth > 3) {
$base = 'array';
}
2023-01-27 18:12:33 +01:00
if (!$base) {
$constructors = [];
foreach ($this->TL->getConstructors()->by_id as $constructor) {
if ($constructor['type'] === $type) {
$params = ["_: '{$constructor['predicate']}'"];
foreach ($this->filterParams($constructor['params'], $constructor['type']) as $name => $param) {
if (isset($stack[$param['type']])) {
continue 2;
}
$stack[$param['type']] = true;
if ($input) {
$optional = isset($param['pow']) ? '?' : '';
} else {
$optional = isset($param['pow']) && \is_int($param['pow']) && $param['type'] !== 'true'
? '?'
: '';
}
2023-01-27 18:25:02 +01:00
$params []= "$name$optional: ".$this->prepareTLPsalmType($param['type'], $input, $depth+1, $stack);
2023-01-27 18:12:33 +01:00
unset($stack[$param['type']]);
}
2023-10-01 20:05:04 +02:00
$params = implode(', ', $params);
2023-01-27 18:12:33 +01:00
$constructors []= 'array{'.$params.'}';
}
}
2023-01-27 19:10:52 +01:00
if ($constructors === []) {
throw new AssertionError("No constructors for $type!");
}
2023-10-01 20:05:04 +02:00
$base = implode('|', $constructors);
2023-01-27 18:12:33 +01:00
}
2023-01-27 18:25:02 +01:00
if ($type === 'InputMessage') {
2023-01-27 19:00:11 +01:00
$base = "int|$base";
2023-01-27 18:25:02 +01:00
}
2023-05-06 20:35:03 +02:00
if ($type === 'InputMedia') {
2023-07-21 19:21:34 +02:00
$base = "\\danog\\MadelineProto\\EventHandler\\Media|string|$base";
2023-05-06 20:35:03 +02:00
}
2023-01-27 18:12:33 +01:00
if ($isVector) {
$base = "list<$base>";
}
return $base;
}
2023-01-27 14:20:47 +01:00
private function prepareTLDefault(string $type): string
{
2023-01-27 18:12:33 +01:00
[$isVector, $type] = self::isVector($type);
if ($isVector) {
return '[]';
}
2023-01-27 14:20:47 +01:00
return match ($type) {
'string' => "''",
'bytes' => "''",
'int' => '0',
'long' => '0',
'strlong' => '0',
2023-01-27 14:20:47 +01:00
'double' => '0.0',
'float' => '0.0',
'Bool' => 'false',
2023-01-27 18:12:33 +01:00
'true' => 'false',
'DataJSON' => 'null',
2023-01-27 19:10:52 +01:00
'JSONValue' => 'null',
2023-09-06 18:39:48 +02:00
default => 'null'
2023-01-27 14:20:47 +01:00
};
}
2023-08-05 19:13:26 +02:00
private function preparePsalmDefault(string $type): string
{
[$isVector, $type] = self::isVector($type);
if ($isVector) {
return 'array<never, never>';
}
return match ($type) {
'string' => "''",
'bytes' => "''",
'int' => '0',
'long' => '0',
'strlong' => '0',
'double' => '0.0',
'float' => '0.0',
'Bool' => 'false',
'true' => 'false',
'DataJSON' => 'null',
'JSONValue' => 'null',
2023-09-06 18:39:48 +02:00
default => 'null'
2023-08-05 19:13:26 +02:00
};
}
2023-01-27 18:12:33 +01:00
private function prepareTLTypeDescription(string $type, string $description): string
2023-01-27 14:20:47 +01:00
{
2023-01-27 18:12:33 +01:00
[$isList, $type] = self::isVector($type);
if ($isList) {
$descriptionEnhanced = "Array of $description";
} else {
$descriptionEnhanced = $description;
}
2023-01-27 14:20:47 +01:00
return match ($type) {
2023-01-27 18:12:33 +01:00
'string' => $description,
'bytes' => $description,
2023-07-08 17:11:21 +02:00
'waveform' => $description,
2023-01-27 18:12:33 +01:00
'int' => $description,
'long' => $description,
'strlong' => $description,
2023-01-27 18:12:33 +01:00
'double' => $description,
'float' => $description,
'Bool' => $description,
'true' => $description,
'DataJSON' => 'Any JSON-encodable data',
2023-01-27 19:10:52 +01:00
'JSONValue' => 'Any JSON-encodable data',
2023-01-27 18:12:33 +01:00
'InputFile' => 'A file name or a file URL. You can also use amphp async streams, amphp HTTP response objects, and [much more](https://docs.madelineproto.xyz/docs/FILES.html#downloading-files)!',
default => $descriptionEnhanced
? "$descriptionEnhanced @see https://docs.madelineproto.xyz/API_docs/types/$type.html"
: "@see https://docs.madelineproto.xyz/API_docs/types/$type.html"
2023-01-27 14:20:47 +01:00
};
}
2023-01-27 18:12:33 +01:00
private function filterParams(array $params, string $type, ?string $method = null): array
{
$newParams = [];
foreach ($params as $param) {
2023-06-28 15:50:38 +02:00
if (\in_array($param['name'], ['flags', 'flags2', 'random_id', 'random_bytes'], true)) {
2023-01-27 18:12:33 +01:00
continue;
}
if ($method) {
2023-10-01 20:05:04 +02:00
$param['description'] = str_replace(['](../', '.md'], ['](https://docs.madelineproto.xyz/API_docs/', '.html'], Lang::$lang['en']["method_{$method}_param_{$param['name']}_type_{$param['type']}"] ?? '');
2023-01-27 18:12:33 +01:00
}
2023-10-01 20:05:04 +02:00
$param['type'] = ltrim($param['type'], '%');
2023-01-27 18:12:33 +01:00
if ($param['name'] === 'data' && $type === 'messages.SentEncryptedMessage') {
$param['name'] = 'message';
$param['type'] = 'DecryptedMessage';
}
2023-01-27 19:00:11 +01:00
if ($param['name'] === 'bytes' && $type === 'EncryptedMessage') {
$param['name'] = 'decrypted_message';
$param['type'] = 'DecryptedMessage';
}
2023-06-28 15:50:38 +02:00
if ($type === 'DecryptedMessageMedia' && \in_array($param['name'], ['key', 'iv'], true)) {
2023-01-27 19:00:11 +01:00
continue;
}
2023-01-27 18:12:33 +01:00
if ($param['name'] === 'chat_id' && $method !== 'messages.discardEncryption') {
$param['type'] = 'InputPeer';
}
if ($param['name'] === 'hash' && $param['type'] === 'long') {
2023-12-15 18:45:28 +01:00
$param['pow'] ??= 'optional';
2023-01-27 18:12:33 +01:00
$param['type'] = 'Vector t';
$param['subtype'] = 'int|string';
2023-01-27 18:12:33 +01:00
}
if (\in_array($param['type'], ['int', 'long', 'strlong', 'string', 'bytes'], true)) {
2023-12-15 18:45:28 +01:00
$param['pow'] ??= 'optional';
2023-01-27 18:12:33 +01:00
}
$param['array'] = isset($param['subtype']);
if ($param['array']) {
2023-10-01 20:05:04 +02:00
$param['subtype'] = ltrim($param['subtype'], '%');
2023-01-27 18:12:33 +01:00
$param['type'] = 'Vector<'.$param['subtype'].'>';
2023-12-15 18:45:28 +01:00
$param['pow'] ??= 'optional';
2023-01-27 18:12:33 +01:00
}
2023-10-01 20:05:04 +02:00
if ($this->TL->getConstructors()->findByPredicate(lcfirst($param['type']).'Empty')) {
2023-12-15 18:45:28 +01:00
$param['pow'] ??= 'optional';
2023-09-06 18:39:48 +02:00
}
2023-01-27 18:12:33 +01:00
$newParams[$param['name']] = $param;
}
2023-11-11 16:55:29 +01:00
uasort($newParams, static fn (array $arr1, array $arr2) => isset($arr1['pow']) <=> isset($arr2['pow']));
2023-01-27 18:12:33 +01:00
return $newParams;
}
private function prepareTLParams(array $data): array
{
[
'params' => $params,
'type' => $type,
'method' => $method
] = $data;
$params = $this->filterParams($params, $type, $method);
$contents = '';
$signature = [];
2023-12-23 22:13:17 +01:00
if (\in_array($method, ['messages.getSplitRanges', 'contacts.getSaved', 'channels.getLeftChannels'], true)) {
$contents .= " * @param int \$takeoutId Takeout ID, generated using account.initTakeoutSession, see [the takeout docs](https://core.telegram.org/api/takeout) for more info.\n";
$signature []= "int \$takeoutId";
}
2023-01-27 18:12:33 +01:00
foreach ($params as $name => $param) {
2023-10-26 20:26:25 +02:00
if ($name === 'reply_to') {
2023-12-15 18:45:28 +01:00
$param['pow'] ??= 'yes';
2023-10-26 20:26:25 +02:00
$contents .= " * @param int \$reply_to_msg_id ID Of message to reply to\n";
$signature []= "int \$reply_to_msg_id = 0";
$contents .= " * @param int \$top_msg_id This field must contain the topic ID only when replying to messages in forum topics different from the \"General\" topic (i.e. reply_to_msg_id is set and reply_to_msg_id != topicID and topicID != 1). \n";
$signature []= "int \$top_msg_id = 0";
}
2023-01-27 18:12:33 +01:00
$description = $this->prepareTLTypeDescription($param['type'], $param['description']);
$psalmType = $this->prepareTLPsalmType($param['type'], true);
2023-12-15 18:45:28 +01:00
$type = $this->prepareTLType($param['type'], $param['pow'] ?? null);
2023-01-27 18:12:33 +01:00
$param_var = $type.' $'.$name;
if (isset($param['pow'])) {
2023-12-15 18:45:28 +01:00
if (\is_int($param['pow'])) {
$param_var .= ' = null';
$psalmDef = '|null';
} else {
$param_var .= ' = '.$this->prepareTLDefault($param['type']);
$psalmDef = $this->preparePsalmDefault($param['type']);
if ($psalmDef === 'array<never, never>') {
$psalmType .= '|'.$psalmDef;
}
2023-08-05 19:13:26 +02:00
}
2023-01-27 18:12:33 +01:00
}
$signature []= $param_var;
$contents .= " * @param {$psalmType} \${$name} {$description}\n";
2023-01-27 19:00:11 +01:00
if ($name === 'entities') {
2023-07-15 16:34:38 +02:00
$contents .= " * @param \\danog\\MadelineProto\\ParseMode \$parse_mode Whether to parse HTML or Markdown markup in the message\n";
$signature []= "\\danog\\MadelineProto\\ParseMode \$parse_mode = \\danog\\MadelineProto\\ParseMode::TEXT";
2023-01-27 19:00:11 +01:00
}
2023-01-27 18:12:33 +01:00
}
$contents .= " * @param ?int \$floodWaitLimit Can be used to specify a custom flood wait limit: if a FLOOD_WAIT_ rate limiting error is received with a waiting period bigger than this integer, an RPCErrorException will be thrown; otherwise, MadelineProto will simply wait for the specified amount of time. Defaults to the value specified in the settings: https://docs.madelineproto.xyz/PHP/danog/MadelineProto/Settings/RPC.html#setfloodtimeout-int-floodtimeout-self\n";
$signature []= "?int \$floodWaitLimit = null";
$contents .= " * @param ?string \$queueId If specified, ensures strict server-side execution order of concurrent calls with the same queue ID.\n";
$signature []= "?string \$queueId = null";
$contents .= " * @param ?\\Amp\\Cancellation \$cancellation Cancellation\n";
$signature []= "?\\Amp\\Cancellation \$cancellation = null";
2023-12-23 22:13:17 +01:00
// TODO users.getUsers
if (\in_array($method, ['messages.getDialogs', 'messages.getHistory', 'messages.search', 'stories.getStoriesArchive', 'photos.getUserPhotos', 'account.getAuthorizations', 'account.getWebAuthorizations'], true)) {
$contents .= " * @param ?int \$takeoutId Optional takeout ID, generated using account.initTakeoutSession, see [the takeout docs](https://core.telegram.org/api/takeout) for more info.\n";
$signature []= "?int \$takeoutId = null";
}
if (\in_array($method, ['messages.sendMessage', 'messages.editMessage', 'messages.sendMedia', 'messages.sendMultiMedia', 'messages.setTyping'], true)) {
$contents .= " * @param ?string \$businessConnectionId Business connection ID, received through an updateBotBusinessConnect update.\n";
$signature []= "?string \$businessConnectionId = null";
}
2023-01-27 18:12:33 +01:00
return [$contents, $signature];
}
/**
* Create internalDoc.
*/
private function createInternalClasses(): void
{
2022-12-30 19:21:36 +01:00
Logger::log('Creating internal classes...', Logger::NOTICE);
2019-09-18 20:46:20 +02:00
$internalDoc = [];
2023-01-27 18:12:33 +01:00
foreach ($this->TL->getMethods()->by_id as $data) {
2023-10-01 20:05:04 +02:00
if (!strpos($data['method'], '.')) {
continue;
}
2023-09-28 15:55:32 +02:00
if ($data['type'] === 'Vector t') {
$data['type'] = "Vector<{$data['subtype']}>";
}
2023-10-01 20:05:04 +02:00
[$namespace, $method] = explode('.', $data['method']);
2023-06-28 15:50:38 +02:00
if (!\in_array($namespace, $this->TL->getMethodNamespaces(), true)) {
continue;
}
2023-01-27 14:20:47 +01:00
if (isset($this->blacklist[$data['method']])) {
continue;
}
2023-01-27 18:12:33 +01:00
2023-10-01 20:05:04 +02:00
$title = str_replace(['](../', '.md'], ['](https://docs.madelineproto.xyz/API_docs/', '.html'], Lang::$lang['en']["method_{$data['method']}"] ?? '');
$title = implode("\n * ", explode("\n", $title));
2023-01-27 18:12:33 +01:00
$contents = "\n /**\n";
$contents .= " * {$title}\n";
$contents .= " *\n";
[$params, $signature] = $this->prepareTLParams($data);
$contents .= $params;
2023-12-15 18:45:28 +01:00
$returnType = $this->prepareTLType($data['type'], $data['pow'] ?? null);
2023-01-27 18:12:33 +01:00
$psalmType = $this->prepareTLPsalmType($data['type'], false);
$description = $this->prepareTLTypeDescription($data['type'], '');
$contents .= " * @return {$psalmType} {$description}\n";
$contents .= " */\n";
$contents .= " public function {$method}(";
2023-10-01 20:05:04 +02:00
$contents .= implode(', ', $signature);
2023-01-27 18:12:33 +01:00
$contents .= "): {$returnType};\n";
$internalDoc[$namespace][$method] = $contents;
}
2022-12-30 19:21:36 +01:00
$class = new ReflectionClass($this->reflectionClasses['MTProto']);
$methods = $class->getMethods((ReflectionMethod::IS_STATIC & ReflectionMethod::IS_PUBLIC) | ReflectionMethod::IS_PUBLIC);
$class = new ReflectionClass(Tools::class);
2023-10-01 20:05:04 +02:00
$methods = array_merge($methods, $class->getMethods((ReflectionMethod::IS_STATIC & ReflectionMethod::IS_PUBLIC) | ReflectionMethod::IS_PUBLIC));
2019-09-18 21:21:34 +02:00
foreach ($methods as $key => $method) {
2019-09-18 20:46:20 +02:00
$name = $method->getName();
2023-01-08 20:33:54 +01:00
if ($name == 'methodCallAsyncRead') {
2023-11-11 16:55:29 +01:00
unset($methods[array_search('methodCall', $methods, true)]);
} elseif (str_starts_with($name, '__')) {
2019-09-18 21:21:34 +02:00
unset($methods[$key]);
2023-10-01 20:05:04 +02:00
} elseif (stripos($name, 'async') !== false) {
2023-11-11 16:55:29 +01:00
if (str_contains($name, '_async')) {
unset($methods[array_search(str_ireplace('_async', '', $name), $methods, true)]);
2019-09-18 20:46:20 +02:00
} else {
2023-11-11 16:55:29 +01:00
unset($methods[array_search(str_ireplace('async', '', $name), $methods, true)]);
2019-09-18 20:46:20 +02:00
}
}
}
2020-04-05 15:33:01 +02:00
$sortedMethods = [];
foreach ($methods as $method) {
$sortedMethods[$method->getName()] = $method;
}
2023-10-01 20:05:04 +02:00
ksort($sortedMethods);
$methods = array_values($sortedMethods);
2020-04-05 15:33:01 +02:00
2019-09-18 20:46:20 +02:00
foreach ($methods as $method) {
$name = $method->getName();
2023-11-11 16:55:29 +01:00
if (str_contains($method->getDocComment() ?: '', '@internal')) {
continue;
}
2019-12-28 16:45:00 +01:00
$static = $method->isStatic();
if (!$static) {
2023-10-01 20:05:04 +02:00
$code = file_get_contents($method->getFileName());
$code = implode("\n", \array_slice(explode("\n", $code), $method->getStartLine(), $method->getEndLine() - $method->getStartLine()));
2023-11-11 16:55:29 +01:00
if (!str_contains($code, '$this')) {
2020-01-31 19:29:43 +01:00
Logger::log("{$name} should be STATIC!", Logger::FATAL_ERROR);
2019-12-28 16:45:00 +01:00
}
}
2024-04-06 17:13:15 +02:00
if ($name === 'initDbProperties') {
continue;
}
if ($name === 'saveDbProperties') {
continue;
}
if ($name == 'methodCallAsyncRead') {
$name = 'methodCall';
2023-10-01 20:05:04 +02:00
} elseif (stripos($name, 'async') !== false) {
2023-11-11 16:55:29 +01:00
if (str_contains($name, '_async')) {
2023-10-01 20:05:04 +02:00
$name = str_ireplace('_async', '', $name);
2019-09-18 20:46:20 +02:00
} else {
2023-10-01 20:05:04 +02:00
$name = str_ireplace('async', '', $name);
2019-09-18 20:46:20 +02:00
}
}
2020-06-16 17:52:55 +02:00
$name = StrTools::toCamelCase($name);
2023-10-01 20:05:04 +02:00
$name = str_ireplace(['mtproto', 'api'], ['MTProto', 'API'], $name);
2024-03-10 15:55:18 +01:00
$doc = 'final public ';
if ($static) {
$doc .= 'static ';
}
$doc .= 'function ';
2019-09-18 20:46:20 +02:00
$doc .= $name;
$doc .= '(';
$paramList = '';
2024-06-26 18:25:54 +02:00
$hasCancellation = false;
2019-09-18 20:46:20 +02:00
foreach ($method->getParameters() as $param) {
if ($type = $param->getType()) {
2023-01-08 20:33:54 +01:00
$doc .= $this->typeToStr($type).' ';
2019-12-28 17:11:08 +01:00
} else {
2023-01-04 15:13:55 +01:00
Logger::log($name.'.'.$param->getName().' has no type!', Logger::WARNING);
2019-09-18 20:46:20 +02:00
}
if ($param->isVariadic()) {
$doc .= '...';
}
if ($param->isPassedByReference()) {
$doc .= '&';
}
$doc .= '$';
$doc .= $param->getName();
if ($param->isOptional() && !$param->isVariadic()) {
$doc .= ' = ';
if ($param->isDefaultValueConstant()) {
2023-10-01 20:05:04 +02:00
$doc .= '\\'.str_replace(['NULL', 'self'], ['null', 'danog\\MadelineProto\\MTProto'], $param->getDefaultValueConstantName());
2019-09-18 20:46:20 +02:00
} else {
2023-10-01 20:05:04 +02:00
$doc .= str_replace('NULL', 'null', var_export($param->getDefaultValue(), true));
2019-09-18 20:46:20 +02:00
}
}
$doc .= ', ';
2019-09-18 21:21:34 +02:00
if ($param->isVariadic()) {
$paramList .= '...';
}
2024-06-26 18:25:54 +02:00
$paramList .= '$'.$param->getName();
if ($param->getName() === 'cancellation') {
$hasCancellation = true;
}
$paramList .= ', ';
}
if (!$hasCancellation && !$static) {
Logger::log($name.'.'.$param->getName().' has no cancellation!', Logger::WARNING);
2019-09-18 20:46:20 +02:00
}
2022-07-16 18:10:33 +02:00
$type = $method->getReturnType();
$hasReturnValue = $type !== null;
2023-10-01 20:05:04 +02:00
$doc = rtrim($doc, ', ');
$paramList = rtrim($paramList, ', ');
2023-01-04 15:13:55 +01:00
$doc .= ')';
2019-12-28 16:45:00 +01:00
$async = true;
2023-01-31 14:11:31 +01:00
if ($hasReturnValue) {
2019-09-18 20:46:20 +02:00
$doc .= ': ';
2023-01-08 20:33:54 +01:00
$doc .= $this->typeToStr($type);
2019-12-28 16:45:00 +01:00
$async = false;
2019-09-18 20:46:20 +02:00
}
2020-04-05 15:33:01 +02:00
if ($method->getDeclaringClass()->getName() == Tools::class) {
$async = false;
}
2022-06-04 16:51:23 +02:00
if ($method->getDeclaringClass()->getName() == StrTools::class) {
$async = false;
}
2023-01-27 14:20:47 +01:00
if ($method->getDeclaringClass()->getName() == AsyncTools::class) {
$async = false;
}
$ret = $type && $type instanceof ReflectionNamedType && $type->getName() === 'void' ? '' : 'return';
$doc .= "\n{\n";
2020-01-19 13:46:49 +01:00
if ($async) {
2023-01-31 14:11:31 +01:00
$doc .= " {$ret} \$this->wrapper->getAPI()->{$name}({$paramList});\n";
2020-01-19 13:46:49 +01:00
} elseif (!$static) {
2023-01-26 14:33:30 +01:00
$doc .= " {$ret} \$this->wrapper->getAPI()->{$name}({$paramList});\n";
2019-12-28 16:45:00 +01:00
} else {
2023-01-04 15:13:55 +01:00
$doc .= " {$ret} \\".$method->getDeclaringClass()->getName().'::'.$name."({$paramList});\n";
2019-12-28 16:45:00 +01:00
}
$doc .= "}\n";
if (!$method->getDocComment()) {
2020-01-31 19:29:43 +01:00
Logger::log("{$name} has no PHPDOC!", Logger::FATAL_ERROR);
}
if (!$type) {
2020-01-31 19:29:43 +01:00
Logger::log("{$name} has no return type!", Logger::FATAL_ERROR);
}
2023-01-15 11:21:57 +01:00
$phpdoc = $method->getDocComment() ?: '';
2023-01-27 18:12:33 +01:00
$internalDoc['InternalDoc'][$name] = $phpdoc;
2023-10-01 20:05:04 +02:00
$internalDoc['InternalDoc'][$name] .= "\n ".implode("\n ", explode("\n", $doc));
2019-09-18 20:46:20 +02:00
}
foreach ($internalDoc as $namespace => $methods) {
2019-09-18 20:46:20 +02:00
if ($namespace === 'InternalDoc') {
2023-12-24 15:15:09 +01:00
$handle = fopen(__DIR__.'/../src/InternalDoc.php', 'w');
2023-10-01 20:05:04 +02:00
fwrite($handle, "<?php\n");
fwrite($handle, "/**\n");
fwrite($handle, " * This file is automatically generated by the build_docs.php file\n");
fwrite($handle, " * and is used only for autocompletion in multiple IDEs\n");
fwrite($handle, " * don't modify it manually.\n");
fwrite($handle, " */\n\n");
fwrite($handle, "namespace {$this->namespace};\n");
fwrite($handle, "use Generator;\n");
fwrite($handle, "use Amp\\Future;\n");
fwrite($handle, "use Closure;\n");
fwrite($handle, "use __PHP_Incomplete_Class;\n");
fwrite($handle, "use Amp\\ByteStream\\WritableStream;\n");
fwrite($handle, "use Amp\\ByteStream\\ReadableStream;\n");
fwrite($handle, "use Amp\\ByteStream\\Pipe;\n");
fwrite($handle, "use Amp\\Cancellation;\n");
fwrite($handle, "use Amp\\Http\\Server\\Request as ServerRequest;\n");
fwrite($handle, "use danog\\MadelineProto\\Broadcast\\Action;\n");
fwrite($handle, "use danog\\MadelineProto\\MTProtoTools\\DialogId;\n");
2023-08-31 18:50:48 +02:00
$had = [];
2023-08-05 19:13:26 +02:00
foreach (ClassFinder::getClassesInNamespace(\danog\MadelineProto\EventHandler::class, ClassFinder::RECURSIVE_MODE) as $class) {
2023-10-01 20:05:04 +02:00
$name = basename(str_replace('\\', '//', $class));
2023-09-06 19:33:52 +02:00
if (isset($had[$name]) || $name === 'Status' || $name === 'Action') {
2023-08-31 18:50:48 +02:00
continue;
}
$had[$name] = true;
2023-10-01 20:05:04 +02:00
fwrite($handle, "use $class;\n");
2023-08-05 19:13:26 +02:00
}
2023-08-05 20:15:50 +02:00
/** @psalm-suppress UndefinedClass */
2023-08-05 19:13:26 +02:00
foreach (ClassFinder::getClassesInNamespace(\danog\MadelineProto\Ipc::class, ClassFinder::RECURSIVE_MODE) as $class) {
2023-10-01 20:05:04 +02:00
if (str_contains($class, 'Wrapper')) {
2023-08-05 19:13:26 +02:00
continue;
}
2023-10-01 20:05:04 +02:00
fwrite($handle, "use $class;\n");
2023-08-05 19:13:26 +02:00
}
2023-08-05 20:15:50 +02:00
/** @psalm-suppress UndefinedClass */
2023-08-05 19:13:26 +02:00
foreach (ClassFinder::getClassesInNamespace(\danog\MadelineProto\Broadcast::class, ClassFinder::RECURSIVE_MODE) as $class) {
2023-10-01 20:05:04 +02:00
fwrite($handle, "use $class;\n");
2023-08-05 19:13:26 +02:00
}
2023-01-27 14:20:47 +01:00
2023-12-12 17:57:48 +01:00
fwrite($handle, "\n/** @psalm-suppress PossiblyNullReference, PropertyNotSetInConstructor */\nabstract class {$namespace}\n{\nprotected APIWrapper \$wrapper;\n");
2023-01-27 14:20:47 +01:00
foreach ($this->TL->getMethodNamespaces() as $namespace) {
2023-10-01 20:05:04 +02:00
$namespaceInterface = '\\danog\\MadelineProto\\Namespace\\'.ucfirst($namespace);
fwrite($handle, '/** @var '.$namespaceInterface.' $'.$namespace." */\n");
fwrite($handle, 'public $'.$namespace.";\n");
2023-01-27 14:20:47 +01:00
}
2023-10-01 20:05:04 +02:00
fwrite($handle, '
2023-01-27 14:20:47 +01:00
/**
* Export APIFactory instance with the specified namespace.
2023-01-27 14:42:41 +01:00
* @psalm-suppress InaccessibleProperty
2023-01-27 14:20:47 +01:00
*/
protected function exportNamespaces(): void
{
');
foreach ($this->TL->getMethodNamespaces() as $namespace) {
2023-10-01 20:05:04 +02:00
fwrite($handle, "\$this->$namespace ??= new \\danog\\MadelineProto\\Namespace\\AbstractAPI('$namespace');\n");
fwrite($handle, "\$this->{$namespace}->setWrapper(\$this->wrapper);\n");
2023-01-27 14:20:47 +01:00
}
2023-10-01 20:05:04 +02:00
fwrite($handle, "}\n");
2019-09-18 20:46:20 +02:00
} else {
2023-10-01 20:05:04 +02:00
$namespace = ucfirst($namespace);
2023-12-24 15:15:09 +01:00
$handle = fopen(__DIR__."/../src/Namespace/$namespace.php", 'w');
2023-10-01 20:05:04 +02:00
fwrite($handle, "<?php\n");
fwrite($handle, "/**\n");
fwrite($handle, " * This file is automatic generated by build_docs.php file\n");
fwrite($handle, " * and is used only for autocomplete in multiple IDE\n");
fwrite($handle, " * don't modify manually.\n");
fwrite($handle, " */\n\n");
fwrite($handle, "namespace {$this->namespace}\\Namespace;\n");
2023-01-27 14:20:47 +01:00
2023-10-01 20:05:04 +02:00
fwrite($handle, "\ninterface {$namespace}\n{");
2019-09-18 20:46:20 +02:00
}
2023-01-27 18:12:33 +01:00
foreach ($methods as $contents) {
2023-10-01 20:05:04 +02:00
fwrite($handle, $contents);
}
2023-10-01 20:05:04 +02:00
fwrite($handle, "}\n");
}
2023-10-01 20:05:04 +02:00
fclose($handle);
2023-09-16 19:49:34 +02:00
2023-12-24 15:15:09 +01:00
$handle = fopen(__DIR__.'/../src/EventHandler/SimpleFilters.php', 'w');
2023-10-01 20:05:04 +02:00
fwrite($handle, "<?php\n");
fwrite($handle, "/**\n");
fwrite($handle, " * This file is automatically generated by the build_docs.php file\n");
fwrite($handle, " * and is used only for autocompletion in multiple IDEs\n");
fwrite($handle, " * don't modify it manually.\n");
fwrite($handle, " */\n\n");
fwrite($handle, "namespace {$this->namespace}\\EventHandler;\n");
fwrite($handle, "/** @internal An internal interface used to avoid type errors when using simple filters. */\n");
fwrite($handle, "interface SimpleFilters extends ");
2023-09-16 19:49:34 +02:00
/** @psalm-suppress UndefinedClass */
2023-11-11 16:55:29 +01:00
fwrite($handle, implode(", ", array_map(static fn ($s) => "\\$s", ClassFinder::getClassesInNamespace(\danog\MadelineProto\EventHandler\SimpleFilter::class, ClassFinder::RECURSIVE_MODE|ClassFinder::ALLOW_INTERFACES))));
2023-10-01 20:05:04 +02:00
fwrite($handle, "{}\n");
}
2023-01-08 20:33:54 +01:00
private function typeToStr(ReflectionType $type): string
{
$new = '';
if ($type instanceof ReflectionNamedType) {
2023-01-31 14:11:31 +01:00
if ($type->allowsNull() && $type->getName() !== 'mixed' && $type->getName() !== 'null') {
2023-01-08 20:33:54 +01:00
$new .= '?';
}
if (!$type->isBuiltin()) {
$new .= '\\';
2023-01-27 19:10:52 +01:00
};
2023-01-08 20:33:54 +01:00
$new .= $type->getName() === 'self' ? $this->reflectionClasses['API'] : $type->getName();
} elseif ($type instanceof ReflectionUnionType) {
2023-10-01 20:05:04 +02:00
return implode('|', array_map($this->typeToStr(...), $type->getTypes()));
2023-01-08 20:33:54 +01:00
}
return $new;
}
2018-02-24 17:54:39 +01:00
}