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: 1 addition & 1 deletion src/Connection/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ public function fetch(array|string $items, array|int $from, mixed $to = null, Im
/**
* Send a "RFC822.SIZE" command.
*
* Fetch message sizes.
* Fetch message sizes for one or more messages.
*
* @see https://datatracker.ietf.org/doc/html/rfc9051#section-6.4.5-9.21
*/
Expand Down
8 changes: 8 additions & 0 deletions src/FileMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public function uid(): int
throw new BadMethodCallException('FileMessage does not support a UID');
}

/**
* {@inheritDoc}
*/
public function size(): ?int
{
return strlen($this->contents);
}

/**
* {@inheritDoc}
*/
Expand Down
12 changes: 11 additions & 1 deletion src/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function __construct(
protected array $flags,
protected string $head,
protected string $body,
protected ?int $size = null,
) {}

/**
Expand All @@ -30,7 +31,7 @@ public function __construct(
public function __sleep(): array
{
// We don't want to serialize the parsed message.
return ['folder', 'uid', 'flags', 'headers', 'contents'];
return ['folder', 'uid', 'flags', 'headers', 'contents', 'size'];
}

/**
Expand All @@ -49,6 +50,14 @@ public function uid(): int
return $this->uid;
}

/**
* Get the message's size in bytes (RFC822.SIZE).
*/
public function size(): ?int
{
return $this->size;
}

/**
* Get the message's flags.
*/
Expand Down Expand Up @@ -203,6 +212,7 @@ public function toArray(): array
'flags' => $this->flags,
'head' => $this->head,
'body' => $this->body,
'size' => $this->size,
];
}

Expand Down
5 changes: 5 additions & 0 deletions src/MessageInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ interface MessageInterface extends FlaggableInterface, Stringable
*/
public function uid(): int;

/**
* Get the message's size in bytes (RFC822.SIZE).
*/
public function size(): ?int;

/**
* Get the message date and time.
*/
Expand Down
13 changes: 11 additions & 2 deletions src/MessageQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ protected function populate(Collection $uids): MessageCollection
$response['flags'] ?? [],
$response['headers'] ?? '',
$response['contents'] ?? '',
$response['size'] ?? null,
)
);
}
Expand All @@ -264,6 +265,10 @@ protected function fetch(Collection $messages): array
$fetch[] = 'FLAGS';
}

if ($this->fetchSize) {
$fetch[] = 'RFC822.SIZE';
}

