Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
a string, a `RichText` object or an array of them, instead of failing with `TypeError` or silently casting scalars
to strings.
- Bug #227: Discard keys of rich text arrays on serialization, so they are always sent as JSON arrays, not objects.
- Bug #228: Fix "Undefined array key" error in `InputFile::filename()` and `CurlTransport` when a stream resource has
no URI.

## 0.22 August 25, 2026

Expand Down
2 changes: 1 addition & 1 deletion src/Transport/CurlTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ private function toCurlFile(InputFile $file): CURLFile|CURLStringFile
}

$metadata = stream_get_meta_data($file->pathOrResource);
if (!str_contains($metadata['uri'], '://')) {
if (isset($metadata['uri']) && !str_contains($metadata['uri'], '://')) {
return new CURLFile($metadata['uri'], $mimeType, $file->filename());
}

Expand Down
8 changes: 6 additions & 2 deletions src/Type/InputFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ public function filename(): ?string
return basename($this->pathOrResource);
}

$uri = stream_get_meta_data($this->pathOrResource)['uri'];
return str_contains($uri, '://') ? null : basename($uri);
$uri = stream_get_meta_data($this->pathOrResource)['uri'] ?? null;
if ($uri === null || str_contains($uri, '://')) {
return null;
}

return basename($uri);
}

/**
Expand Down
19 changes: 19 additions & 0 deletions tests/Support/TestHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Phptg\BotApi\Tests\Support;

use Psr\Log\LoggerInterface;
use RuntimeException;
use Phptg\BotApi\Transport\ApiResponse;
use Phptg\BotApi\TelegramBotApi;

Expand All @@ -26,4 +27,22 @@ public static function createSuccessStubApi(
logger: $logger,
);
}

/**
* Creates a pair of connected sockets. Such stream resources have no URI in their metadata.
*
* @return array{resource, resource}
*/
public static function createSocketPair(): array
{
$pair = stream_socket_pair(
PHP_OS_FAMILY === 'Windows' ? STREAM_PF_INET : STREAM_PF_UNIX,
STREAM_SOCK_STREAM,
STREAM_IPPROTO_IP,
);
if ($pair === false) {
throw new RuntimeException('Failed to create a socket pair.');
}
return $pair;
}
}
24 changes: 24 additions & 0 deletions tests/Transport/CurlTransport/CurlTransportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\TestCase;
use Phptg\BotApi\Tests\Curl\CurlMock;
use Phptg\BotApi\Tests\Support\TestHelper;
use Phptg\BotApi\Transport\CurlTransport;
use Phptg\BotApi\Type\InputFile;

Expand Down Expand Up @@ -155,6 +156,29 @@ public function testPostWithResource(string $mimeType, ?string $filename): void
assertSame($mimeType, $postFields['file']->mime);
}

public function testPostWithResourceWithoutUri(): void
{
$curl = new CurlMock();
$transport = new CurlTransport(curl: $curl);

[$resource, $pair] = TestHelper::createSocketPair();
fwrite($pair, 'socket content');
fclose($pair);

$transport->postWithFiles(
'//url/method',
[],
['file' => new InputFile($resource)],
);

fclose($resource);

$postFields = $curl->getOptions()[CURLOPT_POSTFIELDS];
assertInstanceOf(CURLStringFile::class, $postFields['file']);
assertSame('socket content', $postFields['file']->data);
assertSame('', $postFields['file']->postname);
}

public function testSeekableResource(): void
{
$curl = new CurlMock();
Expand Down
35 changes: 35 additions & 0 deletions tests/Transport/NativeTransport/NativeTransportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PHPUnit\Framework\TestCase;
use RuntimeException;
use Phptg\BotApi\Tests\Support\TestHelper;
use Phptg\BotApi\Tests\Transport\NativeTransport\StreamMock\StreamMock;
use Phptg\BotApi\Transport\MimeTypeResolver\MimeTypeResolverInterface;
use Phptg\BotApi\Transport\NativeTransport;
Expand Down Expand Up @@ -269,6 +270,40 @@ public function testPostWithSeekableStreamResource(): void
assertStringContainsString('stream content', $request['options']['http']['content']);
}

public function testPostWithResourceWithoutUri(): void
{
$transport = new NativeTransport();

StreamMock::enable(
responseHeaders: [
'HTTP/1.1 200 OK',
'Content-Type: text/json',
],
responseBody: '{"ok":true,"result":[]}',
);

[$resource, $pair] = TestHelper::createSocketPair();
fwrite($pair, 'socket content');
fclose($pair);

$transport->postWithFiles(
'http://url/method',
[],
['file' => new InputFile($resource)],
);

fclose($resource);

$request = StreamMock::disable();

assertStringContainsString(
"Content-Disposition: form-data; name=\"file\"\r\n"
. "\r\n"
. "socket content\r\n",
$request['options']['http']['content'],
);
}

public function testErrorOnSend(): void
{
$transport = new NativeTransport();
Expand Down
13 changes: 13 additions & 0 deletions tests/Type/InputFileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Phptg\BotApi\Tests\Type;

use PHPUnit\Framework\TestCase;
use Phptg\BotApi\Tests\Support\TestHelper;
use Phptg\BotApi\Type\InputFile;

use function PHPUnit\Framework\assertNull;
Expand Down Expand Up @@ -53,6 +54,18 @@ public function testFromVirtualResource(): void
fclose($resource);
}

public function testFromResourceWithoutUri(): void
{
[$resource, $pair] = TestHelper::createSocketPair();
$file = new InputFile($resource);

assertNull($file->filename());
assertNull($file->extension());

fclose($resource);
fclose($pair);
}

public function testFromResourceWithFilename(): void
{
$resource = fopen('php://temp', 'r+b');
Expand Down
Loading