diff --git a/.github/workflows/analysis.yaml b/.github/workflows/analysis.yaml
index 7486ca9..e35cf74 100644
--- a/.github/workflows/analysis.yaml
+++ b/.github/workflows/analysis.yaml
@@ -13,7 +13,7 @@ jobs:
os:
- ubuntu-latest
php:
- - "8.1"
+ - "8.5"
steps:
-
name: Checkout
diff --git a/.github/workflows/cs.yaml b/.github/workflows/cs.yaml
index 0c4584d..e7ab8e5 100644
--- a/.github/workflows/cs.yaml
+++ b/.github/workflows/cs.yaml
@@ -13,7 +13,7 @@ jobs:
os:
- ubuntu-latest
php:
- - "8.0"
+ - "8.5"
steps:
-
name: Checkout
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 30f8e5f..425249b 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -18,6 +18,7 @@ jobs:
- "8.2"
- "8.3"
- "8.4"
+ - "8.5"
deps:
- latest
- lowest
@@ -49,6 +50,10 @@ jobs:
name: Update composer
run: composer self-update
+ -
+ name: Remote tools
+ run: composer remove psalm/plugin-symfony vimeo/psalm friendsofphp/php-cs-fixer --dev
+
-
name: Install dependencies with composer
if: matrix.deps == 'latest'
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 074f5ea..0a91bf5 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -54,7 +54,9 @@
'yoda_style' => false,
'declare_strict_types' => false,
'void_return' => false,
- 'phpdoc_align' => [],
+ 'phpdoc_align' => [
+ 'align' => 'left',
+ ],
'phpdoc_to_comment' => false,
'single_line_comment_spacing' => false,
'nullable_type_declaration_for_default_null_value' => true,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a5ca7c2..357adf7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,17 @@
CHANGELOG
=========
+6.0.2 (2025-12-15)
+------------------
+
+* Add Symfony 8 support
+* Add php 8.5 support
+
+6.0.1 (2025-07-10)
+------------------
+
+* Add php 8.4 support
+
6.0.0 (2024-09-27)
------------------
diff --git a/composer.json b/composer.json
index 3e91fd2..ccce56d 100644
--- a/composer.json
+++ b/composer.json
@@ -27,20 +27,20 @@
"php": "^8.0",
"ext-curl": "*",
"ext-json": "*",
- "symfony/console": "^5.4 || ^6.0 || ^7.0",
- "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0",
- "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0",
+ "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0",
+ "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0 || ^8.0",
+ "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"telegram-bot/api": "^2.3.14"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "~3.23.0",
+ "friendsofphp/php-cs-fixer": "~3.92.0",
"symfony/phpunit-bridge": "^7.0.1",
- "symfony/security-http": "^5.4 || ^6.0 || ^7.0",
- "symfony/http-client": "^5.4 || ^6.0 || ^7.0",
- "symfony/messenger": "^5.4 || ^6.0 || ^7.0",
- "symfony/yaml": "^5.4 || ^6.0 || ^7.0",
- "vimeo/psalm": "~4.30.0",
- "psalm/plugin-symfony": "^4.0"
+ "symfony/security-http": "^5.4 || ^6.0 || ^7.0 || ^8.0",
+ "symfony/http-client": "^5.4 || ^6.0 || ^7.0 || ^8.0",
+ "symfony/messenger": "^5.4 || ^6.0 || ^7.0 || ^8.0",
+ "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0",
+ "vimeo/psalm": "~6.14.2",
+ "psalm/plugin-symfony": "^5.2.7"
},
"suggest": {
"symfony/security-guard": "Required to implement user authentication through Telegram",
diff --git a/psalm.xml b/psalm.xml
index 881c5e4..03ee220 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -13,14 +13,10 @@
-
-
-
-
-
-
-
+
+
+
diff --git a/src/Authenticator/TelegramAuthenticator.php b/src/Authenticator/TelegramAuthenticator.php
index 225748c..f0c62c9 100644
--- a/src/Authenticator/TelegramAuthenticator.php
+++ b/src/Authenticator/TelegramAuthenticator.php
@@ -39,6 +39,7 @@ public function __construct(
) {
}
+ #[\Override]
public function supports(Request $request): ?bool
{
$route = $request->attributes->get('_route');
@@ -46,6 +47,7 @@ public function supports(Request $request): ?bool
return $route === $this->guardRoute;
}
+ #[\Override]
public function authenticate(Request $request): Passport
{
$credentials = $request->query->all();
@@ -66,6 +68,7 @@ public function authenticate(Request $request): Passport
}));
}
+ #[\Override]
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
@@ -75,6 +78,7 @@ public function onAuthenticationSuccess(Request $request, TokenInterface $token,
return new RedirectResponse($this->urlGenerator->generate($this->defaultTargetRoute));
}
+ #[\Override]
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
if ($this->loginRoute) {
diff --git a/src/BoShurikTelegramBotBundle.php b/src/BoShurikTelegramBotBundle.php
index 0bc9136..2e3af96 100644
--- a/src/BoShurikTelegramBotBundle.php
+++ b/src/BoShurikTelegramBotBundle.php
@@ -19,6 +19,7 @@
final class BoShurikTelegramBotBundle extends Bundle
{
+ #[\Override]
public function build(ContainerBuilder $container): void
{
parent::build($container);
@@ -26,12 +27,13 @@ public function build(ContainerBuilder $container): void
$container->addCompilerPass(new CommandCompilerPass());
}
+ #[\Override]
public function getContainerExtension(): ?ExtensionInterface
{
if (null === $this->extension) {
$this->extension = new BoShurikTelegramBotExtension();
}
- return $this->extension;
+ return $this->extension ?: null;
}
}
diff --git a/src/Command/UpdatesCommand.php b/src/Command/UpdatesCommand.php
index 17ec800..7e70088 100644
--- a/src/Command/UpdatesCommand.php
+++ b/src/Command/UpdatesCommand.php
@@ -24,6 +24,7 @@ public function __construct(private Telegram $telegram)
parent::__construct();
}
+ #[\Override]
protected function configure(): void
{
$this
@@ -33,9 +34,9 @@ protected function configure(): void
;
}
+ #[\Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- /** @var string|null $bot */
$bot = $input->getArgument('bot');
if ($bot) {
$this->telegram->processUpdates($bot);
diff --git a/src/Command/Webhook/InfoCommand.php b/src/Command/Webhook/InfoCommand.php
index 9bf461e..7c153e5 100644
--- a/src/Command/Webhook/InfoCommand.php
+++ b/src/Command/Webhook/InfoCommand.php
@@ -26,6 +26,7 @@ public function __construct(private BotLocator $botLocator)
parent::__construct();
}
+ #[\Override]
protected function configure(): void
{
$this
@@ -35,11 +36,11 @@ protected function configure(): void
;
}
+ #[\Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
- /** @var string|null $bot */
$bot = $input->getArgument('bot');
if ($bot) {
$api = $this->botLocator->get($bot);
@@ -56,7 +57,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
private function printWebhookInfo(SymfonyStyle $io, string $name, BotApi $api): void
{
- $io->block(sprintf('Bot "%s"', $name));
+ $io->block(\sprintf('Bot "%s"', $name));
$info = $api->getWebhookInfo();
diff --git a/src/Command/Webhook/SetCommand.php b/src/Command/Webhook/SetCommand.php
index e567647..c8b068f 100644
--- a/src/Command/Webhook/SetCommand.php
+++ b/src/Command/Webhook/SetCommand.php
@@ -34,6 +34,7 @@ public function __construct(private BotLocator $botLocator, private UrlGenerator
parent::__construct();
}
+ #[\Override]
protected function configure(): void
{
$this
@@ -51,6 +52,7 @@ protected function configure(): void
;
}
+ #[\Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -58,15 +60,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$certificateFile = null;
if ($certificate = $input->getArgument('certificate')) {
if (!is_file($certificate) || !is_readable($certificate)) {
- throw new \RuntimeException(sprintf('Can\'t read certificate file "%s"', $certificate));
+ throw new \RuntimeException(\sprintf('Can\'t read certificate file "%s"', $certificate));
}
$certificateFile = new \CURLFile($certificate);
}
- /** @var string|null $urlOrHostname */
$urlOrHostname = $input->getArgument('urlOrHostname');
- /** @var string|null $bot */
$bot = $input->getOption('bot');
$allowedUpdates = $input->getOption('allowedUpdateType');
@@ -108,7 +108,7 @@ private function setWebhook(
?\CURLFile $certificateFile = null,
?array $allowedUpdates = null
): bool {
- $io->block(sprintf('Bot "%s"', $name));
+ $io->block(\sprintf('Bot "%s"', $name));
if (!$urlOrHostname) {
$url = $this->urlGenerator->generate('_telegram_bot_webhook', [
@@ -127,7 +127,7 @@ private function setWebhook(
} catch (RouteNotFoundException $e) {
$helpUrl = 'https://github.com/BoShurik/TelegramBotBundle#add-routing-for-webhook';
$message = "We could not find the webhook route. Read on\n%s>\nhow to add the route or use symfony/flex.";
- $io->block(messages: sprintf($message, $helpUrl), escape: false);
+ $io->block(messages: \sprintf($message, $helpUrl), escape: false);
return false;
}
@@ -135,9 +135,9 @@ private function setWebhook(
$url = $urlOrHostname;
}
- $api->setWebhook($url, $certificateFile, null, self::MAX_CONNECTIONS, json_encode($allowedUpdates));
+ $api->setWebhook($url, $certificateFile, null, self::MAX_CONNECTIONS, (string) json_encode($allowedUpdates));
- $message = sprintf('Webhook URL has been set to %s>', $url);
+ $message = \sprintf('Webhook URL has been set to %s>', $url);
$io->block($message, 'OK', 'fg=black;bg=green', ' ', true, false);
return true;
diff --git a/src/Command/Webhook/UnsetCommand.php b/src/Command/Webhook/UnsetCommand.php
index c5b674d..b8b71b4 100644
--- a/src/Command/Webhook/UnsetCommand.php
+++ b/src/Command/Webhook/UnsetCommand.php
@@ -26,6 +26,7 @@ public function __construct(private BotLocator $botLocator)
parent::__construct();
}
+ #[\Override]
protected function configure(): void
{
$this
@@ -35,11 +36,11 @@ protected function configure(): void
;
}
+ #[\Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
- /** @var string|null $bot */
$bot = $input->getArgument('bot');
if ($bot) {
$api = $this->botLocator->get($bot);
@@ -55,7 +56,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
private function deleteWebhook(SymfonyStyle $io, string $name, BotApi $api): void
{
- $io->block(sprintf('Bot "%s"', $name));
+ $io->block(\sprintf('Bot "%s"', $name));
$api->deleteWebhook();
diff --git a/src/DependencyInjection/BoShurikTelegramBotExtension.php b/src/DependencyInjection/BoShurikTelegramBotExtension.php
index 16ea721..83f435f 100644
--- a/src/DependencyInjection/BoShurikTelegramBotExtension.php
+++ b/src/DependencyInjection/BoShurikTelegramBotExtension.php
@@ -17,20 +17,24 @@
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Contracts\HttpClient\HttpClientInterface as SymfonyHttpClientInterface;
use TelegramBot\Api\BotApi;
use TelegramBot\Api\Http\CurlHttpClient;
use TelegramBot\Api\Http\HttpClientInterface;
use TelegramBot\Api\Http\SymfonyHttpClient;
+/**
+ * @psalm-suppress InternalClass
+ */
final class BoShurikTelegramBotExtension extends Extension
{
private const BOT_API_ID_TEMPLATE = 'boshurik_telegram_bot.api.bot.%s';
+ #[\Override]
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
@@ -45,25 +49,30 @@ public function load(array $configs, ContainerBuilder $container): void
$defaultBot = $config['api']['default_bot'];
+ /** @psalm-suppress UndefinedClass */
if (interface_exists(HttpClientInterface::class)) {
+ /** @psalm-suppress UndefinedClass */
if (interface_exists(SymfonyHttpClientInterface::class)) {
+ /** @psalm-suppress UndefinedClass */
$httpClient = new Definition(SymfonyHttpClient::class, [
new Reference(SymfonyHttpClientInterface::class),
]);
} else {
+ /** @psalm-suppress UndefinedClass */
$httpClient = new Definition(CurlHttpClient::class);
$httpClient->addMethodCall('setProxy', [new Parameter('boshurik_telegram_bot.api.proxy')]);
$httpClient->addMethodCall('setOption', [\CURLOPT_TIMEOUT, new Parameter('boshurik_telegram_bot.api.timeout')]);
}
+ /** @psalm-suppress UndefinedClass */
$container->setDefinition(HttpClientInterface::class, $httpClient);
}
$bots = [];
$registries = [];
foreach ($config['api']['bots'] as $name => $bot) {
- $botId = sprintf(self::BOT_API_ID_TEMPLATE, $name);
- $registryId = sprintf(CommandCompilerPass::REGISTRY_ID_TEMPLATE, $name);
+ $botId = \sprintf(self::BOT_API_ID_TEMPLATE, $name);
+ $registryId = \sprintf(CommandCompilerPass::REGISTRY_ID_TEMPLATE, $name);
$container
->setDefinition(
@@ -123,6 +132,7 @@ public function load(array $configs, ContainerBuilder $container): void
;
}
+ #[\Override]
public function getAlias(): string
{
return 'boshurik_telegram_bot';
diff --git a/src/DependencyInjection/Compiler/CommandCompilerPass.php b/src/DependencyInjection/Compiler/CommandCompilerPass.php
index 57df5c4..7e85cf2 100644
--- a/src/DependencyInjection/Compiler/CommandCompilerPass.php
+++ b/src/DependencyInjection/Compiler/CommandCompilerPass.php
@@ -26,6 +26,7 @@ final class CommandCompilerPass implements CompilerPassInterface
public const REGISTRY_ID_TEMPLATE = 'boshurik_telegram_bot.command.registry.%s';
+ #[\Override]
public function process(ContainerBuilder $container): void
{
$commands = [];
@@ -33,11 +34,11 @@ public function process(ContainerBuilder $container): void
foreach ($this->findAndSortTaggedServices(new TaggedIteratorArgument(self::COMMAND_TAG), $container) as $command) {
$definition = $container->getDefinition((string) $command);
if (!$class = $definition->getClass()) {
- throw new LogicException(sprintf('Unknown class for service "%s"', (string) $command));
+ throw new LogicException(\sprintf('Unknown class for service "%s"', (string) $command));
}
$interfaces = class_implements($class);
if (!isset($interfaces[CommandInterface::class])) {
- throw new LogicException(sprintf('Can\'t apply tag "%s" to %s class. It must implement %s interface', self::COMMAND_TAG, $class, CommandInterface::class));
+ throw new LogicException(\sprintf('Can\'t apply tag "%s" to %s class. It must implement %s interface', self::COMMAND_TAG, $class, CommandInterface::class));
}
$tags = $definition->getTag(self::COMMAND_TAG);
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index 9f173af..0308d3e 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -17,13 +17,14 @@
final class Configuration implements ConfigurationInterface
{
+ #[\Override]
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('boshurik_telegram_bot');
/** @var ArrayNodeDefinition $rootNode */
$rootNode = $treeBuilder->getRootNode();
- /** @psalm-suppress all */
+ /** @psalm-suppress PossiblyNullReference,UnusedForeachValue,PossiblyNullArrayOffset,UndefinedInterfaceMethod */
$rootNode
->children()
->arrayNode('api')->isRequired()
diff --git a/src/EventListener/CommandListener.php b/src/EventListener/CommandListener.php
index 3bd4f32..207e155 100644
--- a/src/EventListener/CommandListener.php
+++ b/src/EventListener/CommandListener.php
@@ -18,6 +18,7 @@
final class CommandListener implements EventSubscriberInterface
{
+ #[\Override]
public static function getSubscribedEvents(): array
{
return [
diff --git a/src/Telegram/BotLocator.php b/src/Telegram/BotLocator.php
index 6561e97..f6b95e9 100644
--- a/src/Telegram/BotLocator.php
+++ b/src/Telegram/BotLocator.php
@@ -24,13 +24,15 @@ public function get(string $bot): BotApi
{
$api = $this->locator->get($bot);
if (!$api instanceof BotApi) {
- throw new \RuntimeException(sprintf('Expect "%s", instance of "%s" given', BotApi::class, $api::class));
+ throw new \RuntimeException(\sprintf('Expect "%s", instance of "%s" given', BotApi::class, $api::class));
}
return $api;
}
/**
+ * @psalm-suppress UnusedForeachValue
+ *
* @return \Generator
*/
public function all(): \Generator
diff --git a/src/Telegram/Command/AbstractCommand.php b/src/Telegram/Command/AbstractCommand.php
index 3df5672..273c203 100644
--- a/src/Telegram/Command/AbstractCommand.php
+++ b/src/Telegram/Command/AbstractCommand.php
@@ -31,6 +31,7 @@ public function getAliases(): array
return [];
}
+ #[\Override]
public function isApplicable(Update $update): bool
{
$callbackQuery = $update->getCallbackQuery();
diff --git a/src/Telegram/Command/HelpCommand.php b/src/Telegram/Command/HelpCommand.php
index 49eea34..912c5ba 100644
--- a/src/Telegram/Command/HelpCommand.php
+++ b/src/Telegram/Command/HelpCommand.php
@@ -25,6 +25,7 @@ public function __construct(
) {
}
+ #[\Override]
public function execute(BotApi $api, Update $update): void
{
$commands = $this->commandRegistry->getCommands();
@@ -37,22 +38,25 @@ public function execute(BotApi $api, Update $update): void
continue;
}
- $reply .= sprintf("%s - %s\n", $command->getName(), $command->getDescription());
+ $reply .= \sprintf("%s - %s\n", $command->getName(), $command->getDescription());
}
$api->sendMessage($message->getChat()->getId(), $reply);
}
+ #[\Override]
public function getName(): string
{
return '/help';
}
+ #[\Override]
public function getAliases(): array
{
return $this->aliases;
}
+ #[\Override]
public function getDescription(): string
{
return $this->description;
diff --git a/src/Telegram/Command/Registry/CommandRegistryLocator.php b/src/Telegram/Command/Registry/CommandRegistryLocator.php
index d04652e..a5c6ae4 100644
--- a/src/Telegram/Command/Registry/CommandRegistryLocator.php
+++ b/src/Telegram/Command/Registry/CommandRegistryLocator.php
@@ -23,7 +23,7 @@ public function get(string $bot): CommandRegistry
{
$registry = $this->locator->get($bot);
if (!$registry instanceof CommandRegistry) {
- throw new \RuntimeException(sprintf('Expect "%s", instance of "%s" given', CommandRegistry::class, $registry::class));
+ throw new \RuntimeException(\sprintf('Expect "%s", instance of "%s" given', CommandRegistry::class, $registry::class));
}
return $registry;
diff --git a/src/Telegram/Telegram.php b/src/Telegram/Telegram.php
index b6ef212..e62bf2a 100644
--- a/src/Telegram/Telegram.php
+++ b/src/Telegram/Telegram.php
@@ -26,6 +26,9 @@ public function __construct(
) {
}
+ /**
+ * @psalm-suppress UnusedForeachValue
+ */
public function processAllUpdates(): void
{
foreach ($this->botLocator->all() as $name => $id) {
diff --git a/tests/EventListener/CommandListenerTest.php b/tests/EventListener/CommandListenerTest.php
index afe2d24..346e613 100644
--- a/tests/EventListener/CommandListenerTest.php
+++ b/tests/EventListener/CommandListenerTest.php
@@ -45,7 +45,7 @@ public function testNotProcessedWhenCommandsIsNotApplicable(): void
$event = new UpdateEvent('default', $update);
$listener = $this->createListener([
- new class() implements CommandInterface {
+ new class implements CommandInterface {
public function execute(BotApi $api, Update $update): void
{
}
@@ -68,7 +68,7 @@ public function testProcessed(): void
$event = new UpdateEvent('default', $update);
$listener = $this->createListener([
- new class() implements CommandInterface {
+ new class implements CommandInterface {
public function execute(BotApi $api, Update $update): void
{
}
diff --git a/tests/Kernel/Multiple/MultipleTestKernel.php b/tests/Kernel/Multiple/MultipleTestKernel.php
index 63d57a9..9899f57 100644
--- a/tests/Kernel/Multiple/MultipleTestKernel.php
+++ b/tests/Kernel/Multiple/MultipleTestKernel.php
@@ -35,7 +35,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
protected function build(ContainerBuilder $container): void
{
- $container->addCompilerPass(new class() implements CompilerPassInterface {
+ $container->addCompilerPass(new class implements CompilerPassInterface {
public function process(ContainerBuilder $container): void
{
$router = $container->getDefinition('router.default');
diff --git a/tests/Kernel/Single/SingleTestKernel.php b/tests/Kernel/Single/SingleTestKernel.php
index 8741a02..cca0aee 100644
--- a/tests/Kernel/Single/SingleTestKernel.php
+++ b/tests/Kernel/Single/SingleTestKernel.php
@@ -35,7 +35,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
protected function build(ContainerBuilder $container): void
{
- $container->addCompilerPass(new class() implements CompilerPassInterface {
+ $container->addCompilerPass(new class implements CompilerPassInterface {
public function process(ContainerBuilder $container): void
{
$router = $container->getDefinition('router.default');