if ($this->fetchBody) {
$fetch[] = $this->fetchAsUnread
? 'BODY.PEEK[TEXT]'
Expand All @@ -279,6 +284,7 @@ protected function fetch(Collection $messages): array
if (empty($fetch)) {
return $uids->mapWithKeys(fn (string|int $uid) => [
$uid => [
'size' => null,
'flags' => [],
'headers' => '',
'contents' => '',
Expand All @@ -299,8 +305,11 @@ protected function fetch(Collection $messages): array

$uid = $data->lookup('UID')->value;

$size = $data->lookup('RFC822.SIZE')?->value;

return [
$uid => [
'size' => $size ? (int) $size : null,
'flags' => $data->lookup('FLAGS')?->values() ?? [],
'headers' => $data->lookup('[HEADER]')->value ?? '',
'contents' => $data->lookup('[TEXT]')->value ?? '',
Expand Down Expand Up @@ -356,9 +365,9 @@ protected function id(int $id, ImapFetchIdentifier $identifier = ImapFetchIdenti
/**
* Make a new message from given raw components.
*/
protected function newMessage(int $uid, array $flags, string $headers, string $contents): Message
protected function newMessage(int $uid, array $flags, string $headers, string $contents, ?int $size = null): Message
{
return new Message($this->folder, $uid, $flags, $headers, $contents);
return new Message($this->folder, $uid, $flags, $headers, $contents, $size);
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/MessageQueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public function isFetchingFlags(): bool;
*/
public function isFetchingHeaders(): bool;

/**
* Determine if the size of messages is being fetched.
*/
public function isFetchingSize(): bool;

/**
* Fetch the flags of messages.
*/
Expand All @@ -76,6 +81,11 @@ public function withBody(): MessageQueryInterface;
*/
public function withHeaders(): MessageQueryInterface;

/**
* Fetch the size of messages.
*/
public function withSize(): MessageQueryInterface;

/**
* Don't fetch the body of messages.
*/
Expand All @@ -91,6 +101,11 @@ public function withoutHeaders(): MessageQueryInterface;
*/
public function withoutFlags(): MessageQueryInterface;

/**
* Don't fetch the size of messages.
*/
public function withoutSize(): MessageQueryInterface;

/**
* Set the fetch order.
*/
Expand Down
39 changes: 39 additions & 0 deletions src/QueriesMessages.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ trait QueriesMessages
*/
protected bool $fetchHeaders = false;

/**
* Whether to fetch the message size.
*/
protected bool $fetchSize = false;

/**
* The fetch order.
*
Expand Down Expand Up @@ -165,6 +170,14 @@ public function isFetchingHeaders(): bool
return $this->fetchHeaders;
}

/**
* {@inheritDoc}
*/
public function isFetchingSize(): bool
{
return $this->fetchSize;
}

/**
* {@inheritDoc}
*/
Expand All @@ -189,6 +202,14 @@ public function withHeaders(): MessageQueryInterface
return $this->setFetchHeaders(true);
}

/**
* {@inheritDoc}
*/
public function withSize(): MessageQueryInterface
{
return $this->setFetchSize(true);
}

/**
* {@inheritDoc}
*/
Expand All @@ -213,6 +234,14 @@ public function withoutFlags(): MessageQueryInterface
return $this->setFetchFlags(false);
}

/**
* {@inheritDoc}
*/
public function withoutSize(): MessageQueryInterface
{
return $this->setFetchSize(false);
}

/**
* Set whether to fetch the flags.
*/
Expand Down Expand Up @@ -243,6 +272,16 @@ protected function setFetchHeaders(bool $fetchHeaders): MessageQueryInterface
return $this;
}

/**
* Set whether to fetch the size.
*/
protected function setFetchSize(bool $fetchSize): MessageQueryInterface
{
$this->fetchSize = $fetchSize;

return $this;
}

/** {@inheritDoc} */
public function setFetchOrder(string $fetchOrder): MessageQueryInterface
{
Expand Down
9 changes: 9 additions & 0 deletions src/Testing/FakeMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public function __construct(
protected int $uid,
protected array $flags = [],
protected string $contents = '',
protected ?int $size = null,
) {}

/**
Expand All @@ -29,6 +30,14 @@ public function uid(): int
return $this->uid;
}

/**
* {@inheritDoc}
*/
public function size(): int
{
return $this->size ?? strlen($this->contents);
}

/**
* {@inheritDoc}
*/
Expand Down
52 changes: 52 additions & 0 deletions tests/Integration/MessagesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,60 @@ function folder(): Folder
fn (MessageQuery $query) => $query->withBody(),
fn (MessageQuery $query) => $query->withFlags(),
fn (MessageQuery $query) => $query->withHeaders(),
fn (MessageQuery $query) => $query->withSize(),
]);

test('get with size', function () {
$folder = folder();

$uid = $folder->messages()->append(
new DraftMessage(
from: '[email protected]',
to: '[email protected]',
subject: 'Test Subject',
text: 'hello world',
),
);

// Fetch without size - should be null
$messagesWithoutSize = $folder->messages()->get();
expect($messagesWithoutSize->first()->size())->toBeNull();

// Fetch with size - should have a value
$messagesWithSize = $folder->messages()->withSize()->get();
$message = $messagesWithSize->first();

expect($message->size())->toBeInt();
expect($message->size())->toBeGreaterThan(0);
expect($message->uid())->toBe($uid);
});

test('size reflects actual message size', function () {
$folder = folder();

$shortMessage = new DraftMessage(
from: '[email protected]',
text: 'short',
);

$longMessage = new DraftMessage(
from: '[email protected]',
text: str_repeat('This is a longer message with more content. ', 100),
);

$uid1 = $folder->messages()->append($shortMessage);
$uid2 = $folder->messages()->append($longMessage);

$messages = $folder->messages()->withSize()->get();

$short = $messages->find($uid1);
$long = $messages->find($uid2);

expect($short->size())->toBeInt();
expect($long->size())->toBeInt();
expect($long->size())->toBeGreaterThan($short->size());
});

test('append', function () {
$folder = folder();

Expand Down
15 changes: 15 additions & 0 deletions tests/Unit/FileMessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -481,3 +481,18 @@
// Different content
expect($message1->is($message3))->toBeFalse();
});

test('it can determine size from contents', function () {
$contents = <<<'EOT'
From: "John Doe" <[email protected]>
Subject: Test Subject
Date: Wed, 19 Feb 2025 12:34:56 -0500
Content-Type: text/plain; charset="UTF-8"

Test content
EOT;

$message = new FileMessage($contents);

expect($message->size())->toBe(strlen($contents));
});
12 changes: 12 additions & 0 deletions tests/Unit/Testing/FakeMessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,15 @@
expect($message->isFlagged())->toBeFalse();
expect($message->flags())->toBeEmpty();
});

test('it can get size when set', function () {
$message = new FakeMessage(1, [], 'Test content', 1024);

expect($message->size())->toBe(1024);
});

test('it returns size from contents when size is not set', function () {
$message = new FakeMessage(1, [], 'Test content');

expect($message->size())->toBe(strlen('Test content'));
});