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
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# Telegram Bot API for PHP Change Log

## 0.18.2 under construction

## 0.19 April 18, 2026

- New #195: Add `InputFile::filename()` and `InputFile::extension()` methods.
- Chg #195: Remove `InputFile::fromLocalFile()` method, pass a file path directly to `InputFile` constructor.
- Chg #195: Remove `ResourceReaderInterface` and `NativeResourceReader`.
- Chg #195: Remove `InputFileData`.
- Chg #195: `MimeTypeResolverInterface::resolve()` now accepts `InputFile` instead of `InputFileData`.
- Chg #195: Remove `$resourceReaders` constructor parameter from `CurlTransport` and `NativeTransport`.
- Enh #194: Remove deprecated `curl_close()` call in `CurlTransport`.
- Enh #195: `CurlTransport` now uses `CURLFile` for file-path resources, avoiding loading file content into memory.

## 0.18.1 April 5, 2026

Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ $api->sendMessage(
// Send local photo
$api->sendPhoto(
chatId: 22351,
photo: InputFile::fromLocalFile('/path/to/photo.jpg'),
photo: new InputFile('/path/to/photo.jpg'),
);
```

Expand Down Expand Up @@ -143,7 +143,6 @@ $api->downloadFile($file)->saveTo('/local/path/to/file.jpg');
### Guides

- [Transport](docs/transport.md)
- [Resource readers](docs/resource-readers.md)
- [Logging](docs/logging.md)
- [Webhook handling](docs/webhook-handling.md)
- [Custom requests](docs/custom-requests.md)
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
}
},
"scripts": {
"coverage": "phpunit --coverage-html=runtime/coverage",
"cs-fix": "php-cs-fixer fix",
"dependency-analyser": "composer-dependency-analyser",
"infection": "infection --threads=max",
Expand Down
61 changes: 61 additions & 0 deletions docs/mime-type-resolvers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# MIME type resolvers

MIME type resolvers determine the content type of files sent to the Telegram Bot API. The transport uses a resolver
to set the correct `Content-Type` for each file.

## Built-in resolvers

- `ApacheMimeTypeResolver` — resolves MIME type by file extension based on
[Apache's `mime.types` file](https://svn.apache.org/repos/asf/httpd/httpd/tags/2.4.9/docs/conf/mime.types). Used by
default.
- `CustomMimeTypeResolver` — resolves MIME type by file extension using a custom map.
- `CompositeMimeTypeResolver` — combines multiple resolvers, returning the first non-null result.

## Custom MIME type resolvers

You can create a custom resolver by implementing `MimeTypeResolverInterface`:

```php
use Phptg\BotApi\Transport\MimeTypeResolver\MimeTypeResolverInterface;
use Phptg\BotApi\Type\InputFile;

final class MyMimeTypeResolver implements MimeTypeResolverInterface
{
public function resolve(InputFile $inputFile): ?string
{
// Return MIME type or null if it cannot be determined
return null;
}
}
```

Pass it to the transport constructor:

```php
use Phptg\BotApi\TelegramBotApi;
use Phptg\BotApi\Transport\NativeTransport;

$transport = new NativeTransport(
mimeTypeResolver: new MyMimeTypeResolver(),
);

$api = new TelegramBotApi($token, transport: $transport);
```

## Combining resolvers

To combine multiple resolvers use `CompositeMimeTypeResolver`:

```php
use Phptg\BotApi\Transport\MimeTypeResolver\ApacheMimeTypeResolver;
use Phptg\BotApi\Transport\MimeTypeResolver\CompositeMimeTypeResolver;
use Phptg\BotApi\Transport\MimeTypeResolver\CustomMimeTypeResolver;
use Phptg\BotApi\Transport\NativeTransport;

$transport = new NativeTransport(
mimeTypeResolver: new CompositeMimeTypeResolver(
new CustomMimeTypeResolver(['webp' => 'image/webp']),
new ApacheMimeTypeResolver(),
),
);
```
61 changes: 0 additions & 61 deletions docs/resource-readers.md

This file was deleted.

7 changes: 2 additions & 5 deletions docs/transport.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ $api = new TelegramBotApi($token, transport: $transport);

Constructor parameters:

- `$resourceReaders` — list of [resource readers](resource-readers.md) to handle different resource types. By default,
includes `NativeResourceReader`.
- `$mimeTypeResolver` — [MIME type resolver](mime-type-resolvers.md) for determining file types. Defaults to `ApacheMimeTypeResolver`.

## Native

Expand All @@ -59,9 +58,7 @@ $api = new TelegramBotApi($token, transport: $transport);

Constructor parameters:

- `$mimeTypeResolver` — MIME type resolver for determining file types. Defaults to `ApacheMimeTypeResolver`.
- `$resourceReaders` — List of [resource readers](resource-readers.md) to handle different resource types. By default,
includes `NativeResourceReader`.
- `$mimeTypeResolver` — [MIME type resolver](mime-type-resolvers.md) for determining file types. Defaults to `ApacheMimeTypeResolver`.

Available MIME type resolvers:

Expand Down
46 changes: 36 additions & 10 deletions src/Transport/CurlTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
namespace Phptg\BotApi\Transport;

use CurlShareHandle;
use CURLFile;
use CURLStringFile;
use RuntimeException;
use Phptg\BotApi\Curl\Curl;
use Phptg\BotApi\Curl\CurlException;
use Phptg\BotApi\Curl\CurlInterface;
use Phptg\BotApi\Transport\ResourceReader\NativeResourceReader;
use Phptg\BotApi\Transport\ResourceReader\ResourceReaderInterface;
use Phptg\BotApi\Transport\MimeTypeResolver\ApacheMimeTypeResolver;
use Phptg\BotApi\Transport\MimeTypeResolver\MimeTypeResolverInterface;
use Phptg\BotApi\Type\InputFile;

use function is_int;
use function is_string;

/**
* @api
Expand All @@ -22,13 +26,12 @@
private CurlShareHandle $curlShareHandle;

/**
* @param ResourceReaderInterface[] $resourceReaders List of resource readers to handle different resource types.
* @param MimeTypeResolverInterface $mimeTypeResolver MIME type resolver for determining file types. Defaults
* to {@see ApacheMimeTypeResolver}.
* @param CurlInterface $curl cURL interface implementation for making HTTP requests.
*/
public function __construct(
private array $resourceReaders = [
new NativeResourceReader(),
],
private MimeTypeResolverInterface $mimeTypeResolver = new ApacheMimeTypeResolver(),
private CurlInterface $curl = new Curl(),
) {
$this->curlShareHandle = $this->createCurlShareHandle();
Expand Down Expand Up @@ -62,10 +65,7 @@ public function post(string $url, string $body, array $headers): ApiResponse
public function postWithFiles(string $url, array $data, array $files): ApiResponse
{
foreach ($files as $key => $file) {
$data[$key] = new CURLStringFile(
(new InputFileData($file, $this->resourceReaders))->read(),
$file->filename ?? '',
);
$data[$key] = $this->toCurlFile($file);
}

$options = [
Expand All @@ -76,6 +76,32 @@ public function postWithFiles(string $url, array $data, array $files): ApiRespon
return $this->send($options);
}

private function toCurlFile(InputFile $file): CURLFile|CURLStringFile
{
$mimeType = $this->mimeTypeResolver->resolve($file);

if (is_string($file->pathOrResource)) {
return new CURLFile($file->pathOrResource, $mimeType, $file->filename());
}

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

if ($metadata['seekable']) {
rewind($file->pathOrResource);
}

$contents = stream_get_contents($file->pathOrResource);
if ($contents === false) {
// `stream_get_contents()` can return false only on error, but we can't trigger it in tests.
throw new RuntimeException('Failed to read the stream.'); // @codeCoverageIgnore
}

return new CURLStringFile($contents, $file->filename() ?? '', $mimeType ?? 'application/octet-stream');
}

public function downloadFile(string $url): mixed
{
/**
Expand Down
106 changes: 0 additions & 106 deletions src/Transport/InputFileData.php

This file was deleted.

6 changes: 3 additions & 3 deletions src/Transport/MimeTypeResolver/ApacheMimeTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Phptg\BotApi\Transport\MimeTypeResolver;

use Phptg\BotApi\Transport\InputFileData;
use Phptg\BotApi\Type\InputFile;

/**
* @see https://svn.apache.org/repos/asf/httpd/httpd/tags/2.4.9/docs/conf/mime.types
Expand Down Expand Up @@ -997,9 +997,9 @@
'ice' => 'x-conference/x-cooltalk',
];

public function resolve(InputFileData $fileData): ?string
public function resolve(InputFile $inputFile): ?string
{
$extension = $fileData->extension();
$extension = $inputFile->extension();
if ($extension === null) {
return null;
}
Expand Down
Loading
Loading